Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions src/API/Google/Middleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Google;

use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData;
use Automattic\WooCommerce\GoogleListingsAndAds\Google\GoogleHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidTerm;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidDomainName;
Expand All @@ -20,8 +21,10 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Psr\Container\ContainerExceptionInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Psr\Container\NotFoundExceptionInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Psr\Http\Client\ClientExceptionInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Exception\BadResponseException;
use DateTime;
use Exception;
use WP_REST_Response;

defined( 'ABSPATH' ) || exit;

Expand Down Expand Up @@ -154,7 +157,7 @@ protected function create_merchant_account_request( string $name, string $site_u
throw new Exception( $error, $result->getStatusCode() );
} catch ( ClientExceptionInterface $e ) {
$message = $this->client_exception_message( $e, __( 'Error creating account', 'google-listings-and-ads' ) );

$status = $e->getCode() ?: 400;
if ( preg_match( '/terms?.* are|is not allowed/', $message ) ) {
throw InvalidTerm::contains_invalid_terms( $name );
}
Expand All @@ -168,7 +171,8 @@ protected function create_merchant_account_request( string $name, string $site_u
}

do_action( 'woocommerce_gla_guzzle_client_exception', $e, __METHOD__ );
throw new Exception( $message, $e->getCode() );

throw new Exception( $message, $status, $e );
}
}

Expand Down Expand Up @@ -218,11 +222,9 @@ public function link_merchant_to_mca(): bool {
throw new Exception( $error, $result->getStatusCode() );
} catch ( ClientExceptionInterface $e ) {
do_action( 'woocommerce_gla_guzzle_client_exception', $e, __METHOD__ );

throw new Exception(
$this->client_exception_message( $e, __( 'Error linking merchant to MCA', 'google-listings-and-ads' ) ),
$e->getCode()
);
$message = $this->client_exception_message( $e, __( 'Error linking merchant to MCA', 'google-listings-and-ads' ) );
$status = $e->getCode() ?: 400;
throw new Exception( $message, $status, $e );
}
}

Expand Down Expand Up @@ -264,11 +266,20 @@ public function claim_merchant_website( bool $overwrite = false ): bool {
} catch ( ClientExceptionInterface $e ) {
do_action( 'woocommerce_gla_guzzle_client_exception', $e, __METHOD__ );
do_action( 'woocommerce_gla_site_claim_failure', [ 'details' => 'google_manager' ] );

throw new Exception(
$this->client_exception_message( $e, __( 'Error claiming website', 'google-listings-and-ads' ) ),
$e->getCode()
);
$message = $this->client_exception_message( $e, __( 'Error claiming website', 'google-listings-and-ads' ) );
$status = $e->getCode() ?: 400;
$errors = [];
if ( $e instanceof BadResponseException ) {
$raw = json_decode( $e->getResponse()->getBody()->getContents(), true );
if ( is_array( $raw ) && ! empty( $raw['errors'] ) && is_array( $raw['errors'] ) ) {
foreach ( $raw['errors'] as $err ) {
if ( isset( $err['code'], $err['message'] ) ) {
$errors[ (string) $err['code'] ] = (string) $err['message'];
}
}
}
}
throw new Exception( $message, $status );
}
}

Expand Down Expand Up @@ -386,6 +397,20 @@ public function link_ads_account( int $id ): array {
} catch ( ClientExceptionInterface $e ) {
do_action( 'woocommerce_gla_guzzle_client_exception', $e, __METHOD__ );

if ( $e instanceof BadResponseException ) {
$raw = json_decode( $e->getResponse()->getBody()->getContents(), true );

throw new ExceptionWithResponseData(
$raw['message'] ?? __( 'Error linking ads account', 'google-listings-and-ads' ),
$e->getCode() ?: 400,
null,
[
'code' => 'API_ERROR',
'data' => $raw,
]
);
}

throw new Exception(
$this->client_exception_message( $e, __( 'Error linking account', 'google-listings-and-ads' ) ),
$e->getCode()
Expand Down
1 change: 1 addition & 0 deletions src/API/Site/Controllers/Ads/AccountController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\BaseController;
use Automattic\WooCommerce\GoogleListingsAndAds\API\TransportMethods;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Exception;
use WP_REST_Request as Request;
Expand Down
17 changes: 17 additions & 0 deletions src/API/Site/Controllers/MerchantCenter/AccountController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ApiNotReady;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\AccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\RESTServer;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData;
use Exception;
use WP_REST_Request as Request;
use WP_REST_Response as Response;
Expand Down Expand Up @@ -171,6 +172,22 @@ protected function setup_account_callback( string $action = 'setup_account' ): c
} catch ( ApiNotReady $e ) {
return $this->get_time_to_wait_response( $e );
} catch ( Exception $e ) {
if ( $e instanceof ExceptionWithResponseData ) {
$data = $e->get_response_data();
if ( isset( $data['code'] ) && 'API_ERROR' === $data['code'] ) {
$status = $e->getCode() ?: 400;
$error = is_array( $data ) ? ( $data['error'] ?? [] ) : [];
$message = is_array( $error ) && isset( $error['message'] ) ? (string) $error['message'] : $e->getMessage();
return new Response(
[
'code' => 'API_ERROR',
'message' => $message,
'data' => $error,
],
$status
);
}
}
return $this->response_from_exception( $e );
}
};
Expand Down
21 changes: 21 additions & 0 deletions src/Ads/AccountService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsAwareTrait;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\TransientsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Exception\BadResponseException;
use Exception;

defined( 'ABSPATH' ) || exit;
Expand Down Expand Up @@ -193,6 +194,26 @@ public function setup_account(): array {
$step['status'] = AdsAccountState::STEP_ERROR;
$step['message'] = $e->getMessage();
$this->state->update( $state );

if ( $e->getPrevious() instanceof BadResponseException ) {
/** @var BadResponseException $prev */
$prev = $e->getPrevious();
$body = method_exists( $prev, 'getResponse' ) && $prev->getResponse() ? (string) $prev->getResponse()->getBody() : '';
$decoded = json_decode( $body, true );
$error = is_array( $decoded ) ? ( $decoded['error'] ?? [] ) : [];
$message = is_array( $error ) && isset( $error['message'] ) ? (string) $error['message'] : $e->getMessage();

throw new ExceptionWithResponseData(
$message,
$e->getCode() ?: 400,
null,
[
'code' => 'API_ERROR',
'data' => $decoded,
]
);
}

throw $e;
}
}
Expand Down
34 changes: 32 additions & 2 deletions src/MerchantCenter/AccountService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\TransientsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\Psr\Container\ContainerInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Exception\BadResponseException;
use Exception;
use Jetpack_Options;

Expand Down Expand Up @@ -141,6 +141,10 @@ public function use_existing_account_id( int $account_id ): void {
} catch ( ExceptionWithResponseData $e ) {
throw $e;
} catch ( Exception $e ) {
if ( $e->getPrevious() instanceof BadResponseException ) {
throw $this->prepare_api_error_exception( $e );
}

throw $this->prepare_exception( $e->getMessage(), [], $e->getCode() );
}
}
Expand All @@ -165,6 +169,9 @@ public function setup_account( int $account_id ) {
} catch ( ExceptionWithResponseData | ApiNotReady $e ) {
throw $e;
} catch ( Exception $e ) {
if ( $e->getPrevious() instanceof BadResponseException ) {
throw $this->prepare_api_error_exception( $e );
}
throw $this->prepare_exception( $e->getMessage(), [], $e->getCode() );
}
}
Expand Down Expand Up @@ -465,7 +472,6 @@ private function maybe_add_merchant_center_url( int $merchant_id ) {
/** @var Merchant $merchant */
$merchant = $this->container->get( Merchant::class );

/** @var Account $account */
$account = $merchant->get_account( $merchant_id );
$account_url = $account->getWebsiteUrl() ?: '';

Expand Down Expand Up @@ -552,6 +558,30 @@ private function prepare_exception( string $message, array $data = [], ?int $cod
return new ExceptionWithResponseData( $message, $code ?: 400, null, $data );
}

/**
* Prepares an API error Exception to be thrown with Merchant data.
*
* @param Exception $e The caught exception.
*
* @return ExceptionWithResponseData
*/
private function prepare_api_error_exception( Exception $e ) {
/** @var BadResponseException $prev */
$prev = $e->getPrevious();
$body = method_exists( $prev, 'getResponse' ) && $prev->getResponse() ? (string) $prev->getResponse()->getBody() : '';
$decoded = json_decode( $body, true );
$error = is_array( $decoded ) ? ( $decoded['error'] ?? [] ) : [];
$message = is_array( $error ) && isset( $error['message'] ) ? (string) $error['message'] : $e->getMessage();
return $this->prepare_exception(
$message,
[
'code' => 'API_ERROR',
'error' => $decoded,
],
$e->getCode() ?: 400
);
}

/**
* Delete the option regarding WPCOM authorization
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,35 @@ public function test_create_account_with_api_exception_data() {
$this->assertEquals( 406, $response->get_status() );
}

public function test_create_account_with_structured_error_response() {
$this->account->expects( $this->once() )
->method( 'setup_account' )
->willThrowException(
new ExceptionWithResponseData(
'creation failed',
400,
null,
[
'code' => 'API_ERROR',
'error' => [
'message' => 'creation failed',
'status' => 400,
'details' => [ 'reason' => 'invalid' ],
],
]
)
);

$response = $this->do_request( self::ROUTE_ACCOUNTS, 'POST' );

$data = $response->get_data();
$this->assertEquals( 'API_ERROR', $data['code'] );
$this->assertEquals( 'creation failed', $data['message'] );
$this->assertIsArray( $data['data'] );
$this->assertEquals( 'creation failed', $data['data']['message'] );
$this->assertEquals( 400, $response->get_status() );
}

public function test_create_account_with_time_to_wait() {
$this->account->expects( $this->once() )
->method( 'setup_account' )
Expand Down
22 changes: 22 additions & 0 deletions tests/proxy/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,27 @@ module.exports.checkRequest = ( request, h ) => {
}
}

if (
request.params.path.includes( 'google/manager/link-customer' ) &&
request.method === 'post'
) {
return h
.response(
require( './mocks/ads/connection/link-existing-account-error.json' )
)
.code( 400 );
}

if (
request.params.path.includes( 'google/manager/link-merchant' ) &&
request.method === 'post'
) {
return h
.response(
require( './mocks/mc/connection/link-existing-account-error.json' )
)
.code( 400 );
}

return false;
};
34 changes: 34 additions & 0 deletions tests/proxy/mocks/ads/connection/link-existing-account-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"statusCode": 400,
"message": "Unable to accept link for the customer account",
"error": {
"code": 400,
"message": "Request contains an invalid argument.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.ads.googleads.v20.errors.GoogleAdsFailure",
"errors": [
{
"errorCode": {
"managerLinkError": "TOO_MANY_MANAGERS"
},
"message": "Client is already linked to too many managers.",
"trigger": {
"int64Value": "6530335391"
},
"location": {
"fieldPathElements": [
{
"fieldName": "operations",
"index": 0
}
]
}
}
],
"requestId": "T-Ayj9dDBlp2VI4yuiq3Kw"
}
]
}
}
15 changes: 15 additions & 0 deletions tests/proxy/mocks/mc/connection/link-existing-account-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"statusCode": 400,
"message": "Unable to link merchant center account",
"error": {
"code": 400,
"message": "You do not have necessary permissions to perform this action.",
"errors": [
{
"message": "You do not have necessary permissions to perform this action.",
"domain": "global",
"reason": "invalid"
}
]
}
}
Loading