diff --git a/_dev/js/front/src/components/1_6/payment-options.component.js b/_dev/js/front/src/components/1_6/payment-options.component.js index 374d2f41f..076078574 100644 --- a/_dev/js/front/src/components/1_6/payment-options.component.js +++ b/_dev/js/front/src/components/1_6/payment-options.component.js @@ -52,7 +52,8 @@ export class PaymentOptionsComponent extends BaseComponent { } renderPaymentOptionListener() { - const HTMLListenerElements = this.children.paymentOptions.map((paymentOption) => { + const HTMLListenerElements = this.children.paymentOptions.map( + (paymentOption) => { const HTMLElement = paymentOption.data.HTMLElementContainer; const [button, form] = Array.prototype.slice.call( HTMLElement.querySelectorAll('.payment_module') @@ -74,21 +75,28 @@ export class PaymentOptionsComponent extends BaseComponent { this.data.notification.hideError(); }); - if (this.config.expressCheckout.active && (('ps_checkout-' + this.payPalService.getFundingSource()) !== HTMLListenerElements[index].button.dataset.moduleName)) { - this.psCheckoutApi.postCancelOrder( - { + if ( + this.config.expressCheckout.active && + 'ps_checkout-' + this.payPalService.getFundingSource() !== + HTMLListenerElements[index].button.dataset.moduleName + ) { + this.psCheckoutApi + .postCancelOrder({ orderID: this.payPalService.getOrderId(), fundingSource: this.payPalService.getFundingSource(), - isExpressCheckout: true - } - ).then(() => { - this.config.expressCheckout.active = false; + isExpressCheckout: true, + reason: 'payment_option_changed' + }) + .then(() => { + this.config.expressCheckout.active = false; - const expressCheckoutContainer = document.querySelector('#ps_checkout-express-checkout-banner'); - if (expressCheckoutContainer) { - expressCheckoutContainer.style.display = 'none'; - } - }); + const expressCheckoutContainer = document.querySelector( + '#ps_checkout-express-checkout-banner' + ); + if (expressCheckoutContainer) { + expressCheckoutContainer.style.display = 'none'; + } + }); } HTMLListenerElements[index].button.classList.add('open'); @@ -100,7 +108,10 @@ export class PaymentOptionsComponent extends BaseComponent { if (this.config.expressCheckout.active) { HTMLListenerElements.forEach(({ button, form }) => { - if (button.dataset.moduleName === ('ps_checkout-' + this.payPalService.getFundingSource())) { + if ( + button.dataset.moduleName === + 'ps_checkout-' + this.payPalService.getFundingSource() + ) { button.classList.add('open'); button.classList.remove('closed'); form.classList.add('open'); diff --git a/_dev/js/front/src/components/1_7/payment-options.component.js b/_dev/js/front/src/components/1_7/payment-options.component.js index 31a38cc62..a4d47d0bd 100644 --- a/_dev/js/front/src/components/1_7/payment-options.component.js +++ b/_dev/js/front/src/components/1_7/payment-options.component.js @@ -40,7 +40,10 @@ export class PaymentOptionsComponent extends BaseComponent { `[data-module-name^="ps_checkout-${fundingSource.name}"]` ); - if (this.config.expressCheckout.active && this.payPalService.getFundingSource() === fundingSource.name) { + if ( + this.config.expressCheckout.active && + this.payPalService.getFundingSource() === fundingSource.name + ) { HTMLElement.click(); } @@ -63,21 +66,28 @@ export class PaymentOptionsComponent extends BaseComponent { this.data.notification.hideCancelled(); this.data.notification.hideError(); - if (this.config.expressCheckout.active && (('ps_checkout-' + this.payPalService.getFundingSource()) !== radio.dataset.moduleName)) { - this.psCheckoutApi.postCancelOrder( - { + if ( + this.config.expressCheckout.active && + 'ps_checkout-' + this.payPalService.getFundingSource() !== + radio.dataset.moduleName + ) { + this.psCheckoutApi + .postCancelOrder({ orderID: this.payPalService.getOrderId(), fundingSource: this.payPalService.getFundingSource(), - isExpressCheckout: true - } - ).then(() => { - this.config.expressCheckout.active = false; + isExpressCheckout: true, + reason: 'payment_option_changed' + }) + .then(() => { + this.config.expressCheckout.active = false; - const expressCheckoutContainer = document.querySelector('#ps_checkout-express-checkout-banner'); - if (expressCheckoutContainer) { - expressCheckoutContainer.style.display = 'none'; - } - }); + const expressCheckoutContainer = document.querySelector( + '#ps_checkout-express-checkout-banner' + ); + if (expressCheckoutContainer) { + expressCheckoutContainer.style.display = 'none'; + } + }); } }); }); diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 49c4cc883..45b766ba9 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -263,6 +263,15 @@ export class CardFieldsComponent extends BaseComponent { let message = error.message || this.$('checkout.form.error.label'); this.data.notification.showError(message); this.data.HTMLElementButton.removeAttribute('disabled'); + + return this.psCheckoutApi + .postCancelOrder({ + fundingSource: this.data.name, + isExpressCheckout: this.config.expressCheckout.active, + reason: 'card_fields_error', + error: error instanceof Error ? error.message : error + }) + .catch((error) => console.error(error)); }, inputEvents: { /** diff --git a/_dev/js/front/src/components/common/express-checkout-button.component.js b/_dev/js/front/src/components/common/express-checkout-button.component.js index 8bdcf624b..583a3fbc6 100644 --- a/_dev/js/front/src/components/common/express-checkout-button.component.js +++ b/_dev/js/front/src/components/common/express-checkout-button.component.js @@ -37,7 +37,7 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { ...data, fundingSource: this.props.fundingSource, isExpressCheckout: true, - orderID: this.payPalService.getOrderId(), + orderID: this.payPalService.getOrderId() }, actions ) @@ -48,7 +48,16 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { } onError(error) { - return console.error(error); + console.error(error); + + return this.psCheckoutApi + .postCancelOrder({ + fundingSource: this.props.fundingSource, + isExpressCheckout: true, + reason: 'express_checkout_error', + error: error instanceof Error ? error.message : error + }) + .catch((error) => console.error(error)); } onApprove(data, actions) { @@ -66,7 +75,8 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { return this.psCheckoutApi.postCancelOrder({ ...data, fundingSource: this.props.fundingSource, - isExpressCheckout: true + isExpressCheckout: true, + reason: 'express_checkout_cancelled' }); } diff --git a/_dev/js/front/src/components/common/smart-button.component.js b/_dev/js/front/src/components/common/smart-button.component.js index 56473d0af..4456eecaf 100644 --- a/_dev/js/front/src/components/common/smart-button.component.js +++ b/_dev/js/front/src/components/common/smart-button.component.js @@ -91,41 +91,53 @@ export class SmartButtonComponent extends BaseComponent { } return this.psCheckoutApi - .postCheckCartOrder({ + .postCheckCartOrder( + { ...data, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active, - orderID: this.payPalService.getOrderId(), + orderID: this.payPalService.getOrderId() }, actions ) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError(error.message); return actions.reject(); }); }, - onError: error => { + onError: (error) => { + let errorMessage = this.handleError(error); console.error(error); this.data.loader.hide(); - this.data.notification.showError(this.handleError(error)); + this.data.notification.showError(errorMessage); + + return this.psCheckoutApi + .postCancelOrder({ + fundingSource: this.data.name, + isExpressCheckout: this.config.expressCheckout.active, + reason: 'checkout_error', + error: errorMessage + }) + .catch((error) => console.error(error)); }, onApprove: (data, actions) => { this.data.loader.show(); return this.psCheckoutApi - .postValidateOrder({ + .postValidateOrder( + { ...data, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active }, actions ) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError(error.message); }); }, - onCancel: data => { + onCancel: (data) => { this.data.loader.hide(); this.data.notification.showCanceled(); @@ -133,21 +145,22 @@ export class SmartButtonComponent extends BaseComponent { .postCancelOrder({ ...data, fundingSource: this.data.name, - isExpressCheckout: this.config.expressCheckout.active + isExpressCheckout: this.config.expressCheckout.active, + reason: 'checkout_cancelled' }) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError(error.message); }); }, - createOrder: data => { + createOrder: (data) => { return this.psCheckoutApi .postCreateOrder({ ...data, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active }) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError( `${error.message} ${error.name}` @@ -165,10 +178,16 @@ export class SmartButtonComponent extends BaseComponent { if (error.message) { errorMessage = error.message; - if (error.message.includes('CURRENCY_NOT_SUPPORTED_BY_PAYMENT_SOURCE')) { - errorMessage = 'Provided currency is not supported by the selected payment method.'; - } else if (error.message.includes('COUNTRY_NOT_SUPPORTED_BY_PAYMENT_SOURCE')) { - errorMessage = 'Provided country is not supported by the selected payment method.'; + if ( + error.message.includes('CURRENCY_NOT_SUPPORTED_BY_PAYMENT_SOURCE') + ) { + errorMessage = + 'Provided currency is not supported by the selected payment method.'; + } else if ( + error.message.includes('COUNTRY_NOT_SUPPORTED_BY_PAYMENT_SOURCE') + ) { + errorMessage = + 'Provided country is not supported by the selected payment method.'; } } } diff --git a/config/common.yml b/config/common.yml index da0e5ec26..891cd9802 100644 --- a/config/common.yml +++ b/config/common.yml @@ -236,10 +236,6 @@ services: arguments: - "@ps_checkout.module" - ps_checkout.funding_source.entity: - class: 'PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceEntity' - public: true - ps_checkout.repository.country: class: 'PrestaShop\Module\PrestashopCheckout\Repository\CountryRepository' public: true @@ -367,8 +363,11 @@ services: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderSummaryViewBuilder' public: true arguments: - - '@ps_checkout.translations.translations' - - '@ps_checkout.funding_source.translation' + - '@ps_checkout.repository.pscheckoutcart' + - '@ps_checkout.paypal.provider.order' + - '@ps_checkout.prestashop.router' + - '@ps_checkout.paypal.order.translations' + - '@ps_checkout.context.shop' ps_checkout.paypal.builder.view_order_summary: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderSummaryViewBuilder' @@ -456,18 +455,24 @@ services: PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand: "ps_checkout.command.handler.order.create_order" PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand: "ps_checkout.command.handler.order.update_order_status" PrestaShop\Module\PrestashopCheckout\Order\Matrice\Command\UpdateOrderMatriceCommand: "ps_checkout.command.handler.order.matrice.update_order_matrice" + PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CreatePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.create_paypal_order" + PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.update_paypal_order" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.capture_paypal_order" - PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.save_paypal_order" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand: "ps_checkout.command.handler.checkout.cancel_checkout" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\SaveCheckoutCommand: "ps_checkout.command.handler.checkout.save_checkout" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\SavePayPalOrderStatusCommand: "ps_checkout.command.handler.checkout.save_paypal_order_status" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQuery: "ps_checkout.query.handler.order.get_order_for_payment_completed" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQuery: "ps_checkout.query.handler.order.get_order_for_payment_denied" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery: "ps_checkout.query.handler.order.get_order_for_payment_pending" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery: "ps_checkout.query.handler.order.get_order_for_payment_refunded" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery: "ps_checkout.query.handler.order.get_order_for_payment_reversed" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQuery: "ps_checkout.query.handler.order.get_order_for_approval_reversed" + PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_cart_id" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQuery: "ps_checkout.query.handler.paypal.order.get_current_paypal_order_status" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_order_confirmation" PrestaShop\Module\PrestashopCheckout\Checkout\Command\UpdatePaymentMethodSelectedCommand: "ps_checkout.query.handler.checkout.update_payment_method_selected" + PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler' ps_checkout.event.dispatcher.factory: class: 'PrestaShop\Module\PrestashopCheckout\Event\SymfonyEventDispatcherFactory' @@ -510,6 +515,16 @@ services: - "@ps_checkout.cache.paypal.order" - "@ps_checkout.order.state.service.order_state_mapper" + PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber\PayPalRefundEventSubscriber: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber\PayPalRefundEventSubscriber' + arguments: + - '@ps_checkout.module' + - '@ps_checkout.order.service.check_order_amount' + - '@ps_checkout.cache.paypal.capture' + - '@ps_checkout.cache.paypal.order' + - '@ps_checkout.order.state.service.order_state_mapper' + - '@ps_checkout.paypal.provider.order' + ps_checkout.event.dispatcher.symfony: class: 'Symfony\Component\EventDispatcher\EventDispatcherInterface' factory: ["@ps_checkout.event.dispatcher.factory", "create"] @@ -519,6 +534,7 @@ services: "@ps_checkout.event.subscriber.order", "@ps_checkout.event.subscriber.paypal.order", "@ps_checkout.event.subscriber.paypal.capture", + '@PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber\PayPalRefundEventSubscriber' ] ps_checkout.event.dispatcher: @@ -558,18 +574,29 @@ services: arguments: - "@ps_checkout.event.dispatcher" - ps_checkout.command.handler.paypal.order.capture_paypal_order: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CapturePayPalOrderCommandHandler' + ps_checkout.command.handler.paypal.order.create_paypal_order: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CreatePayPalOrderCommandHandler' public: true arguments: + - "@ps_checkout.http.client.checkout" - "@ps_checkout.event.dispatcher" - - "@ps_checkout.cache.paypal.order" + - "@ps_checkout.context.shop" - ps_checkout.command.handler.paypal.order.save_paypal_order: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\SavePayPalOrderCommandHandler' + ps_checkout.command.handler.paypal.order.update_paypal_order: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\UpdatePayPalOrderCommandHandler' public: true arguments: - - "@ps_checkout.repository.pscheckoutcart" + - "@ps_checkout.http.client.checkout" + - "@ps_checkout.event.dispatcher" + - "@ps_checkout.context.shop" + + ps_checkout.command.handler.paypal.order.capture_paypal_order: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CapturePayPalOrderCommandHandler' + public: true + arguments: + - "@ps_checkout.http.client.checkout" + - "@ps_checkout.event.dispatcher" + - "@ps_checkout.cache.paypal.order" ps_checkout.query.handler.paypal.order.get_current_paypal_order_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetCurrentPayPalOrderStatusQueryHandler' @@ -613,6 +640,13 @@ services: arguments: - "@ps_checkout.repository.pscheckoutcart" + ps_checkout.query.handler.paypal.order.get_paypal_order_for_cart_id: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForCartIdQueryHandler' + public: true + arguments: + - '@ps_checkout.cache.paypal.order' + - '@ps_checkout.repository.pscheckoutcart' + ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForCheckoutCompletedQueryHandler' public: true @@ -625,12 +659,41 @@ services: arguments: - "@ps_checkout.cache.paypal.order" + ps_checkout.command.handler.checkout.save_checkout: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\SaveCheckoutCommandHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + + ps_checkout.command.handler.checkout.cancel_checkout: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\CancelCheckoutCommandHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + + ps_checkout.command.handler.checkout.save_paypal_order_status: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\SavePayPalOrderStatusCommandHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + ps_checkout.query.handler.checkout.update_payment_method_selected: class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\UpdatePaymentMethodSelectedCommandHandler' public: true arguments: - "@ps_checkout.repository.pscheckoutcart" + PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler' + arguments: + - '@ps_checkout.http.client.checkout' + - '@ps_checkout.paypal.configuration' + - '@ps_checkout.configuration' + - '@ps_checkout.context.prestashop' + - '@ps_checkout.event.dispatcher' + - '@ps_checkout.cache.paypal.order' + - '@ps_checkout.paypal.provider.order' + ps_checkout.paypal.capture.service.check_transition_paypal_capture_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\CheckTransitionPayPalCaptureStatusService' public: true @@ -640,3 +703,34 @@ services: public: true arguments: - "@ps_checkout.logger" + + ps_checkout.environment.payment: + class: 'PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv' + public: true + + ps_checkout.http.client.configuration: + class: 'PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClientConfigurationBuilder' + public: true + arguments: + - "@ps_checkout.environment.payment" + - "@ps_checkout.prestashop.router" + - "@ps_checkout.context.shop" + - "@ps_checkout.repository.prestashop.account" + - "@ps_checkout.context.prestashop" + + ps_checkout.http.client.factory: + class: 'PrestaShop\Module\PrestashopCheckout\Http\HttpClientFactory' + public: true + + ps_checkout.http.client: + class: 'PrestaShop\Module\PrestashopCheckout\Http\HttpClientInterface' + public: true + factory: ["@ps_checkout.http.client.factory", "create"] + arguments: + - "@ps_checkout.http.client.configuration" + + ps_checkout.http.client.checkout: + class: 'PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient' + public: true + arguments: + - "@ps_checkout.http.client" diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index cce1e0ae7..c6630e0b2 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -19,6 +19,7 @@ */ use Monolog\Logger; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Configuration\BatchConfigurationProcessor; use PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration; use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceConfigurationRepository; @@ -33,6 +34,9 @@ use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller; use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Mode; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundFailedException; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalPayLaterConfiguration; @@ -41,7 +45,6 @@ use PrestaShop\Module\PrestashopCheckout\Settings\RoundingSettings; use PrestaShop\Module\PrestashopCheckout\Validator\BatchConfigurationValidator; use PrestaShop\Module\PrestashopCheckout\Webhook\WebhookSecretTokenService; -use Psr\SimpleCache\CacheInterface; class AdminAjaxPrestashopCheckoutController extends ModuleAdminController { @@ -439,92 +442,60 @@ public function ajaxProcessFetchOrder() public function ajaxProcessRefundOrder() { $orderPayPalId = Tools::getValue('orderPayPalRefundOrder'); - $transactionPayPalId = Tools::getValue('orderPayPalRefundTransaction'); + $captureId = Tools::getValue('orderPayPalRefundTransaction'); $amount = Tools::getValue('orderPayPalRefundAmount'); $currency = Tools::getValue('orderPayPalRefundCurrency'); - if (empty($orderPayPalId) || false === Validate::isGenericName($orderPayPalId)) { - http_response_code(400); - $this->ajaxDie(json_encode([ - 'status' => false, - 'errors' => [ - $this->l('PayPal Order is invalid.', 'translations'), - ], - ])); - } + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); - if (empty($transactionPayPalId) || false === Validate::isGenericName($transactionPayPalId)) { - http_response_code(400); - $this->ajaxDie(json_encode([ - 'status' => false, - 'errors' => [ - $this->l('PayPal Transaction is invalid.', 'translations'), - ], - ])); - } + try { + $commandBus->handle(new RefundPayPalCaptureCommand($orderPayPalId, $captureId, $currency, $amount)); - if (empty($amount) || false === Validate::isPrice($amount) || $amount <= 0) { - http_response_code(400); $this->ajaxDie(json_encode([ - 'status' => false, - 'errors' => [ - $this->l('PayPal refund amount is invalid.', 'translations'), - ], + 'status' => true, + 'content' => $this->l('Refund has been processed by PayPal.', 'translations'), ])); - } - - if (empty($currency) || false === in_array($currency, ['AUD', 'BRL', 'CAD', 'CZK', 'DKK', 'EUR', 'HKD', 'HUF', 'INR', 'ILS', 'JPY', 'MYR', 'MXN', 'TWD', 'NZD', 'NOK', 'PHP', 'PLN', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD'])) { - // https://developer.paypal.com/docs/api/reference/currency-codes/ - http_response_code(400); + } catch (PayPalRefundFailedException $exception) { + http_response_code($exception->getCode()); $this->ajaxDie(json_encode([ 'status' => false, 'errors' => [ - $this->l('PayPal refund currency is invalid.', 'translations'), + $this->l('Refund cannot be processed by PayPal.', 'translations'), ], ])); - } - - /** @var PayPalConfiguration $configurationPayPal */ - $configurationPayPal = $this->module->getService('ps_checkout.paypal.configuration'); - - $response = (new PrestaShop\Module\PrestashopCheckout\Api\Payment\Order($this->context->link))->refund([ - 'orderId' => $orderPayPalId, - 'captureId' => $transactionPayPalId, - 'payee' => [ - 'merchant_id' => $configurationPayPal->getMerchantId(), - ], - 'amount' => [ - 'currency_code' => $currency, - 'value' => $amount, - ], - 'note_to_payer' => 'Refund by ' - . Configuration::get( - 'PS_SHOP_NAME', - null, - null, - (int) Context::getContext()->shop->id - ), - ]); - - if (isset($response['httpCode']) && $response['httpCode'] === 200) { - /** @var CacheInterface $orderPayPalCache */ - $orderPayPalCache = $this->module->getService('ps_checkout.cache.paypal.order'); - if ($orderPayPalCache->has($orderPayPalId)) { - $orderPayPalCache->delete($orderPayPalId); + } catch (PayPalRefundException $invalidArgumentException) { + http_response_code(400); + $error = ''; + switch ($invalidArgumentException->getCode()) { + case PayPalRefundException::INVALID_ORDER_ID: + $error = $this->l('PayPal Order is invalid.', 'translations'); + break; + case PayPalRefundException::INVALID_TRANSACTION_ID: + $error = $this->l('PayPal Transaction is invalid.', 'translations'); + break; + case PayPalRefundException::INVALID_CURRENCY: + $error = $this->l('PayPal refund currency is invalid.', 'translations'); + break; + case PayPalRefundException::INVALID_AMOUNT: + $error = $this->l('PayPal refund amount is invalid.', 'translations'); + break; + default: + break; } - $this->ajaxDie(json_encode([ - 'status' => true, - 'content' => $this->l('Refund has been processed by PayPal.', 'translations'), + 'status' => false, + 'errors' => [$error], ])); - } else { - http_response_code(isset($response['httpCode']) ? (int) $response['httpCode'] : 500); - $this->ajaxDie(json_encode([ + } catch (Exception $exception) { + $this->exitWithResponse([ + 'httpCode' => 500, 'status' => false, 'errors' => [ $this->l('Refund cannot be processed by PayPal.', 'translations'), ], - ])); + 'error' => $exception->getMessage(), + ]); } } diff --git a/controllers/front/cancel.php b/controllers/front/cancel.php index bccd1d7fe..ac4d507e7 100644 --- a/controllers/front/cancel.php +++ b/controllers/front/cancel.php @@ -18,8 +18,9 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; /** * This controller receive ajax call on customer canceled payment @@ -68,6 +69,8 @@ public function postProcess() $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : null; $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; + $reason = isset($bodyValues['reason']) ? Tools::safeOutput($bodyValues['reason']) : null; + $error = isset($bodyValues['error']) ? Tools::safeOutput($bodyValues['error']) : null; if (empty($orderId)) { $this->exitWithResponse([ @@ -76,27 +79,28 @@ public function postProcess() ]); } - /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($orderId); + $commandBus->handle(new CancelCheckoutCommand( + $this->context->cart->id, + $orderId, + PsCheckoutCart::STATUS_CANCELED, + $fundingSource, + $isExpressCheckout, + $isHostedFields + )); - if (false !== $psCheckoutCart) { - $psCheckoutCart->paypal_funding = $fundingSource; - $psCheckoutCart->isExpressCheckout = $isExpressCheckout; - $psCheckoutCart->isHostedFields = $isHostedFields; - $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; - $psCheckoutCartRepository->save($psCheckoutCart); - } - - $this->module->getLogger()->info( + $this->module->getLogger()->log( + $error ? 400 : 200, 'Customer canceled payment', [ 'PayPalOrderId' => $orderId, 'FundingSource' => $fundingSource, 'isExpressCheckout' => $isExpressCheckout, 'isHostedFields' => $isHostedFields, + 'reason' => $reason, + 'error' => $error, ] ); diff --git a/controllers/front/check.php b/controllers/front/check.php index 7f38cc04e..875b107d9 100644 --- a/controllers/front/check.php +++ b/controllers/front/check.php @@ -18,11 +18,11 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\UpdatePaymentMethodSelectedCommand; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Handler\CreatePaypalOrderHandler; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; -use Psr\SimpleCache\CacheInterface; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCommand; /** * This controller receive ajax call on customer click on a payment button @@ -67,6 +67,7 @@ public function postProcess() ]); } + $cartId = (int) $this->context->cart->id; $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : 'paypal'; $orderId = isset($bodyValues['orderID']) ? $bodyValues['orderID'] : null; $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; @@ -79,52 +80,14 @@ public function postProcess() ]); } - /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); - - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($orderId); - - if (false === $psCheckoutCart) { - $psCheckoutCart = new PsCheckoutCart(); - $psCheckoutCart->id_cart = (int) $this->context->cart->id; - } - - if ($fundingSource) { - $psCheckoutCart->paypal_funding = $fundingSource; - } - - $psCheckoutCart->isExpressCheckout = $isExpressCheckout; - $psCheckoutCart->isHostedFields = $isHostedFields; - $psCheckoutCartRepository->save($psCheckoutCart); - - if (false === empty($psCheckoutCart->paypal_order)) { - $paypalOrder = new CreatePaypalOrderHandler($this->context); - $response = $paypalOrder->handle($isExpressCheckout || empty($this->context->cart->id_address_delivery), $psCheckoutCart->paypal_funding === 'card', true, $psCheckoutCart->paypal_order); - - if (false === $response['status']) { - $this->module->getLogger()->error( - 'Failed to patch PayPal Order', - [ - 'PayPalOrderId' => $orderId, - 'FundingSource' => $fundingSource, - 'isExpressCheckout' => $isExpressCheckout, - 'isHostedFields' => $isHostedFields, - 'id_cart' => (int) $this->context->cart->id, - 'response' => $response, - ] - ); - $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; - $psCheckoutCartRepository->save($psCheckoutCart); - throw new PsCheckoutException(sprintf('Unable to patch PayPal Order - Exception %s : %s', $response['exceptionCode'], $response['exceptionMessage']), PsCheckoutException::PSCHECKOUT_UPDATE_ORDER_HANDLE_ERROR); - } - - /** @var CacheInterface $orderPayPalCache */ - $orderPayPalCache = $this->module->getService('ps_checkout.cache.paypal.order'); - $orderPayPalCache->delete($psCheckoutCart->getPaypalOrderId()); - - $this->module->getLogger()->info( - 'PayPal Order patched', + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); + $commandBus->handle(new UpdatePaymentMethodSelectedCommand($cartId, $orderId, $fundingSource, $isExpressCheckout, $isHostedFields)); + try { + $commandBus->handle(new UpdatePayPalOrderCommand($orderId, $cartId, $fundingSource, $isHostedFields, $isExpressCheckout)); + } catch (Exception $exception) { + $this->module->getLogger()->error( + 'Failed to patch PayPal Order', [ 'PayPalOrderId' => $orderId, 'FundingSource' => $fundingSource, @@ -133,6 +96,14 @@ public function postProcess() 'id_cart' => (int) $this->context->cart->id, ] ); + $commandBus->handle(new CancelCheckoutCommand( + $this->context->cart->id, + $orderId, + PsCheckoutCart::STATUS_CANCELED, + $fundingSource, + $isExpressCheckout, + $isHostedFields + )); } $this->exitWithResponse([ diff --git a/controllers/front/create.php b/controllers/front/create.php index 182b4da49..b5fadb430 100755 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -19,11 +19,11 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Handler\CreatePaypalOrderHandler; -use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CreatePayPalOrderCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQueryResult; /** * This controller receive ajax call to create a PayPal Order @@ -93,97 +93,25 @@ public function postProcess() } // END Express Checkout - if (false === Validate::isLoadedObject($this->context->cart)) { - $this->exitWithResponse([ - 'httpCode' => 400, - 'body' => 'No cart found.', - ]); - } - - /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); - - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByCartId((int) $this->context->cart->id); - - $isExpressCheckout = (isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']) || empty($this->context->cart->id_address_delivery); - - if (false !== $psCheckoutCart && $psCheckoutCart->isExpressCheckout() && $psCheckoutCart->isOrderAvailable()) { - $this->exitWithResponse([ - 'status' => true, - 'httpCode' => 200, - 'body' => [ - 'orderID' => $psCheckoutCart->paypal_order, - ], - 'exceptionCode' => null, - 'exceptionMessage' => null, - ]); - } - - // If we have a PayPal Order Id with a status CREATED or APPROVED or PAYER_ACTION_REQUIRED we mark it as CANCELED and create new one - // This is needed because cart gets updated so we need to update paypal order too - if ( - false !== $psCheckoutCart && $psCheckoutCart->getPaypalOrderId() - ) { - $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; - $psCheckoutCartRepository->save($psCheckoutCart); - $psCheckoutCart = false; - } - - $paypalOrder = new CreatePaypalOrderHandler($this->context); - $response = $paypalOrder->handle($isExpressCheckout, isset($bodyValues['fundingSource']) && $bodyValues['fundingSource'] === 'card'); + $cartId = (int) $this->context->cart->id; - if (false === $response['status']) { - throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); - } - - if (empty($response['body']['id'])) { - throw new PsCheckoutException('Paypal order id is missing.', PsCheckoutException::PAYPAL_ORDER_IDENTIFIER_MISSING); - } - - $paymentSource = isset($response['body']['payment_source']) ? key($response['body']['payment_source']) : 'paypal'; - $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : $paymentSource; - $orderId = isset($bodyValues['orderID']) ? $bodyValues['orderID'] : null; - $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; + $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : 'paypal'; $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; - /** @var PayPalConfiguration $configuration */ - $configuration = $this->module->getService('ps_checkout.paypal.configuration'); - - $this->module->getLogger()->info( - 'PayPal Order created', - [ - 'PayPalOrderId' => $orderId, - 'FundingSource' => $fundingSource, - 'isExpressCheckout' => $isExpressCheckout, - 'isHostedFields' => $isHostedFields, - 'id_cart' => (int) $this->context->cart->id, - 'amount' => $this->context->cart->getOrderTotal(true, Cart::BOTH), - 'environment' => $configuration->getPaymentMode(), - 'intent' => $configuration->getIntent(), - ] - ); + $isExpressCheckout = (isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']) || empty($this->context->cart->id_address_delivery); - if (false === $psCheckoutCart) { - $psCheckoutCart = new PsCheckoutCart(); - $psCheckoutCart->id_cart = (int) $this->context->cart->id; - } + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); + $commandBus->handle(new CreatePayPalOrderCommand($cartId, $fundingSource, $isHostedFields, $isExpressCheckout)); - $psCheckoutCart->paypal_funding = $fundingSource; - $psCheckoutCart->paypal_order = $response['body']['id']; - $psCheckoutCart->paypal_status = $response['body']['status']; - $psCheckoutCart->paypal_intent = $configuration->getIntent(); - $psCheckoutCart->paypal_token = $response['body']['client_token']; - $psCheckoutCart->paypal_token_expire = (new DateTime())->modify('+3550 seconds')->format('Y-m-d H:i:s'); - $psCheckoutCart->environment = $configuration->getPaymentMode(); - $psCheckoutCart->isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; - $psCheckoutCart->isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; - $psCheckoutCartRepository->save($psCheckoutCart); + /** @var GetPayPalOrderForCartIdQueryResult $getPayPalOrderForCartIdQueryResult */ + $getPayPalOrderForCartIdQueryResult = $commandBus->handle(new GetPayPalOrderForCartIdQuery($cartId)); + $order = $getPayPalOrderForCartIdQueryResult->getOrder(); $this->exitWithResponse([ 'status' => true, 'httpCode' => 200, 'body' => [ - 'orderID' => $psCheckoutCart->paypal_order, + 'orderID' => $order['id'], ], 'exceptionCode' => null, 'exceptionMessage' => null, diff --git a/src/Api/Payment/Order.php b/src/Api/Payment/Order.php index 9b4b9db89..18f97a0d5 100644 --- a/src/Api/Payment/Order.php +++ b/src/Api/Payment/Order.php @@ -25,10 +25,13 @@ /** * Handle order requests + * + * @deprecated */ class Order extends PaymentClient { /** + * @deprecated * Create order to paypal api * * @param array $payload Cart details (json) @@ -43,6 +46,7 @@ public function create($payload) } /** + * @deprecated * Capture order funds * * @param string $orderId paypal @@ -65,6 +69,7 @@ public function capture($orderId, $merchantId, $fundingSource) } /** + * @deprecated * Get paypal order details * * @param string $orderId paypal @@ -95,6 +100,7 @@ public function authorize($orderId, $merchantId) } /** + * @deprecated * Refund an order * * @param array $payload @@ -109,6 +115,7 @@ public function refund($payload) } /** + * @deprecated * Patch paypal order * * @param array $payload diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index 61011b1b4..7d75a802c 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -109,8 +109,8 @@ public function buildFullPayload() } if ($this->isCard) { - $this->buildPaymentSourceNode(); - $this->buildSupplementaryDataNode(); + //$this->buildPaymentSourceNode(); + //$this->buildSupplementaryDataNode(); } } diff --git a/src/Checkout/Command/CancelCheckoutCommand.php b/src/Checkout/Command/CancelCheckoutCommand.php new file mode 100644 index 000000000..50c7530c5 --- /dev/null +++ b/src/Checkout/Command/CancelCheckoutCommand.php @@ -0,0 +1,128 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class CancelCheckoutCommand +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @var string + */ + private $orderPayPalStatus; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @param int $cartId + * @param string $orderPayPalId + * @param string $orderPayPalStatus + * @param string $fundingSource + * @param bool $isExpressCheckout + * @param bool $isHostedFields + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($cartId, $orderPayPalId, $orderPayPalStatus, $fundingSource, $isExpressCheckout, $isHostedFields) + { + $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->orderPayPalStatus = $orderPayPalStatus; + $this->fundingSource = $fundingSource; + $this->isExpressCheckout = $isExpressCheckout; + $this->isHostedFields = $isHostedFields; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return PayPalOrderId + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return string + */ + public function getOrderPayPalStatus() + { + return $this->orderPayPalStatus; + } +} diff --git a/src/Checkout/Command/SaveCheckoutCommand.php b/src/Checkout/Command/SaveCheckoutCommand.php new file mode 100644 index 000000000..748bdbb07 --- /dev/null +++ b/src/Checkout/Command/SaveCheckoutCommand.php @@ -0,0 +1,158 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class SaveCheckoutCommand +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @var string + */ + private $orderPayPalStatus; + + /** + * @var string + */ + private $intent; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var string + */ + private $environment; + + /** + * @param int $cartId + * @param string $orderPayPalId + * @param string $orderPayPalStatus + * @param string $intent + * @param string $fundingSource + * @param bool $isExpressCheckout + * @param bool $isHostedFields + * @param string $environment + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($cartId, $orderPayPalId, $orderPayPalStatus, $intent, $fundingSource, $isExpressCheckout, $isHostedFields, $environment) + { + $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->orderPayPalStatus = $orderPayPalStatus; + $this->intent = $intent; + $this->fundingSource = $fundingSource; + $this->isExpressCheckout = $isExpressCheckout; + $this->isHostedFields = $isHostedFields; + $this->environment = $environment; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return PayPalOrderId + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return string + */ + public function getOrderPayPalStatus() + { + return $this->orderPayPalStatus; + } + + /** + * @return string + */ + public function getIntent() + { + return $this->intent; + } + + /** + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } +} diff --git a/src/PayPal/Order/Command/SavePayPalOrderCommand.php b/src/Checkout/Command/SavePayPalOrderStatusCommand.php similarity index 80% rename from src/PayPal/Order/Command/SavePayPalOrderCommand.php rename to src/Checkout/Command/SavePayPalOrderStatusCommand.php index 4533468e8..cdda30214 100644 --- a/src/PayPal/Order/Command/SavePayPalOrderCommand.php +++ b/src/Checkout/Command/SavePayPalOrderStatusCommand.php @@ -1,5 +1,4 @@ orderPayPalId = new PayPalOrderId($orderPayPalId); $this->orderPayPalStatus = $orderPayPalStatus; - $this->orderPayPal = $orderPayPal; } /** @@ -66,16 +58,8 @@ public function getOrderPayPalId() /** * @return string */ - public function getOrderPaypalStatus() + public function getOrderPayPalStatus() { return $this->orderPayPalStatus; } - - /** - * @return array - */ - public function getOrderPayPal() - { - return $this->orderPayPal; - } } diff --git a/src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php b/src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php new file mode 100644 index 000000000..36610d03a --- /dev/null +++ b/src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php @@ -0,0 +1,68 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Exception\PsCheckoutSessionException; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PsCheckoutCart; + +class CancelCheckoutCommandHandler +{ + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + } + + /** + * @param CancelCheckoutCommand $command + * + * @throws PsCheckoutSessionException + */ + public function handle(CancelCheckoutCommand $command) + { + try { + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($command->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new CartNotFoundException(sprintf('Unable to retrieve PrestaShop Cart #%s', var_export($command->getCartId()->getValue(), true))); + } + + $psCheckoutCart->id_cart = $command->getCartId()->getValue(); + $psCheckoutCart->paypal_order = $command->getOrderPayPalId()->getValue(); + $psCheckoutCart->paypal_funding = $command->getFundingSource(); + $psCheckoutCart->isHostedFields = $command->isHostedFields(); + $psCheckoutCart->isExpressCheckout = $command->isExpressCheckout(); + $psCheckoutCart->paypal_status = $command->getOrderPayPalStatus(); + $this->psCheckoutCartRepository->save($psCheckoutCart); + } catch (Exception $exception) { + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($command->getCartId()->getValue(), true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); + } + } +} diff --git a/src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php b/src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php new file mode 100644 index 000000000..3855f1bbb --- /dev/null +++ b/src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php @@ -0,0 +1,69 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\SaveCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Exception\PsCheckoutSessionException; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PsCheckoutCart; + +class SaveCheckoutCommandHandler +{ + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + } + + /** + * @param SaveCheckoutCommand $command + * + * @throws PsCheckoutSessionException + */ + public function handle(SaveCheckoutCommand $command) + { + try { + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByCartId($command->getCartId()->getValue()); + + if (false === $psCheckoutCart) { + $psCheckoutCart = new PsCheckoutCart(); + } + + $psCheckoutCart->id_cart = $command->getCartId()->getValue(); + $psCheckoutCart->paypal_order = $command->getOrderPayPalId()->getValue(); + $psCheckoutCart->paypal_funding = $command->getFundingSource(); + $psCheckoutCart->isHostedFields = $command->isHostedFields(); + $psCheckoutCart->isExpressCheckout = $command->isExpressCheckout(); + $psCheckoutCart->paypal_status = $command->getOrderPayPalStatus(); + $psCheckoutCart->paypal_intent = $command->getIntent(); + $psCheckoutCart->environment = $command->getEnvironment(); + $this->psCheckoutCartRepository->save($psCheckoutCart); + } catch (Exception $exception) { + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($command->getCartId()->getValue(), true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); + } + } +} diff --git a/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php b/src/Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php similarity index 52% rename from src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php rename to src/Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php index f083a905f..00483385b 100644 --- a/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php +++ b/src/Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php @@ -1,5 +1,4 @@ psCheckoutCartRepository = $psCheckoutCartRepository; } - public function handle(SavePayPalOrderCommand $savePayPalOrderCommand) + /** + * @param SavePayPalOrderStatusCommand $command + * + * @throws PsCheckoutSessionException + */ + public function handle(SavePayPalOrderStatusCommand $command) { try { /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($savePayPalOrderCommand->getOrderPayPalId()->getValue()); + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($command->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new CartNotFoundException(sprintf('Unable to retrieve PayPal Order %s', var_export($command->getOrderPayPalId()->getValue(), true))); + } - $psCheckoutCart->paypal_order = $savePayPalOrderCommand->getOrderPayPalId()->getValue(); - $psCheckoutCart->paypal_status = $savePayPalOrderCommand->getOrderPaypalStatus(); - $psCheckoutCart->paypal_token = null; - $psCheckoutCart->paypal_token_expire = null; + $psCheckoutCart->paypal_order = $command->getOrderPayPalId()->getValue(); + $psCheckoutCart->paypal_status = $command->getOrderPayPalStatus(); $this->psCheckoutCartRepository->save($psCheckoutCart); } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PrestaShop cart #%d', $savePayPalOrderCommand->getOrderPayPalId()->getValue()), PayPalOrderException::SESSION_EXCEPTION, $exception); + $sessionId = isset($psCheckoutCart) ? $psCheckoutCart->getIdCart() : $command->getOrderPayPalId()->getValue(); + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($sessionId, true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); } } } diff --git a/src/Controller/AbstractFrontController.php b/src/Controller/AbstractFrontController.php index f58cb7169..6de73b31c 100644 --- a/src/Controller/AbstractFrontController.php +++ b/src/Controller/AbstractFrontController.php @@ -40,7 +40,12 @@ protected function exitWithExceptionMessage(Exception $exception) $this->exitWithResponse([ 'status' => false, 'httpCode' => 500, - 'body' => '', + 'body' => [ + 'error' => [ + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + ], + ], 'exceptionCode' => $exception->getCode(), 'exceptionMessage' => $exception->getMessage(), ]); diff --git a/src/Dispatcher/OrderDispatcher.php b/src/Dispatcher/OrderDispatcher.php index 039ec85c9..d3a91a8ba 100644 --- a/src/Dispatcher/OrderDispatcher.php +++ b/src/Dispatcher/OrderDispatcher.php @@ -30,9 +30,9 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureRefundedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureReversedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; use Ps_checkout; use Psr\Log\LoggerInterface; diff --git a/src/Exception/PayPalException.php b/src/Exception/PayPalException.php index 015b0a728..710268a8b 100644 --- a/src/Exception/PayPalException.php +++ b/src/Exception/PayPalException.php @@ -154,4 +154,23 @@ class PayPalException extends PsCheckoutException const CARD_BRAND_NOT_SUPPORTED = 129; const RESOURCE_NOT_FOUND = 130; const PAYMENT_SOURCE_CANNOT_BE_USED = 131; + const CANNOT_PROCESS_REFUNDS = 132; + const INVALID_REFUND_AMOUNT = 133; + const PUI_DUPLICATE_ORDER = 134; + const SAVE_ORDER_NOT_SUPPORTED = 135; + const CARD_CLOSED = 136; + const UNSUPPORTED_SHIPPING_TYPE = 137; + const SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT = 138; + const PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED = 139; + const PAYMENT_SOURCE_DECLINED_BY_PROCESSOR = 140; + const MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED = 141; + const MULTIPLE_ITEM_CATEGORIES = 142; + const MISSING_PICKUP_ADDRESS = 143; + const DONATION_ITEMS_NOT_SUPPORTED = 144; + const CARD_EXPIRED = 145; + const BILLING_ADDRESS_INVALID = 146; + const MALFORMED_REQUEST = 147; + const PERMISSION_DENIED_FOR_DONATION_ITEMS = 148; + const MALFORMED_REQUEST_JSON = 149; + const PAYPAL_REQUEST_ID_REQUIRED = 150; } diff --git a/src/Handler/CreatePaypalOrderHandler.php b/src/Handler/CreatePaypalOrderHandler.php index d7599d2b0..cb34dc75d 100644 --- a/src/Handler/CreatePaypalOrderHandler.php +++ b/src/Handler/CreatePaypalOrderHandler.php @@ -23,9 +23,11 @@ use Context; use Module; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; use PrestaShop\Module\PrestashopCheckout\Builder\Payload\OrderPayloadBuilder; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; use PrestaShop\Module\PrestashopCheckout\Presenter\Cart\CartPresenter; use PrestaShop\Module\PrestashopCheckout\ShopContext; use Ps_checkout; @@ -91,25 +93,36 @@ public function handle($expressCheckout = false, $isCardPayment = false, $update $payload = $builder->presentPayload()->getArray(); - // Create the paypal order or update it - if (true === $updateOrder) { - $paypalOrder = (new Order($this->context->link))->patch($payload); - } else { - $paypalOrder = (new Order($this->context->link))->create($payload); - } - - // Retry with minimal payload when full payload failed (only on 1.7) - if (substr((string) $paypalOrder['httpCode'], 0, 1) === '4' && $shopContext->isShop17()) { - $builder->buildMinimalPayload(); - $payload = $builder->presentPayload()->getArray(); + /** @var CheckoutHttpClient $checkoutHttpClient */ + $checkoutHttpClient = $module->getService('ps_checkout.http.client.checkout'); + // Create the paypal order or update it + try { if (true === $updateOrder) { - $paypalOrder = (new Order($this->context->link))->patch($payload); + $response = $checkoutHttpClient->updateOrder($payload); + } else { + $response = $checkoutHttpClient->createOrder($payload); + } + } catch (PayPalException $exception) { + $previousException = $exception->getPrevious(); + $response = method_exists($previousException, 'getResponse') ? $previousException->getResponse() : null; + // Retry with minimal payload when full payload failed (only on 1.7) + if ($response && substr((string) $response->getStatusCode(), 0, 1) === '4' && $shopContext->isShop17()) { + $builder->buildMinimalPayload(); + $payload = $builder->presentPayload()->getArray(); + + if (true === $updateOrder) { + $response = $checkoutHttpClient->updateOrder($payload); + } else { + $response = $checkoutHttpClient->createOrder($payload); + } } else { - $paypalOrder = (new Order($this->context->link))->create($payload); + throw $exception; } } - return $paypalOrder; + $responseHandler = new ResponseApiHandler(); + + return $responseHandler->handleResponse($response); } } diff --git a/src/Handler/Response/ResponseApiHandler.php b/src/Handler/Response/ResponseApiHandler.php index 8fa0cc3c0..da912f8f5 100644 --- a/src/Handler/Response/ResponseApiHandler.php +++ b/src/Handler/Response/ResponseApiHandler.php @@ -36,8 +36,7 @@ class ResponseApiHandler */ public function handleResponse(ResponseInterface $response) { - $response->getBody()->seek(0); // Rewind Stream to avoid empty body - $responseContents = json_decode($response->getBody()->getContents(), true); + $responseContents = json_decode($response->getBody(), true); return [ 'status' => $this->responseIsSuccessful($responseContents, $response->getStatusCode()), diff --git a/src/Http/CheckoutHttpClient.php b/src/Http/CheckoutHttpClient.php new file mode 100644 index 000000000..0d03828cf --- /dev/null +++ b/src/Http/CheckoutHttpClient.php @@ -0,0 +1,184 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use GuzzleHttp\Psr7\Request; +use Http\Client\Exception\HttpException; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\Exception\TransferException; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\PayPalError; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +class CheckoutHttpClient implements HttpClientInterface +{ + /** + * @var HttpClientInterface + */ + private $httpClient; + + public function __construct(HttpClientInterface $httpClient) + { + $this->httpClient = $httpClient; + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function createOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/create', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function updateOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/update', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function fetchOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/fetch', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function captureOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function refundOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/refund', $options, json_encode($payload))); + } + + /** + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function sendRequest(RequestInterface $request) + { + try { + $response = $this->httpClient->sendRequest($request); + } catch (HttpException $exception) { + $response = $exception->getResponse(); + $message = $this->extractMessage(json_decode($response->getBody(), true)); + + if ($message) { + (new PayPalError($message))->throwException($exception); + } + + throw $exception; + } + + return $response; + } + + /** + * @param array $body + * + * @return string + */ + private function extractMessage(array $body) + { + if (isset($body['details'][0]['issue']) && preg_match('/^[0-9A-Z_]+$/', $body['details'][0]['issue']) === 1) { + return $body['details'][0]['issue']; + } + + if (isset($body['error']) && preg_match('/^[0-9A-Z_]+$/', $body['error']) === 1) { + return $body['error']; + } + + if (isset($body['message']) && preg_match('/^[0-9A-Z_]+$/', $body['message']) === 1) { + return $body['message']; + } + + if (isset($body['name']) && preg_match('/^[0-9A-Z_]+$/', $body['name']) === 1) { + return $body['name']; + } + + return ''; + } +} diff --git a/src/Http/CheckoutHttpClientConfigurationBuilder.php b/src/Http/CheckoutHttpClientConfigurationBuilder.php new file mode 100644 index 000000000..03f5bb8b9 --- /dev/null +++ b/src/Http/CheckoutHttpClientConfigurationBuilder.php @@ -0,0 +1,108 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; +use PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv; +use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; +use PrestaShop\Module\PrestashopCheckout\Routing\Router; +use PrestaShop\Module\PrestashopCheckout\ShopContext; +use Ps_checkout; + +class CheckoutHttpClientConfigurationBuilder implements HttpClientConfigurationBuilderInterface +{ + const TIMEOUT = 10; + + /** + * @var PaymentEnv + */ + private $paymentEnv; + + /** + * @var Router + */ + private $router; + + /** + * @var ShopContext + */ + private $shopContext; + + /** + * @var PsAccountRepository + */ + private $psAccountRepository; + + /** + * @var PrestaShopContext + */ + private $prestaShopContext; + + public function __construct( + PaymentEnv $paymentEnv, + Router $router, + ShopContext $shopContext, + PsAccountRepository $psAccountRepository, + PrestaShopContext $prestaShopContext + ) { + $this->paymentEnv = $paymentEnv; + $this->router = $router; + $this->shopContext = $shopContext; + $this->psAccountRepository = $psAccountRepository; + $this->prestaShopContext = $prestaShopContext; + } + + /** + * @return array + */ + public function build() + { + return [ + 'base_url' => $this->paymentEnv->getPaymentApiUrl(), + 'verify' => $this->getVerify(), + 'timeout' => static::TIMEOUT, + 'headers' => [ + 'Content-Type' => 'application/vnd.checkout.v1+json', // api version to use (psl side) + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->psAccountRepository->getIdToken(), // Token we get from PsAccounts + 'Shop-Id' => $this->psAccountRepository->getShopUuid(), // Shop UUID we get from PsAccounts + 'Hook-Url' => $this->router->getDispatchWebhookLink($this->prestaShopContext->getShopId()), + 'Bn-Code' => $this->shopContext->getBnCode(), + 'Module-Version' => Ps_checkout::VERSION, // version of the module + 'Prestashop-Version' => _PS_VERSION_, // prestashop version + ], + ]; + } + + /** + * @see https://docs.guzzlephp.org/en/5.3/clients.html#verify + * + * @return true|string + */ + protected function getVerify() + { + if (defined('_PS_CACHE_CA_CERT_FILE_') && file_exists(constant('_PS_CACHE_CA_CERT_FILE_'))) { + return constant('_PS_CACHE_CA_CERT_FILE_'); + } + + return true; + } +} diff --git a/src/Http/HttpClientConfigurationBuilderInterface.php b/src/Http/HttpClientConfigurationBuilderInterface.php new file mode 100644 index 000000000..232ab832d --- /dev/null +++ b/src/Http/HttpClientConfigurationBuilderInterface.php @@ -0,0 +1,29 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +interface HttpClientConfigurationBuilderInterface +{ + /** + * @return array + */ + public function build(); +} diff --git a/src/Http/HttpClientFactory.php b/src/Http/HttpClientFactory.php new file mode 100644 index 000000000..6c1ca840c --- /dev/null +++ b/src/Http/HttpClientFactory.php @@ -0,0 +1,34 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +class HttpClientFactory +{ + /** + * @param HttpClientConfigurationBuilderInterface $httpClientConfigurationBuilder + * + * @return HttpClientInterface + */ + public function create(HttpClientConfigurationBuilderInterface $httpClientConfigurationBuilder) + { + return new PsrHttpClientAdapter($httpClientConfigurationBuilder->build()); + } +} diff --git a/src/Http/HttpClientInterface.php b/src/Http/HttpClientInterface.php new file mode 100644 index 000000000..275432b11 --- /dev/null +++ b/src/Http/HttpClientInterface.php @@ -0,0 +1,50 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use Http\Client\Exception\HttpException; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\Exception\TransferException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Interface HttpClientInterface + * + * This interface provides a PSR-18 compliant implementation for PHP 5.6 + */ +interface HttpClientInterface +{ + /** + * Sends a PSR-7 request and returns a PSR-7 response. + * + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + */ + public function sendRequest(RequestInterface $request); +} diff --git a/src/Http/PsrHttpClientAdapter.php b/src/Http/PsrHttpClientAdapter.php new file mode 100644 index 000000000..fac735f2d --- /dev/null +++ b/src/Http/PsrHttpClientAdapter.php @@ -0,0 +1,63 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use Http\Client\Exception\HttpException; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\TransferException; +use Prestashop\ModuleLibGuzzleAdapter\ClientFactory; +use Psr\Http\Message\RequestInterface; + +class PsrHttpClientAdapter implements HttpClientInterface +{ + private $client; + + /** + * @param array $configuration + */ + public function __construct(array $configuration) + { + $this->client = (new ClientFactory())->getClient($configuration); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + try { + $response = $this->client->sendRequest($request); + } catch (\GuzzleHttp\Ring\Exception\ConnectException $exception) { + // Guzzle 5.3 use RingPHP for the low level connection + throw new NetworkException($exception->getMessage(), $request, $exception); + } catch (\GuzzleHttp\Ring\Exception\RingException $exception) { + // Guzzle 5.3 use RingPHP for the low level connection + throw new TransferException($exception->getMessage(), 0, $exception); + } + + // Guzzle 5.3 does not throw exceptions on 4xx and 5xx status codes + if ($response->getStatusCode() >= 400) { + throw new HttpException($response->getReasonPhrase(), $request, $response); + } + + return $response; + } +} diff --git a/src/PayPal/Order/Command/CreatePayPalOrderCommand.php b/src/PayPal/Order/Command/CreatePayPalOrderCommand.php new file mode 100644 index 000000000..b3b8998ef --- /dev/null +++ b/src/PayPal/Order/Command/CreatePayPalOrderCommand.php @@ -0,0 +1,95 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; + +class CreatePayPalOrderCommand +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @param int $cartId + * @param string $fundingSource + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * + * @throws CartException + */ + public function __construct($cartId, $fundingSource, $isHostedFields, $isExpressCheckout) + { + $this->cartId = new CartId($cartId); + $this->fundingSource = $fundingSource; + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } +} diff --git a/src/PayPal/Order/Command/UpdatePayPalOrderCommand.php b/src/PayPal/Order/Command/UpdatePayPalOrderCommand.php new file mode 100644 index 000000000..ed125614c --- /dev/null +++ b/src/PayPal/Order/Command/UpdatePayPalOrderCommand.php @@ -0,0 +1,112 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class UpdatePayPalOrderCommand +{ + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @var CartId + */ + private $cartId; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @param string $orderPayPalId + * @param int $cartId + * @param string $fundingSource + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * + * @throws CartException|PayPalOrderException + */ + public function __construct($orderPayPalId, $cartId, $fundingSource, $isHostedFields, $isExpressCheckout) + { + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->cartId = new CartId($cartId); + $this->fundingSource = $fundingSource; + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + } + + /** + * @return PayPalOrderId + */ + public function getPayPalOrderId() + { + return $this->orderPayPalId; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } +} diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index af28d2068..7731c10d2 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -23,9 +23,9 @@ use Configuration; use Context; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; @@ -33,7 +33,6 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus; -use PrestaShop\Module\PrestashopCheckout\PayPalError; use PrestaShop\Module\PrestashopCheckout\PayPalProcessorResponse; use Psr\SimpleCache\CacheInterface; @@ -43,13 +42,20 @@ class CapturePayPalOrderCommandHandler * @var EventDispatcherInterface */ private $eventDispatcher; + /** * @var CacheInterface */ private $orderPayPalCache; - public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) + /** + * @var CheckoutHttpClient + */ + private $httpClient; + + public function __construct(CheckoutHttpClient $httpClient, EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) { + $this->httpClient = $httpClient; $this->eventDispatcher = $eventDispatcher; $this->orderPayPalCache = $orderPayPalCache; } @@ -58,34 +64,16 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) { $context = Context::getContext(); $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); - $apiOrder = new Order($context->link); - $response = $apiOrder->capture( - $capturePayPalOrderCommand->getOrderId()->getValue(), - $merchantId, - $capturePayPalOrderCommand->getFundingSource() - ); - - if (false === $response['status']) { - if (isset($response['body']['details'][0]['issue'])) { - (new PayPalError($response['body']['details'][0]['issue']))->throwException(); - } - - if (isset($response['body']['name'])) { - (new PayPalError($response['body']['name']))->throwException(); - } - - if (false === empty($response['body']['message'])) { - (new PayPalError($response['body']['message']))->throwException(); - } - - if (false === empty($response['exceptionMessage']) && false === empty($response['exceptionCode'])) { - throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); - } - - throw new PsCheckoutException(isset($response['body']['error']) ? $response['body']['error'] : 'Unknown error', PsCheckoutException::UNKNOWN); - } - $orderPayPal = $response['body']; + $response = $this->httpClient->captureOrder([ + 'mode' => $capturePayPalOrderCommand->getFundingSource(), + 'orderId' => $capturePayPalOrderCommand->getOrderId()->getValue(), + 'payee' => [ + 'merchant_id' => $merchantId, + ], + ]); + + $orderPayPal = json_decode($response->getBody(), true); $payPalOrderFromCache = $this->orderPayPalCache->get($orderPayPal['id']); diff --git a/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php new file mode 100644 index 000000000..d3a9f4233 --- /dev/null +++ b/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php @@ -0,0 +1,95 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; + +use PrestaShop\Module\PrestashopCheckout\Builder\Payload\OrderPayloadBuilder; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CreatePayPalOrderCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCreatedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\Presenter\Cart\CartPresenter; +use PrestaShop\Module\PrestashopCheckout\ShopContext; + +class CreatePayPalOrderCommandHandler +{ + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var CheckoutHttpClient + */ + private $httpClient; + /** + * @var ShopContext + */ + private $shopContext; + + public function __construct( + CheckoutHttpClient $httpClient, + EventDispatcherInterface $eventDispatcher, + ShopContext $shopContext + ) { + $this->httpClient = $httpClient; + $this->eventDispatcher = $eventDispatcher; + $this->shopContext = $shopContext; + } + + /** + * @param CreatePayPalOrderCommand $command + * + * @return void + * + * @throws PayPalException + * @throws PayPalOrderException + * @throws PsCheckoutException + */ + public function handle(CreatePayPalOrderCommand $command) + { + $cartPresenter = (new CartPresenter())->present(); + $builder = new OrderPayloadBuilder($cartPresenter); + $builder->setIsCard($command->getFundingSource() === 'card'); + $builder->setExpressCheckout($command->isExpressCheckout()); + + if ($this->shopContext->isShop17()) { + // Build full payload in 1.7 + $builder->buildFullPayload(); + } else { + // if on 1.6 always build minimal payload + $builder->buildMinimalPayload(); + } + + $response = $this->httpClient->createOrder($builder->presentPayload()->getArray()); + $order = json_decode($response->getBody(), true); + $this->eventDispatcher->dispatch(new PayPalOrderCreatedEvent( + $order['id'], + $order, + $command->getCartId()->getValue(), + $command->isHostedFields(), + $command->isExpressCheckout(), + $command->getFundingSource() + )); + } +} diff --git a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php new file mode 100644 index 000000000..57eb02a1c --- /dev/null +++ b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php @@ -0,0 +1,104 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\Builder\Payload\OrderPayloadBuilder; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderUpdatedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\Presenter\Cart\CartPresenter; +use PrestaShop\Module\PrestashopCheckout\ShopContext; + +class UpdatePayPalOrderCommandHandler +{ + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var CheckoutHttpClient + */ + private $httpClient; + + /** + * @var ShopContext + */ + private $shopContext; + + /** + * @param CheckoutHttpClient $httpClient + * @param EventDispatcherInterface $eventDispatcher + * @param ShopContext $shopContext + */ + public function __construct( + CheckoutHttpClient $httpClient, + EventDispatcherInterface $eventDispatcher, + ShopContext $shopContext + ) { + $this->httpClient = $httpClient; + $this->eventDispatcher = $eventDispatcher; + $this->shopContext = $shopContext; + } + + /** + * @param UpdatePayPalOrderCommand $command + * + * @return void + * + * @throws CartException|PayPalException|PayPalOrderException|PsCheckoutException|Exception + */ + public function handle(UpdatePayPalOrderCommand $command) + { + $cartPresenter = (new CartPresenter())->present(); + $builder = new OrderPayloadBuilder($cartPresenter, true); + $builder->setIsUpdate(true); + $builder->setPaypalOrderId($command->getPayPalOrderId()->getValue()); + $builder->setIsCard($command->getFundingSource() === 'card'); + $builder->setExpressCheckout($command->isExpressCheckout()); + + if ($this->shopContext->isShop17()) { + // Build full payload in 1.7 + $builder->buildFullPayload(); + } else { + // if on 1.6 always build minimal payload + $builder->buildMinimalPayload(); + } + + $response = $this->httpClient->updateOrder($builder->presentPayload()->getArray()); + $order = json_decode($response->getBody(), true); + + $this->eventDispatcher->dispatch(new PayPalOrderUpdatedEvent( + $order['id'], + $order, + $command->getCartId()->getValue(), + $command->isHostedFields(), + $command->isExpressCheckout(), + $command->getFundingSource() + )); + } +} diff --git a/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php b/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php index 375348587..53c1e46ee 100644 --- a/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php +++ b/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php @@ -20,6 +20,81 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; + class PayPalOrderCreatedEvent extends PayPalOrderEvent { + /** + * @var CartId + */ + private $cartId; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var string + */ + private $fundingSource; + + /** + * @param string $orderPayPalId + * @param array $orderPayPal + * @param int $cartId + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * @param string $fundingSource + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($orderPayPalId, $orderPayPal, $cartId, $isHostedFields, $isExpressCheckout, $fundingSource) + { + parent::__construct($orderPayPalId, $orderPayPal); + $this->cartId = new CartId($cartId); + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + $this->fundingSource = $fundingSource; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } } diff --git a/src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php b/src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php new file mode 100644 index 000000000..cc2f43dbf --- /dev/null +++ b/src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php @@ -0,0 +1,100 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; + +class PayPalOrderUpdatedEvent extends PayPalOrderEvent +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var string + */ + private $fundingSource; + + /** + * @param string $orderPayPalId + * @param array $orderPayPal + * @param int $cartId + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * @param string $fundingSource + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($orderPayPalId, $orderPayPal, $cartId, $isHostedFields, $isExpressCheckout, $fundingSource) + { + parent::__construct($orderPayPalId, $orderPayPal); + $this->cartId = new CartId($cartId); + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + $this->fundingSource = $fundingSource; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } +} diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index 5cf29b9a9..a4d5328c7 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -22,6 +22,8 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\EventSubscriber; use PrestaShop\Module\PrestashopCheckout\Checkout\CheckoutChecker; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\SaveCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\SavePayPalOrderStatusCommand; use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; @@ -32,13 +34,14 @@ use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\CheckTransitionPayPalOrderStatusService; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovalReversedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCreatedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderUpdatedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; use Ps_checkout; use Psr\SimpleCache\CacheInterface; @@ -122,25 +125,27 @@ public static function getSubscribedEvents() ['setApprovalReversedOrderStatus'], ['clearCache'], ], + PayPalOrderUpdatedEvent::class => [ + ['clearCache'], + ], ]; } public function saveCreatedPayPalOrder(PayPalOrderCreatedEvent $event) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); - - if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - - if (!$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($psCheckoutCart->getPaypalStatus(), PayPalOrderStatus::CREATED)) { - return; - } + /** @var PayPalConfiguration $configuration */ + $configuration = $this->module->getService('ps_checkout.paypal.configuration'); + $order = $event->getOrderPayPal(); - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SaveCheckoutCommand( + $event->getCartId()->getValue(), $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::CREATED, - $event->getOrderPayPal() + $order['status'], + $order['intent'], + $event->getFundingSource(), + $event->isExpressCheckout(), + $event->isHostedFields(), + $configuration->getPaymentMode() )); } @@ -156,10 +161,9 @@ public function saveApprovedPayPalOrder(PayPalOrderApprovedEvent $event) return; } - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderStatusCommand( $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::APPROVED, - $event->getOrderPayPal() + PayPalOrderStatus::APPROVED )); } @@ -175,10 +179,9 @@ public function saveCompletedPayPalOrder(PayPalOrderCompletedEvent $event) return; } - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderStatusCommand( $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::COMPLETED, - $event->getOrderPayPal() + PayPalOrderStatus::COMPLETED )); } @@ -194,10 +197,9 @@ public function saveApprovalReversedPayPalOrder(PayPalOrderApprovalReversedEvent return; } - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderStatusCommand( $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::REVERSED, - $event->getOrderPayPal() + PayPalOrderStatus::REVERSED )); } @@ -265,7 +267,7 @@ public function updateCache(PayPalOrderEvent $event) $this->orderPayPalCache->set($event->getOrderPayPalId()->getValue(), $newOrderPayPal); } - public function clearCache(PayPalOrderApprovalReversedEvent $event) + public function clearCache(PayPalOrderEvent $event) { $this->orderPayPalCache->delete($event->getOrderPayPalId()->getValue()); } diff --git a/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php new file mode 100644 index 000000000..345fd6ac2 --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php @@ -0,0 +1,25 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +class GetPayPalOrderForAdminViewQuery +{ +} diff --git a/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php new file mode 100644 index 000000000..9ee9a9a3b --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php @@ -0,0 +1,25 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +class GetPayPalOrderForAdminViewQueryResult +{ +} diff --git a/src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php new file mode 100644 index 000000000..c9232288d --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php @@ -0,0 +1,50 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; + +class GetPayPalOrderForCartIdQuery +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @param int $cartId + * + * @throws CartException + */ + public function __construct($cartId) + { + $this->cartId = new CartId($cartId); + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } +} diff --git a/src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php new file mode 100644 index 000000000..ea663dbf4 --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php @@ -0,0 +1,45 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +class GetPayPalOrderForCartIdQueryResult +{ + /** + * @var array + */ + private $order; + + /** + * @param array{id: string, status: string, intent: string, payment_source: array, purchase_units: array, payer: array, create_time: string, links: array} $order + */ + public function __construct(array $order) + { + $this->order = $order; + } + + /** + * @return array{id: string, status: string, intent: string, payment_source: array, purchase_units: array, payer: array, create_time: string, links: array} + */ + public function getOrder() + { + return $this->order; + } +} diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php new file mode 100644 index 000000000..c41d2f7a4 --- /dev/null +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php @@ -0,0 +1,32 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForAdminViewQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForAdminViewQueryResult; + +class GetPayPalOrderForAdminViewQueryHandler +{ + public function handle(GetPayPalOrderForAdminViewQuery $query) + { + return new GetPayPalOrderForAdminViewQueryResult(); + } +} diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php new file mode 100644 index 000000000..256e36a68 --- /dev/null +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php @@ -0,0 +1,66 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQueryResult; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use Psr\SimpleCache\CacheInterface; + +class GetPayPalOrderForCartIdQueryHandler +{ + /** + * @var CacheInterface + */ + private $orderPayPalCache; + /** + * @var PsCheckoutCartRepository + */ + private $checkoutCartRepository; + + public function __construct(CacheInterface $orderPayPalCache, PsCheckoutCartRepository $checkoutCartRepository) + { + $this->orderPayPalCache = $orderPayPalCache; + $this->checkoutCartRepository = $checkoutCartRepository; + } + + /** + * @param GetPayPalOrderForCartIdQuery $getPayPalOrderQuery + * + * @return GetPayPalOrderForCartIdQueryResult + * + * @throws PayPalOrderException + */ + public function handle(GetPayPalOrderForCartIdQuery $getPayPalOrderQuery) + { + $psCheckoutCart = $this->checkoutCartRepository->findOneByCartId($getPayPalOrderQuery->getCartId()->getValue()); + + /** @var array $order */ + $order = $this->orderPayPalCache->get($psCheckoutCart->getPaypalOrderId()); + + if (empty($order)) { + throw new PayPalOrderException('PayPal order not found', PayPalOrderException::CANNOT_RETRIEVE_ORDER); + } + + return new GetPayPalOrderForCartIdQueryResult($order); + } +} diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index c4979cc41..eefa235de 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -31,8 +31,6 @@ use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQueryResult; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; @@ -42,7 +40,6 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureRefundedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureReversedEvent; use Ps_checkout; use Psr\SimpleCache\CacheInterface; @@ -116,10 +113,6 @@ public static function getSubscribedEvents() ['setPaymentPendingOrderStatus'], ['updateCache'], ], - PayPalCaptureRefundedEvent::class => [ - ['setPaymentRefundedOrderStatus'], - ['updateCache'], - ], PayPalCaptureReversedEvent::class => [ ['setPaymentReversedOrderStatus'], ['updateCache'], @@ -216,30 +209,6 @@ public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR))); } - public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) - { - try { - /** @var GetOrderForPaymentRefundedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); - } catch (OrderNotFoundException $exception) { - return; - } - - if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { - return; - } - - $capture = $event->getCapture(); - // In case there no OrderSlip for this refund, we use the refund amount from payload - $totalRefunded = $order->getTotalRefund() ? $order->getTotalRefund() : $capture['amount']['value']; - - if ($this->checkOrderAmount->checkAmount($order->getTotalAmount(), $totalRefunded) === CheckOrderAmount::ORDER_NOT_FULL_PAID) { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED))); - } else { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED))); - } - } - public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) { try { diff --git a/src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php b/src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php new file mode 100644 index 000000000..8e2ec5452 --- /dev/null +++ b/src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php @@ -0,0 +1,109 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundException; +use Validate; + +class RefundPayPalCaptureCommand +{ + /** + * @var string + */ + private $orderPayPalId; + /** + * @var string + */ + private $captureId; + /** + * @var string + */ + private $currencyCode; + /** + * @var string + */ + private $amount; + + /** + * @param string $orderPayPalId + * @param string $captureId + * @param string $currencyCode + * @param mixed $amount + * + * @throws PayPalRefundException + */ + public function __construct($orderPayPalId, $captureId, $currencyCode, $amount) + { + if (empty($orderPayPalId) || !Validate::isGenericName($orderPayPalId)) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_ORDER_ID); + } + + if (empty($captureId) || !Validate::isGenericName($captureId)) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_TRANSACTION_ID); + } + + // https://developer.paypal.com0/docs/api/reference/currency-codes/ + if (empty($currencyCode) || !in_array($currencyCode, ['AUD', 'BRL', 'CAD', 'CZK', 'DKK', 'EUR', 'HKD', 'HUF', 'INR', 'ILS', 'JPY', 'MYR', 'MXN', 'TWD', 'NZD', 'NOK', 'PHP', 'PLN', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD'])) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_CURRENCY); + } + + if (empty($amount) || !Validate::isPrice($amount) || $amount <= 0) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_AMOUNT); + } + + $this->orderPayPalId = $orderPayPalId; + $this->captureId = $captureId; + $this->currencyCode = $currencyCode; + $this->amount = $amount; + } + + /** + * @return string + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + /** + * @return string + */ + public function getCaptureId() + { + return $this->captureId; + } + + /** + * @return string + */ + public function getCurrencyCode() + { + return $this->currencyCode; + } + + /** + * @return mixed + */ + public function getAmount() + { + return $this->amount; + } +} diff --git a/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php b/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php new file mode 100644 index 000000000..291984583 --- /dev/null +++ b/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php @@ -0,0 +1,123 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler; + +use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; +use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundFailedException; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; +use Psr\SimpleCache\CacheInterface; + +class RefundPayPalCaptureCommandHandler +{ + /** + * @var CheckoutHttpClient + */ + private $checkoutHttpClient; + /** + * @var PayPalConfiguration + */ + private $payPalConfiguration; + /** + * @var PrestaShopConfiguration + */ + private $prestaShopConfiguration; + /** + * @var PrestaShopContext + */ + private $prestaShopContext; + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + /** + * @var CacheInterface + */ + private $orderPayPalCache; + + public function __construct( + CheckoutHttpClient $checkoutHttpClient, + PayPalConfiguration $payPalConfiguration, + PrestaShopConfiguration $prestaShopConfiguration, + PrestaShopContext $prestaShopContext, + EventDispatcherInterface $eventDispatcher, + CacheInterface $orderPayPalCache + ) { + $this->checkoutHttpClient = $checkoutHttpClient; + $this->payPalConfiguration = $payPalConfiguration; + $this->prestaShopConfiguration = $prestaShopConfiguration; + $this->prestaShopContext = $prestaShopContext; + $this->eventDispatcher = $eventDispatcher; + $this->orderPayPalCache = $orderPayPalCache; + } + + /** + * @param RefundPayPalCaptureCommand $command + * + * @throws PayPalException + * @throws PayPalRefundFailedException + * @throws PayPalOrderException + */ + public function handle(RefundPayPalCaptureCommand $command) + { + $response = $this->checkoutHttpClient->refundOrder([ + 'orderId' => $command->getOrderPayPalId(), + 'captureId' => $command->getCaptureId(), + 'payee' => [ + 'merchant_id' => $this->payPalConfiguration->getMerchantId(), + ], + 'amount' => [ + 'currency_code' => $command->getCurrencyCode(), + 'value' => $command->getAmount(), + ], + 'note_to_payer' => 'Refund by ' + . $this->prestaShopConfiguration->get( + 'PS_SHOP_NAME', + ['id_shop' => $this->prestaShopContext->getShopId()] + ), + ]); + $responseHandler = new ResponseApiHandler(); + $response = $responseHandler->handleResponse($response); + + if (isset($response['httpCode']) && $response['httpCode'] === 200) { + if ($this->orderPayPalCache->has($command->getOrderPayPalId())) { + $this->orderPayPalCache->delete($command->getOrderPayPalId()); + } + } else { + throw new PayPalRefundFailedException('', isset($response['httpCode']) ? $response['httpCode'] : 500); + } + + $this->eventDispatcher->dispatch( + new PayPalCaptureRefundedEvent( + $response['body']['id'], + $command->getOrderPayPalId(), + $response['body'] + ) + ); + } +} diff --git a/src/PayPal/Payment/Capture/Event/PayPalCaptureRefundedEvent.php b/src/PayPal/Payment/Refund/Event/PayPalCaptureRefundedEvent.php similarity index 85% rename from src/PayPal/Payment/Capture/Event/PayPalCaptureRefundedEvent.php rename to src/PayPal/Payment/Refund/Event/PayPalCaptureRefundedEvent.php index a281444a8..c7e010544 100644 --- a/src/PayPal/Payment/Capture/Event/PayPalCaptureRefundedEvent.php +++ b/src/PayPal/Payment/Refund/Event/PayPalCaptureRefundedEvent.php @@ -18,8 +18,8 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event; +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event; -class PayPalCaptureRefundedEvent extends PayPalCaptureEvent +class PayPalCaptureRefundedEvent extends PayPalRefundEvent { } diff --git a/src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php b/src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php new file mode 100644 index 000000000..5f67c1954 --- /dev/null +++ b/src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php @@ -0,0 +1,82 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event; + +use PrestaShop\Module\PrestashopCheckout\Event\Event; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +abstract class PayPalRefundEvent extends Event +{ + /** + * @var string + */ + private $refundId; + + /** + * @var PayPalOrderId + */ + private $paypalOrderId; + + /** + * @var array{id: string, status: string, amount: array, create_time: string, update_time: string, custom_id: string, note_to_payer: string, payer: array, seller_payable_breakdown: array, links: array} + */ + private $refund; + + /** + * @param string $refundId + * @param string $paypalOrderId + * @param array{id: string, status: string, amount: array, create_time: string, update_time: string, custom_id: string, note_to_payer: string, payer: array, seller_payable_breakdown: array, links: array} $refund + * + * @throws PayPalOrderException + */ + public function __construct($refundId, $paypalOrderId, array $refund) + { + $this->refundId = $refundId; + $this->paypalOrderId = new PayPalOrderId($paypalOrderId); + $this->refund = $refund; + } + + /** + * @return string + */ + public function getPayPalRefundId() + { + return $this->refundId; + } + + /** + * @return PayPalOrderId + */ + public function getPayPalOrderId() + { + return $this->paypalOrderId; + } + + /** + * @return array{id: string, status: string, amount: array, create_time: string, update_time: string, custom_id: string, note_to_payer: string, payer: array, seller_payable_breakdown: array, links: array} + */ + public function getRefund() + { + return $this->refund; + } +} diff --git a/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php new file mode 100644 index 000000000..2c9fdeeec --- /dev/null +++ b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php @@ -0,0 +1,161 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber; + +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; +use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQueryResult; +use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalRefundEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; +use Ps_checkout; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class PayPalRefundEventSubscriber implements EventSubscriberInterface +{ + /** + * @var Ps_checkout + */ + private $module; + + /** + * @var CheckOrderAmount + */ + private $checkOrderAmount; + + /** + * @var CommandBusInterface + */ + private $commandBus; + + /** + * @var CacheInterface + */ + private $capturePayPalCache; + + /** + * @var CacheInterface + */ + private $orderPayPalCache; + + /** + * @var OrderStateMapper + */ + private $orderStateMapper; + /** + * @var PayPalOrderProvider + */ + private $orderProvider; + + public function __construct( + Ps_checkout $module, + CheckOrderAmount $checkOrderAmount, + CacheInterface $capturePayPalCache, + CacheInterface $orderPayPalCache, + OrderStateMapper $orderStateMapper, + PayPalOrderProvider $orderProvider + ) { + $this->module = $module; + $this->checkOrderAmount = $checkOrderAmount; + $this->commandBus = $this->module->getService('ps_checkout.bus.command'); + $this->capturePayPalCache = $capturePayPalCache; + $this->orderPayPalCache = $orderPayPalCache; + $this->orderStateMapper = $orderStateMapper; + $this->orderProvider = $orderProvider; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + PayPalCaptureRefundedEvent::class => [ + ['setPaymentRefundedOrderStatus'], + ['updateCache'], + ], + ]; + } + + public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) + { + try { + /** @var GetOrderForPaymentRefundedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } + + if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { + return; + } + + $orderPayPal = $this->orderProvider->getById($event->getPayPalOrderId()->getValue()); + + if (empty($orderPayPal['purchase_units'][0]['payments']['refunds'])) { + return; + } + + $totalRefunded = array_reduce($orderPayPal['purchase_units'][0]['payments']['refunds'], function ($totalRefunded, $refund) { + return $totalRefunded + (float) $refund['amount']['value']; + }); + + $orderFullyRefunded = (float) $order->getTotalAmount() <= (float) $totalRefunded; + + $this->commandBus->handle( + new UpdateOrderStatusCommand( + $order->getOrderId()->getValue(), + $this->orderStateMapper->getIdByKey($orderFullyRefunded ? OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED : OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED) + ) + ); + } + + public function updateCache(PayPalRefundEvent $event) + { + if ($this->orderPayPalCache->has($event->getPayPalOrderId()->getValue())) { + $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); + } +// $this->capturePayPalCache->set($event->getPayPalCaptureId()->getValue(), $event->getCapture()); +// +// $needToClearOrderPayPalCache = true; +// $orderPayPalCache = $this->orderPayPalCache->get($event->getPayPalOrderId()->getValue()); +// +// if ($orderPayPalCache && isset($orderPayPalCache['purchase_units'][0]['payments']['captures'])) { +// foreach ($orderPayPalCache['purchase_units'][0]['payments']['captures'] as $key => $capture) { +// if ($capture['id'] === $event->getPayPalCaptureId()->getValue()) { +// $needToClearOrderPayPalCache = false; +// $orderPayPalCache['purchase_units'][0]['payments']['captures'][$key] = $event->getCapture(); +// $this->orderPayPalCache->set($event->getPayPalOrderId()->getValue(), $orderPayPalCache); +// } +// } +// } +// +// if ($needToClearOrderPayPalCache) { +// $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); +// } + } +} diff --git a/src/PayPal/Payment/Refund/Exception/PayPalRefundException.php b/src/PayPal/Payment/Refund/Exception/PayPalRefundException.php new file mode 100644 index 000000000..ee2efdede --- /dev/null +++ b/src/PayPal/Payment/Refund/Exception/PayPalRefundException.php @@ -0,0 +1,32 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception; + +use Exception; + +class PayPalRefundException extends Exception +{ + const INVALID_ORDER_ID = 1; + const INVALID_TRANSACTION_ID = 2; + const INVALID_CURRENCY = 3; + const INVALID_AMOUNT = 4; + const INVALID_REFUND_ID = 4; +} diff --git a/src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php b/src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php new file mode 100644 index 000000000..c037a864f --- /dev/null +++ b/src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php @@ -0,0 +1,27 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception; + +use Exception; + +class PayPalRefundFailedException extends Exception +{ +} diff --git a/src/PayPalError.php b/src/PayPalError.php index 7d30d09c1..643346fa2 100644 --- a/src/PayPalError.php +++ b/src/PayPalError.php @@ -20,6 +20,7 @@ namespace PrestaShop\Module\PrestashopCheckout; +use Http\Client\Exception\HttpException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; class PayPalError @@ -38,275 +39,315 @@ public function __construct($message) } /** + * @param HttpException|null $previous + * * @throws PayPalException */ - public function throwException() + public function throwException(HttpException $previous = null) { switch ($this->message) { case 'ACTION_DOES_NOT_MATCH_INTENT': - throw new PayPalException('Order was created with an intent to CAPTURE, to complete the transaction, call capture payment for order or create an order with an intent of AUTHORIZE.', PayPalException::ACTION_DOES_NOT_MATCH_INTENT); + throw new PayPalException('Order was created with an intent to CAPTURE, to complete the transaction, call capture payment for order or create an order with an intent of AUTHORIZE.', PayPalException::ACTION_DOES_NOT_MATCH_INTENT, $previous); case 'AGREEMENT_ALREADY_CANCELLED': - throw new PayPalException('The requested agreement is already cancelled, the specified agreement ID cannot be used for this transaction.', PayPalException::AGREEMENT_ALREADY_CANCELLED); + throw new PayPalException('The requested agreement is already cancelled, the specified agreement ID cannot be used for this transaction.', PayPalException::AGREEMENT_ALREADY_CANCELLED, $previous); case 'AMOUNT_CANNOT_BE_SPECIFIED': - throw new PayPalException('An authorization amount can only be specified if an order was saved. Save the order and try again.', PayPalException::AMOUNT_CANNOT_BE_SPECIFIED); + throw new PayPalException('An authorization amount can only be specified if an order was saved. Save the order and try again.', PayPalException::AMOUNT_CANNOT_BE_SPECIFIED, $previous); case 'AMOUNT_MISMATCH': - throw new PayPalException('The amount specified does not match the breakdown : amount must equal item_total + tax_total + shipping + handling + insurance - shipping_discount - discount.', PayPalException::AMOUNT_MISMATCH); + throw new PayPalException('The amount specified does not match the breakdown : amount must equal item_total + tax_total + shipping + handling + insurance - shipping_discount - discount.', PayPalException::AMOUNT_MISMATCH, $previous); case 'AMOUNT_NOT_PATCHABLE': - throw new PayPalException('The amount cannot be updated as the payer has chosen and approved a specific financing offer for a given amount. Create an order with the updated order amount and have the payer approve the new payment terms.', PayPalException::AMOUNT_NOT_PATCHABLE); + throw new PayPalException('The amount cannot be updated as the payer has chosen and approved a specific financing offer for a given amount. Create an order with the updated order amount and have the payer approve the new payment terms.', PayPalException::AMOUNT_NOT_PATCHABLE, $previous); case 'AUTH_CAPTURE_NOT_ENABLED': - throw new PayPalException('The authorization and capture feature is not enabled for the merchant. Make sure that the recipient of the funds is a verified business account.', PayPalException::AUTH_CAPTURE_NOT_ENABLED); + throw new PayPalException('The authorization and capture feature is not enabled for the merchant. Make sure that the recipient of the funds is a verified business account.', PayPalException::AUTH_CAPTURE_NOT_ENABLED, $previous); case 'AUTHENTICATION_FAILURE': - throw new PayPalException('The account validations failed for the user.', PayPalException::AUTHENTICATION_FAILURE); + throw new PayPalException('The account validations failed for the user.', PayPalException::AUTHENTICATION_FAILURE, $previous); case 'AUTHORIZATION_AMOUNT_EXCEEDED': - throw new PayPalException('The currency of the authorization must match the currency of the order that the payer created and approved. Check the currency_code and try the request again.', PayPalException::AUTHORIZATION_AMOUNT_EXCEEDED); + throw new PayPalException('The currency of the authorization must match the currency of the order that the payer created and approved. Check the currency_code and try the request again.', PayPalException::AUTHORIZATION_AMOUNT_EXCEEDED, $previous); case 'BILLING_AGREEMENT_NOT_FOUND': - throw new PayPalException('The requested Billing Agreement token was not found. Verify the token and try the request again.', PayPalException::BILLING_AGREEMENT_NOT_FOUND); + throw new PayPalException('The requested Billing Agreement token was not found. Verify the token and try the request again.', PayPalException::BILLING_AGREEMENT_NOT_FOUND, $previous); case 'CANNOT_BE_NEGATIVE': - throw new PayPalException('Must be greater than or equal to zero. Try the request again with a different value.', PayPalException::CANNOT_BE_NEGATIVE); + throw new PayPalException('Must be greater than or equal to zero. Try the request again with a different value.', PayPalException::CANNOT_BE_NEGATIVE, $previous); case 'CANNOT_BE_ZERO_OR_NEGATIVE': - throw new PayPalException('Must be greater than zero. Try the request again with a different value.', PayPalException::CANNOT_BE_ZERO_OR_NEGATIVE); + throw new PayPalException('Must be greater than zero. Try the request again with a different value.', PayPalException::CANNOT_BE_ZERO_OR_NEGATIVE, $previous); case 'CARD_TYPE_NOT_SUPPORTED': - throw new PayPalException('Processing of this card type is not supported. Use another card type.', PayPalException::CARD_TYPE_NOT_SUPPORTED); + throw new PayPalException('Processing of this card type is not supported. Use another card type.', PayPalException::CARD_TYPE_NOT_SUPPORTED, $previous); case 'INVALID_SECURITY_CODE_LENGTH': - throw new PayPalException('The security_code length is invalid for the specified card type.', PayPalException::INVALID_SECURITY_CODE_LENGTH); + throw new PayPalException('The security_code length is invalid for the specified card type.', PayPalException::INVALID_SECURITY_CODE_LENGTH, $previous); case 'CITY_REQUIRED': - throw new PayPalException('The specified country requires a city (address.admin_area_2). Specify a city and try the request again.', PayPalException::CITY_REQUIRED); + throw new PayPalException('The specified country requires a city (address.admin_area_2). Specify a city and try the request again.', PayPalException::CITY_REQUIRED, $previous); case 'COMPLIANCE_VIOLATION': - throw new PayPalException('Transaction cannot be processed due to a possible compliance violation. To get more information about the transaction, call Customer Support.', PayPalException::COMPLIANCE_VIOLATION); + throw new PayPalException('Transaction cannot be processed due to a possible compliance violation. To get more information about the transaction, call Customer Support.', PayPalException::COMPLIANCE_VIOLATION, $previous); case 'CONSENT_NEEDED': - throw new PayPalException('Authorization failed due to insufficient permissions. To continue with this transaction, the payer must provide consent.', PayPalException::CONSENT_NEEDED); + throw new PayPalException('Authorization failed due to insufficient permissions. To continue with this transaction, the payer must provide consent.', PayPalException::CONSENT_NEEDED, $previous); case 'CURRENCY_NOT_SUPPORTED_FOR_COUNTRY': - throw new PayPalException('Currency code not supported for direct card payments in this country.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_COUNTRY); + throw new PayPalException('Currency code not supported for direct card payments in this country.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_COUNTRY, $previous); case 'CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE': - throw new PayPalException('The currency code is not supported for direct card payments for this card type.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE); + throw new PayPalException('The currency code is not supported for direct card payments for this card type.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE, $previous); case 'DECIMAL_PRECISION': - throw new PayPalException('The value of the field should not be more than two decimal places. Verify the number of decimal places and try the request again.', PayPalException::DECIMAL_PRECISION); + throw new PayPalException('The value of the field should not be more than two decimal places. Verify the number of decimal places and try the request again.', PayPalException::DECIMAL_PRECISION, $previous); case 'DOMESTIC_TRANSACTION_REQUIRED': - throw new PayPalException('This transaction requires the payee and payer to be resident in the same country. To create this payment, a domestic transaction is required.', PayPalException::DOMESTIC_TRANSACTION_REQUIRED); + throw new PayPalException('This transaction requires the payee and payer to be resident in the same country. To create this payment, a domestic transaction is required.', PayPalException::DOMESTIC_TRANSACTION_REQUIRED, $previous); case 'DUPLICATE_INVOICE_ID': - throw new PayPalException('Duplicate Invoice ID detected. To avoid a duplicate transaction, verify that the invoice ID is unique for each transaction.', PayPalException::DUPLICATE_INVOICE_ID); + throw new PayPalException('Duplicate Invoice ID detected. To avoid a duplicate transaction, verify that the invoice ID is unique for each transaction.', PayPalException::DUPLICATE_INVOICE_ID, $previous); case 'DUPLICATE_REQUEST_ID': - throw new PayPalException('The value of PayPal-Request-Id header has already been used. Specify a different value and try the request again.', PayPalException::DUPLICATE_REQUEST_ID); + throw new PayPalException('The value of PayPal-Request-Id header has already been used. Specify a different value and try the request again.', PayPalException::DUPLICATE_REQUEST_ID, $previous); case 'FIELD_NOT_PATCHABLE': - throw new PayPalException('Field cannot be patched. You cannot update this field.', PayPalException::FIELD_NOT_PATCHABLE); + throw new PayPalException('Field cannot be patched. You cannot update this field.', PayPalException::FIELD_NOT_PATCHABLE, $previous); case 'INSTRUMENT_DECLINED': - throw new PayPalException('The funding instrument presented was either declined by the processor or bank. The specified funding instrument cannot be used for this payment.', PayPalException::INSTRUMENT_DECLINED); + throw new PayPalException('The funding instrument presented was either declined by the processor or bank. The specified funding instrument cannot be used for this payment.', PayPalException::INSTRUMENT_DECLINED, $previous); case 'INTERNAL_SERVER_ERROR': - throw new PayPalException('An internal server error has occurred. Retry the request later.', PayPalException::INTERNAL_SERVER_ERROR); + throw new PayPalException('An internal server error has occurred. Retry the request later.', PayPalException::INTERNAL_SERVER_ERROR, $previous); case 'INTERNAL_SERVICE_ERROR': - throw new PayPalException('An internal service error has occurred.', PayPalException::INTERNAL_SERVICE_ERROR); + throw new PayPalException('An internal service error has occurred.', PayPalException::INTERNAL_SERVICE_ERROR, $previous); case 'INVALID_ACCOUNT_STATUS': - throw new PayPalException('Account validations failed for the user. To continue with this transaction, the payer must provide consent.', PayPalException::INVALID_ACCOUNT_STATUS); + throw new PayPalException('Account validations failed for the user. To continue with this transaction, the payer must provide consent.', PayPalException::INVALID_ACCOUNT_STATUS, $previous); case 'INVALID_ARRAY_MAX_ITEMS': - throw new PayPalException('The number of items in an array parameter is too large.', PayPalException::INVALID_ARRAY_MAX_ITEMS); + throw new PayPalException('The number of items in an array parameter is too large.', PayPalException::INVALID_ARRAY_MAX_ITEMS, $previous); case 'INVALID_ARRAY_MIN_ITEMS': - throw new PayPalException('The number of items in an array parameter is too small.', PayPalException::INVALID_ARRAY_MIN_ITEMS); + throw new PayPalException('The number of items in an array parameter is too small.', PayPalException::INVALID_ARRAY_MIN_ITEMS, $previous); case 'INVALID_COUNTRY_CODE': - throw new PayPalException('Country code is invalid.', PayPalException::INVALID_COUNTRY_CODE); + throw new PayPalException('Country code is invalid.', PayPalException::INVALID_COUNTRY_CODE, $previous); case 'INVALID_CURRENCY_CODE': - throw new PayPalException('Currency code is invalid or is not currently supported.', PayPalException::INVALID_CURRENCY_CODE); + throw new PayPalException('Currency code is invalid or is not currently supported.', PayPalException::INVALID_CURRENCY_CODE, $previous); case 'INVALID_JSON_POINTER_FORMAT': - throw new PayPalException('Path should be a valid JavaScript Object Notation (JSON) Pointer that references a location within the request where the operation is performed. The path is not valid.', PayPalException::INVALID_JSON_POINTER_FORMAT); + throw new PayPalException('Path should be a valid JavaScript Object Notation (JSON) Pointer that references a location within the request where the operation is performed. The path is not valid.', PayPalException::INVALID_JSON_POINTER_FORMAT, $previous); case 'INVALID_PARAMETER_SYNTAX': - throw new PayPalException('The value of a field does not conform to the expected format. Verify that the pattern is supported and try the request again.', PayPalException::INVALID_PARAMETER_SYNTAX); + throw new PayPalException('The value of a field does not conform to the expected format. Verify that the pattern is supported and try the request again.', PayPalException::INVALID_PARAMETER_SYNTAX, $previous); case 'INVALID_PARAMETER_VALUE': - throw new PayPalException('The value of a field is invalid. Verify the parameter value and try the request again.', PayPalException::INVALID_PARAMETER_VALUE); + throw new PayPalException('The value of a field is invalid. Verify the parameter value and try the request again.', PayPalException::INVALID_PARAMETER_VALUE, $previous); case 'INVALID_PARAMETER': - throw new PayPalException('Cannot be specified as part of the request. Check that the API supports this parameter and try the request again.', PayPalException::INVALID_PARAMETER); + throw new PayPalException('Cannot be specified as part of the request. Check that the API supports this parameter and try the request again.', PayPalException::INVALID_PARAMETER, $previous); case 'INVALID_PATCH_OPERATION': - throw new PayPalException('Request is not well-formed, syntactically incorrect, or violates schema. The operation cannot be honored. You cannot add a property that is already present. Instead, use replace. You cannot remove a property that is not present. Instead, use add. You cannot replace a property that is not present. Instead, use add.', PayPalException::INVALID_PATCH_OPERATION); + throw new PayPalException('Request is not well-formed, syntactically incorrect, or violates schema. The operation cannot be honored. You cannot add a property that is already present. Instead, use replace. You cannot remove a property that is not present. Instead, use add. You cannot replace a property that is not present. Instead, use add.', PayPalException::INVALID_PATCH_OPERATION, $previous); case 'INVALID_PAYER_ID': - throw new PayPalException('The payer ID is not valid. Verify the payer ID and try the request again.', PayPalException::INVALID_PAYER_ID); + throw new PayPalException('The payer ID is not valid. Verify the payer ID and try the request again.', PayPalException::INVALID_PAYER_ID, $previous); case 'INVALID_RESOURCE_ID': - throw new PayPalException('Specified resource ID does not exist. Verify the resource ID and try the request again.', PayPalException::INVALID_RESOURCE_ID); + throw new PayPalException('Specified resource ID does not exist. Verify the resource ID and try the request again.', PayPalException::INVALID_RESOURCE_ID, $previous); case 'INVALID_STRING_LENGTH': - throw new PayPalException('The value of a field is either too short or too long. Verify the minimum and maximum values and try the request again.', PayPalException::INVALID_STRING_LENGTH); + throw new PayPalException('The value of a field is either too short or too long. Verify the minimum and maximum values and try the request again.', PayPalException::INVALID_STRING_LENGTH, $previous); case 'ITEM_TOTAL_MISMATCH': - throw new PayPalException('Verify the corresponding values and try the request again. The item total should equal the sum of (unit_amount * quantity) across all items for a purchase_unit.', PayPalException::ITEM_TOTAL_MISMATCH); + throw new PayPalException('Verify the corresponding values and try the request again. The item total should equal the sum of (unit_amount * quantity) across all items for a purchase_unit.', PayPalException::ITEM_TOTAL_MISMATCH, $previous); case 'ITEM_TOTAL_REQUIRED': - throw new PayPalException('If item details are specified (items.unit_amount and items.quantity) corresponding amount.breakdown.item_total is required. The amount.breakdown.item_total value was not found.', PayPalException::ITEM_TOTAL_REQUIRED); + throw new PayPalException('If item details are specified (items.unit_amount and items.quantity) corresponding amount.breakdown.item_total is required. The amount.breakdown.item_total value was not found.', PayPalException::ITEM_TOTAL_REQUIRED, $previous); case 'MAX_AUTHORIZATION_COUNT_EXCEEDED': - throw new PayPalException('The maximum number of authorizations that are allowed for the order was reached. To increase your limit, contact Customer Support.', PayPalException::MAX_AUTHORIZATION_COUNT_EXCEEDED); + throw new PayPalException('The maximum number of authorizations that are allowed for the order was reached. To increase your limit, contact Customer Support.', PayPalException::MAX_AUTHORIZATION_COUNT_EXCEEDED, $previous); case 'MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED': - throw new PayPalException('You have exceeded the maximum number of payment attempts. To review the maximum number of payment attempts allowed and retry this transaction, call Customer Support.', PayPalException::MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED); + throw new PayPalException('You have exceeded the maximum number of payment attempts. To review the maximum number of payment attempts allowed and retry this transaction, call Customer Support.', PayPalException::MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED, $previous); case 'MAX_VALUE_EXCEEDED': - throw new PayPalException('Should be less than or equal to 9999999.99 ; try the request again with a different value.', PayPalException::MAX_VALUE_EXCEEDED); + throw new PayPalException('Should be less than or equal to 9999999.99 ; try the request again with a different value.', PayPalException::MAX_VALUE_EXCEEDED, $previous); case 'MISSING_REQUIRED_PARAMETER': - throw new PayPalException('A required field or parameter is missing. Verify that you have specified all required parameters and try the request again.', PayPalException::MISSING_REQUIRED_PARAMETER); + throw new PayPalException('A required field or parameter is missing. Verify that you have specified all required parameters and try the request again.', PayPalException::MISSING_REQUIRED_PARAMETER, $previous); case 'MISSING_SHIPPING_ADDRESS': - throw new PayPalException('The shipping address is required when shipping_preference=SET_PROVIDED_ADDRESS. Verify that you have provided the shipping address and try the request again.', PayPalException::MISSING_SHIPPING_ADDRESS); + throw new PayPalException('The shipping address is required when shipping_preference=SET_PROVIDED_ADDRESS. Verify that you have provided the shipping address and try the request again.', PayPalException::MISSING_SHIPPING_ADDRESS, $previous); case 'MULTI_CURRENCY_ORDER': - throw new PayPalException('Multiple differing values of currency_code are not supported. The entire order request must have the same currency code.', PayPalException::MULTI_CURRENCY_ORDER); + throw new PayPalException('Multiple differing values of currency_code are not supported. The entire order request must have the same currency code.', PayPalException::MULTI_CURRENCY_ORDER, $previous); case 'MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED': - throw new PayPalException('Multiple shipping addresses are not supported. Try the request again with the same shipping_address.', PayPalException::MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED); + throw new PayPalException('Multiple shipping addresses are not supported. Try the request again with the same shipping_address.', PayPalException::MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED, $previous); case 'MULTIPLE_SHIPPING_OPTION_SELECTED': - throw new PayPalException('Only one shipping.option can be set to selected = true.', PayPalException::MULTIPLE_SHIPPING_OPTION_SELECTED); + throw new PayPalException('Only one shipping.option can be set to selected = true.', PayPalException::MULTIPLE_SHIPPING_OPTION_SELECTED, $previous); case 'INVALID_PICKUP_ADDRESS': - throw new PayPalException('Invalid shipping address. If the \'shipping_option.type\' is set as \'PICKUP\' then the \'shipping_detail.name.full_name\' should start with \'S2S\' meaning Ship To Store. Example: \'S2S My Store\'.', PayPalException::INVALID_PICKUP_ADDRESS); + throw new PayPalException('Invalid shipping address. If the \'shipping_option.type\' is set as \'PICKUP\' then the \'shipping_detail.name.full_name\' should start with \'S2S\' meaning Ship To Store. Example: \'S2S My Store\'.', PayPalException::INVALID_PICKUP_ADDRESS, $previous); case 'NOT_AUTHORIZED': - throw new PayPalException('Authorization failed due to insufficient permissions. To check that your application has sufficient permissions, log in to the PayPal Developer Portal.', PayPalException::NOT_AUTHORIZED); + throw new PayPalException('Authorization failed due to insufficient permissions. To check that your application has sufficient permissions, log in to the PayPal Developer Portal.', PayPalException::NOT_AUTHORIZED, $previous); case 'NOT_ENABLED_FOR_CARD_PROCESSING': - throw new PayPalException('The request fails. The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING); + throw new PayPalException('The request fails. The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING, $previous); case 'NOT_PATCHABLE': - throw new PayPalException('Cannot be patched. You cannot update this field.', PayPalException::NOT_PATCHABLE); + throw new PayPalException('Cannot be patched. You cannot update this field.', PayPalException::NOT_PATCHABLE, $previous); case 'NOT_SUPPORTED': - throw new PayPalException('This field is not currently supported. Specify only supported parameters and try the request again.', PayPalException::NOT_SUPPORTED); + throw new PayPalException('This field is not currently supported. Specify only supported parameters and try the request again.', PayPalException::NOT_SUPPORTED, $previous); case 'ORDER_ALREADY_AUTHORIZED': - throw new PayPalException('Order already authorized. If intent=AUTHORIZE only one authorization per order is allowed. The order was already authorized and you can create only one authorization for an order.', PayPalException::ORDER_ALREADY_AUTHORIZED); + throw new PayPalException('Order already authorized. If intent=AUTHORIZE only one authorization per order is allowed. The order was already authorized and you can create only one authorization for an order.', PayPalException::ORDER_ALREADY_AUTHORIZED, $previous); case 'ORDER_ALREADY_CAPTURED': - throw new PayPalException('Order already captured. If intent=CAPTURE only one capture per order is allowed. The order was already captured and you can capture only one payment for an order.', PayPalException::ORDER_ALREADY_CAPTURED); + throw new PayPalException('Order already captured. If intent=CAPTURE only one capture per order is allowed. The order was already captured and you can capture only one payment for an order.', PayPalException::ORDER_ALREADY_CAPTURED, $previous); case 'ORDER_ALREADY_COMPLETED': - throw new PayPalException('The order cannot be patched after it is completed.', PayPalException::ORDER_ALREADY_COMPLETED); + throw new PayPalException('The order cannot be patched after it is completed.', PayPalException::ORDER_ALREADY_COMPLETED, $previous); case 'ORDER_CANNOT_BE_SAVED': - throw new PayPalException('The option to save an order is only available if the intent is AUTHORIZE and the processing_instruction is ORDER_SAVED_EXPLICITLY. Change the intent to AUTHORIZE and the processing_instruction to ORDER_SAVED_EXPLICITLY and try the request again.', PayPalException::ORDER_CANNOT_BE_SAVED); + throw new PayPalException('The option to save an order is only available if the intent is AUTHORIZE and the processing_instruction is ORDER_SAVED_EXPLICITLY. Change the intent to AUTHORIZE and the processing_instruction to ORDER_SAVED_EXPLICITLY and try the request again.', PayPalException::ORDER_CANNOT_BE_SAVED, $previous); case 'ORDER_COMPLETED_OR_VOIDED': - throw new PayPalException('Order is voided or completed and hence cannot be authorized.', PayPalException::ORDER_COMPLETED_OR_VOIDED); + throw new PayPalException('Order is voided or completed and hence cannot be authorized.', PayPalException::ORDER_COMPLETED_OR_VOIDED, $previous); case 'ORDER_EXPIRED': - throw new PayPalException('Order is expired and hence cannot be authorized. Please contact Customer Support if you need to increase your order validity period.', PayPalException::ORDER_EXPIRED); + throw new PayPalException('Order is expired and hence cannot be authorized. Please contact Customer Support if you need to increase your order validity period.', PayPalException::ORDER_EXPIRED, $previous); case 'ORDER_NOT_APPROVED': - throw new PayPalException('Payer has not yet approved the Order for payment. The payer has not yet approved payment for the order. Redirect the payer to the rel:approve URL that was returned in the HATEOAS links in the create order response or provide a valid payment_source in the request.', PayPalException::ORDER_NOT_APPROVED); + throw new PayPalException('Payer has not yet approved the Order for payment. The payer has not yet approved payment for the order. Redirect the payer to the rel:approve URL that was returned in the HATEOAS links in the create order response or provide a valid payment_source in the request.', PayPalException::ORDER_NOT_APPROVED, $previous); case 'ORDER_NOT_SAVED': - throw new PayPalException('Please save the order or alternately, If you do not intend to save the order, PATCH the order to update the value of processing_instruction to NO_INSTRUCTION.', PayPalException::ORDER_NOT_SAVED); + throw new PayPalException('Please save the order or alternately, If you do not intend to save the order, PATCH the order to update the value of processing_instruction to NO_INSTRUCTION.', PayPalException::ORDER_NOT_SAVED, $previous); case 'ORDER_PREVIOUSLY_VOIDED': - throw new PayPalException('This order has been previously voided and cannot be voided again. Verify the order id and try again.', PayPalException::ORDER_PREVIOUSLY_VOIDED); + throw new PayPalException('This order has been previously voided and cannot be voided again. Verify the order id and try again.', PayPalException::ORDER_PREVIOUSLY_VOIDED, $previous); case 'PARAMETER_VALUE_NOT_SUPPORTED': - throw new PayPalException('The value specified for this field is not currently supported. The specified parameter value is not supported.', PayPalException::PARAMETER_VALUE_NOT_SUPPORTED); + throw new PayPalException('The value specified for this field is not currently supported. The specified parameter value is not supported.', PayPalException::PARAMETER_VALUE_NOT_SUPPORTED, $previous); case 'PATCH_PATH_REQUIRED': - throw new PayPalException('Specify a path for the field for which the operation needs to be performed. To complete the operation for this field, specify a path for the field.', PayPalException::PATCH_PATH_REQUIRED); + throw new PayPalException('Specify a path for the field for which the operation needs to be performed. To complete the operation for this field, specify a path for the field.', PayPalException::PATCH_PATH_REQUIRED, $previous); case 'PATCH_VALUE_REQUIRED': - throw new PayPalException('Please specify a value to for the field that is being patched.', PayPalException::PATCH_VALUE_REQUIRED); + throw new PayPalException('Please specify a value to for the field that is being patched.', PayPalException::PATCH_VALUE_REQUIRED, $previous); case 'PAYEE_ACCOUNT_INVALID': - throw new PayPalException('Payee account specified is invalid. Please check the payee.email_address or payee.merchant_id specified and try again. Ensure that either payee.merchant_id or payee.email_address is specified. Specify either payee.merchant_id or payee.email_address.', PayPalException::PAYEE_ACCOUNT_INVALID); + throw new PayPalException('Payee account specified is invalid. Please check the payee.email_address or payee.merchant_id specified and try again. Ensure that either payee.merchant_id or payee.email_address is specified. Specify either payee.merchant_id or payee.email_address.', PayPalException::PAYEE_ACCOUNT_INVALID, $previous); case 'PAYEE_ACCOUNT_LOCKED_OR_CLOSED': - throw new PayPalException('Payee account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_LOCKED_OR_CLOSED); + throw new PayPalException('Payee account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_LOCKED_OR_CLOSED, $previous); case 'PAYEE_ACCOUNT_RESTRICTED': - throw new PayPalException('The merchant account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_RESTRICTED); + throw new PayPalException('The merchant account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_RESTRICTED, $previous); case 'PAYEE_BLOCKED_TRANSACTION': - throw new PayPalException('The fraud settings for this seller are such that this payment cannot be executed. Verify the fraud settings. Then, retry the transaction.', PayPalException::PAYEE_BLOCKED_TRANSACTION); + throw new PayPalException('The fraud settings for this seller are such that this payment cannot be executed. Verify the fraud settings. Then, retry the transaction.', PayPalException::PAYEE_BLOCKED_TRANSACTION, $previous); case 'PAYER_ACCOUNT_LOCKED_OR_CLOSED': - throw new PayPalException('Payer account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_LOCKED_OR_CLOSED); + throw new PayPalException('Payer account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_LOCKED_OR_CLOSED, $previous); case 'PAYER_ACCOUNT_RESTRICTED': - throw new PayPalException('Payer account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_RESTRICTED); + throw new PayPalException('Payer account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_RESTRICTED, $previous); case 'PAYER_CANNOT_PAY': - throw new PayPalException('Payer cannot pay for this transaction. Please contact the payer to find other ways to pay for this transaction.', PayPalException::PAYER_CANNOT_PAY); + throw new PayPalException('Payer cannot pay for this transaction. Please contact the payer to find other ways to pay for this transaction.', PayPalException::PAYER_CANNOT_PAY, $previous); case 'PAYER_CONSENT_REQUIRED': - throw new PayPalException('The payer has not provided appropriate consent to proceed with this transaction. To proceed with the transaction, you must get payer consent.', PayPalException::PAYER_CONSENT_REQUIRED); + throw new PayPalException('The payer has not provided appropriate consent to proceed with this transaction. To proceed with the transaction, you must get payer consent.', PayPalException::PAYER_CONSENT_REQUIRED, $previous); case 'PAYER_COUNTRY_NOT_SUPPORTED': - throw new PayPalException('Payer Country is not supported. The Payer country is not supported. Redirect the payer to select another funding source.', PayPalException::PAYER_COUNTRY_NOT_SUPPORTED); + throw new PayPalException('Payer Country is not supported. The Payer country is not supported. Redirect the payer to select another funding source.', PayPalException::PAYER_COUNTRY_NOT_SUPPORTED, $previous); case 'PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING': - throw new PayPalException('The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING); + throw new PayPalException('The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING, $previous); case 'PAYMENT_INSTRUCTION_REQUIRED': - throw new PayPalException('You must provide the payment instruction when you capture an authorized payment for intent=AUTHORIZE. For details, see Capture authorization. For intent=CAPTURE, send the payment instruction when you create the order.', PayPalException::PAYMENT_INSTRUCTION_REQUIRED); + throw new PayPalException('You must provide the payment instruction when you capture an authorized payment for intent=AUTHORIZE. For details, see Capture authorization. For intent=CAPTURE, send the payment instruction when you create the order.', PayPalException::PAYMENT_INSTRUCTION_REQUIRED, $previous); case 'PERMISSION_DENIED': - throw new PayPalException('You do not have permission to access or perform operations on this resource. If you make API calls on behalf of a merchant or payee, ensure that you have been granted appropriate permissions to continue with this request.', PayPalException::PERMISSION_DENIED); + throw new PayPalException('You do not have permission to access or perform operations on this resource. If you make API calls on behalf of a merchant or payee, ensure that you have been granted appropriate permissions to continue with this request.', PayPalException::PERMISSION_DENIED, $previous); case 'POSTAL_CODE_REQUIRED': - throw new PayPalException('The specified country requires a postal code. Specify a postal code and try the request again.', PayPalException::POSTAL_CODE_REQUIRED); + throw new PayPalException('The specified country requires a postal code. Specify a postal code and try the request again.', PayPalException::POSTAL_CODE_REQUIRED, $previous); case 'PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH': - throw new PayPalException('The amount provided in the preferred shipping option should match the amount provided in amount breakdown.', PayPalException::PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH); + throw new PayPalException('The amount provided in the preferred shipping option should match the amount provided in amount breakdown.', PayPalException::PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH, $previous); case 'REDIRECT_PAYER_FOR_ALTERNATE_FUNDING': - throw new PayPalException('Transaction failed. Redirect the payer to select another funding source.', PayPalException::REDIRECT_PAYER_FOR_ALTERNATE_FUNDING); + throw new PayPalException('Transaction failed. Redirect the payer to select another funding source.', PayPalException::REDIRECT_PAYER_FOR_ALTERNATE_FUNDING, $previous); case 'REFERENCE_ID_NOT_FOUND': - throw new PayPalException('Filter expression value is incorrect. Check the value of the reference_id and try the request again.', PayPalException::REFERENCE_ID_NOT_FOUND); + throw new PayPalException('Filter expression value is incorrect. Check the value of the reference_id and try the request again.', PayPalException::REFERENCE_ID_NOT_FOUND, $previous); case 'REFERENCE_ID_REQUIRED': - throw new PayPalException('\'reference_id\' is required for each \'purchase_unit\' if multiple \'purchase_unit\' are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::REFERENCE_ID_REQUIRED); + throw new PayPalException('\'reference_id\' is required for each \'purchase_unit\' if multiple \'purchase_unit\' are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::REFERENCE_ID_REQUIRED, $previous); case 'DUPLICATE_REFERENCE_ID': - throw new PayPalException('reference_id must be unique if multiple purchase_unit are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::DUPLICATE_REFERENCE_ID); + throw new PayPalException('reference_id must be unique if multiple purchase_unit are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::DUPLICATE_REFERENCE_ID, $previous); case 'SHIPPING_ADDRESS_INVALID': - throw new PayPalException('Provided shipping address is invalid.', PayPalException::SHIPPING_ADDRESS_INVALID); + throw new PayPalException('Provided shipping address is invalid.', PayPalException::SHIPPING_ADDRESS_INVALID, $previous); case 'SHIPPING_OPTION_NOT_SELECTED': - throw new PayPalException('At least one of the shipping.option values must be selected = true.', PayPalException::SHIPPING_OPTION_NOT_SELECTED); + throw new PayPalException('At least one of the shipping.option values must be selected = true.', PayPalException::SHIPPING_OPTION_NOT_SELECTED, $previous); case 'SHIPPING_OPTIONS_NOT_SUPPORTED': - throw new PayPalException('Shipping options are not supported when application_context.shipping_preference is set as NO_SHIPPING or SET_PROVIDED_ADDRESS.', PayPalException::SHIPPING_OPTIONS_NOT_SUPPORTED); + throw new PayPalException('Shipping options are not supported when application_context.shipping_preference is set as NO_SHIPPING or SET_PROVIDED_ADDRESS.', PayPalException::SHIPPING_OPTIONS_NOT_SUPPORTED, $previous); case 'TAX_TOTAL_MISMATCH': - throw new PayPalException('Should equal sum of (tax * quantity) across all items for a given purchase_unit. The tax total must equal the sum of (tax * quantity) across all items for a purchase_unit.', PayPalException::TAX_TOTAL_MISMATCH); + throw new PayPalException('Should equal sum of (tax * quantity) across all items for a given purchase_unit. The tax total must equal the sum of (tax * quantity) across all items for a purchase_unit.', PayPalException::TAX_TOTAL_MISMATCH, $previous); case 'TAX_TOTAL_REQUIRED': - throw new PayPalException('If item details are specified (items.tax_total and items.quantity), the corresponding amount.breakdown.tax_total is required. The amount.breakdown.tax_total is a required field.', PayPalException::TAX_TOTAL_REQUIRED); + throw new PayPalException('If item details are specified (items.tax_total and items.quantity), the corresponding amount.breakdown.tax_total is required. The amount.breakdown.tax_total is a required field.', PayPalException::TAX_TOTAL_REQUIRED, $previous); case 'TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT': - throw new PayPalException('The transaction amount exceeds monthly maximum limit. To review the monthly transaction limits and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT); + throw new PayPalException('The transaction amount exceeds monthly maximum limit. To review the monthly transaction limits and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT, $previous); case 'TRANSACTION_BLOCKED_BY_PAYEE': - throw new PayPalException('The transaction was blocked by the payee’s Fraud Protection settings.', PayPalException::TRANSACTION_BLOCKED_BY_PAYEE); + throw new PayPalException('The transaction was blocked by the payee’s Fraud Protection settings.', PayPalException::TRANSACTION_BLOCKED_BY_PAYEE, $previous); case 'TRANSACTION_LIMIT_EXCEEDED': - throw new PayPalException('Total payment amount exceeded transaction limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_LIMIT_EXCEEDED); + throw new PayPalException('Total payment amount exceeded transaction limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_LIMIT_EXCEEDED, $previous); case 'TRANSACTION_RECEIVING_LIMIT_EXCEEDED': - throw new PayPalException('The transaction exceeds the payee\'s receiving limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_RECEIVING_LIMIT_EXCEEDED); + throw new PayPalException('The transaction exceeds the payee\'s receiving limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_RECEIVING_LIMIT_EXCEEDED, $previous); case 'TRANSACTION_REFUSED': - throw new PayPalException('The transaction was refused. Verify the transaction and try the request again.', PayPalException::TRANSACTION_REFUSED); + throw new PayPalException('The transaction was refused. Verify the transaction and try the request again.', PayPalException::TRANSACTION_REFUSED, $previous); case 'UNSUPPORTED_INTENT': - throw new PayPalException('intent=AUTHORIZE is not supported for multiple purchase units. Only intent=CAPTURE is supported.', PayPalException::UNSUPPORTED_INTENT); + throw new PayPalException('intent=AUTHORIZE is not supported for multiple purchase units. Only intent=CAPTURE is supported.', PayPalException::UNSUPPORTED_INTENT, $previous); case 'UNSUPPORTED_PATCH_PARAMETER_VALUE': - throw new PayPalException('The value specified for this field is not currently supported. Try the request again with a different value.', PayPalException::UNSUPPORTED_PATCH_PARAMETER_VALUE); + throw new PayPalException('The value specified for this field is not currently supported. Try the request again with a different value.', PayPalException::UNSUPPORTED_PATCH_PARAMETER_VALUE, $previous); case 'UNSUPPORTED_PAYMENT_INSTRUCTION': - throw new PayPalException('Only supported when the intent=CAPTURE. If intent is AUTHORIZE, you must provide a payment_instruction when you capture payment for the authorization.', PayPalException::UNSUPPORTED_PAYMENT_INSTRUCTION); + throw new PayPalException('Only supported when the intent=CAPTURE. If intent is AUTHORIZE, you must provide a payment_instruction when you capture payment for the authorization.', PayPalException::UNSUPPORTED_PAYMENT_INSTRUCTION, $previous); case 'PAYEE_ACCOUNT_NOT_SUPPORTED': - throw new PayPalException('Payee does not have an account with PayPal. Your current setup requires the \'payee\' to have a verified account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_SUPPORTED); + throw new PayPalException('Payee does not have an account with PayPal. Your current setup requires the \'payee\' to have a verified account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_SUPPORTED, $previous); case 'PAYEE_ACCOUNT_NOT_VERIFIED': - throw new PayPalException('Payee has not verified their account with PayPal. Your current setup requires the \'payee\' to have an account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_VERIFIED); + throw new PayPalException('Payee has not verified their account with PayPal. Your current setup requires the \'payee\' to have an account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_VERIFIED, $previous); case 'PAYEE_NOT_CONSENTED': - throw new PayPalException('Payee does not have appropriate consent to allow the API caller to process this type of transaction on their behalf. Your current setup requires the \'payee\' to provide a consent before this transaction can be processed successfully.', PayPalException::PAYEE_NOT_CONSENTED); + throw new PayPalException('Payee does not have appropriate consent to allow the API caller to process this type of transaction on their behalf. Your current setup requires the \'payee\' to provide a consent before this transaction can be processed successfully.', PayPalException::PAYEE_NOT_CONSENTED, $previous); case 'AUTH_CAPTURE_CURRENCY_MISMATCH': - throw new PayPalException('Currency of capture must be the same as currency of authorization. Verify the currency of the capture and try the request again.', PayPalException::AUTH_CAPTURE_CURRENCY_MISMATCH); + throw new PayPalException('Currency of capture must be the same as currency of authorization. Verify the currency of the capture and try the request again.', PayPalException::AUTH_CAPTURE_CURRENCY_MISMATCH, $previous); case 'AUTHORIZATION_ALREADY_CAPTURED': - throw new PayPalException('Authorization has already been captured. If final_capture is set to to true, additional captures are not possible against the authorization.', PayPalException::AUTHORIZATION_ALREADY_CAPTURED); + throw new PayPalException('Authorization has already been captured. If final_capture is set to to true, additional captures are not possible against the authorization.', PayPalException::AUTHORIZATION_ALREADY_CAPTURED, $previous); case 'AUTHORIZATION_DENIED': - throw new PayPalException('A denied authorization cannot be captured. You cannot capture a denied authorization.', PayPalException::AUTHORIZATION_DENIED); + throw new PayPalException('A denied authorization cannot be captured. You cannot capture a denied authorization.', PayPalException::AUTHORIZATION_DENIED, $previous); case 'AUTHORIZATION_EXPIRED': - throw new PayPalException('An expired authorization cannot be captured. You cannot capture an expired authorization.', PayPalException::AUTHORIZATION_EXPIRED); + throw new PayPalException('An expired authorization cannot be captured. You cannot capture an expired authorization.', PayPalException::AUTHORIZATION_EXPIRED, $previous); case 'AUTHORIZATION_VOIDED': - throw new PayPalException('A voided authorization cannot be captured or reauthorized. You cannot capture or reauthorize a voided authorization.', PayPalException::AUTHORIZATION_VOIDED); + throw new PayPalException('A voided authorization cannot be captured or reauthorized. You cannot capture or reauthorize a voided authorization.', PayPalException::AUTHORIZATION_VOIDED, $previous); case 'CANNOT_BE_VOIDED': - throw new PayPalException('A reauthorization cannot be voided. Please void the original parent authorization. You cannot void a reauthorized payment. You must void the original parent authorized payment.', PayPalException::CANNOT_BE_VOIDED); + throw new PayPalException('A reauthorization cannot be voided. Please void the original parent authorization. You cannot void a reauthorized payment. You must void the original parent authorized payment.', PayPalException::CANNOT_BE_VOIDED, $previous); case 'REFUND_NOT_PERMITTED_DUE_TO_CHARGEBACK': - throw new PayPalException('Refunds not allowed on this capture due to a chargeback on the card or bank. Please contact the payee to resolve the chargeback.', PayPalException::REFUND_NOT_PERMITTED_DUE_TO_CHARGEBACK); + throw new PayPalException('Refunds not allowed on this capture due to a chargeback on the card or bank. Please contact the payee to resolve the chargeback.', PayPalException::REFUND_NOT_PERMITTED_DUE_TO_CHARGEBACK, $previous); case 'CAPTURE_DISPUTED_PARTIAL_REFUND_NOT_ALLOWED': - throw new PayPalException('Refund for an amount less than the remaining transaction amount cannot be processed at this time because of an open dispute on the capture. Please visit the PayPal Resolution Center to view the details.', PayPalException::CAPTURE_DISPUTED_PARTIAL_REFUND_NOT_ALLOWED); + throw new PayPalException('Refund for an amount less than the remaining transaction amount cannot be processed at this time because of an open dispute on the capture. Please visit the PayPal Resolution Center to view the details.', PayPalException::CAPTURE_DISPUTED_PARTIAL_REFUND_NOT_ALLOWED, $previous); case 'CAPTURE_FULLY_REFUNDED': - throw new PayPalException('The capture has already been fully refunded. You cannot capture additional refunds against this capture.', PayPalException::CAPTURE_FULLY_REFUNDED); + throw new PayPalException('The capture has already been fully refunded. You cannot capture additional refunds against this capture.', PayPalException::CAPTURE_FULLY_REFUNDED, $previous); case 'DECIMALS_NOT_SUPPORTED': - throw new PayPalException('Currency does not support decimals.', PayPalException::DECIMALS_NOT_SUPPORTED); + throw new PayPalException('Currency does not support decimals.', PayPalException::DECIMALS_NOT_SUPPORTED, $previous); case 'INVALID_PAYEE_ACCOUNT': - throw new PayPalException('Payee account is invalid. Verify the payee account information and try the request again.', PayPalException::INVALID_PAYEE_ACCOUNT); + throw new PayPalException('Payee account is invalid. Verify the payee account information and try the request again.', PayPalException::INVALID_PAYEE_ACCOUNT, $previous); case 'INVALID_PLATFORM_FEES_AMOUNT': - throw new PayPalException('The platform_fees amount cannot be greater than the capture amount. Verify the platform_fees amount and try the request again.', PayPalException::INVALID_PLATFORM_FEES_AMOUNT); + throw new PayPalException('The platform_fees amount cannot be greater than the capture amount. Verify the platform_fees amount and try the request again.', PayPalException::INVALID_PLATFORM_FEES_AMOUNT, $previous); case 'INVALID_STRING_MAX_LENGTH': - throw new PayPalException('The value of a field is too long. The parameter string is too long.', PayPalException::INVALID_STRING_MAX_LENGTH); + throw new PayPalException('The value of a field is too long. The parameter string is too long.', PayPalException::INVALID_STRING_MAX_LENGTH, $previous); case 'MAX_CAPTURE_AMOUNT_EXCEEDED': - throw new PayPalException('Capture amount exceeds allowable limit. Please contact customer service or your account manager to request the change to your overage limit. The default overage limit is 115%, which allows the sum of all captures to be up to 115% of the authorization amount. Specify a different amount and try the request again. Alternately, contact Customer Support to increase your limits.', PayPalException::MAX_CAPTURE_AMOUNT_EXCEEDED); + throw new PayPalException('Capture amount exceeds allowable limit. Please contact customer service or your account manager to request the change to your overage limit. The default overage limit is 115%, which allows the sum of all captures to be up to 115% of the authorization amount. Specify a different amount and try the request again. Alternately, contact Customer Support to increase your limits.', PayPalException::MAX_CAPTURE_AMOUNT_EXCEEDED, $previous); case 'MAX_CAPTURE_COUNT_EXCEEDED': - throw new PayPalException('Maximum number of allowable captures has been reached. No additional captures are possible for this authorization. Please contact customer service or your account manager to change the number of captures that be made for a given authorization. You cannot make additional captures.', PayPalException::MAX_CAPTURE_COUNT_EXCEEDED); + throw new PayPalException('Maximum number of allowable captures has been reached. No additional captures are possible for this authorization. Please contact customer service or your account manager to change the number of captures that be made for a given authorization. You cannot make additional captures.', PayPalException::MAX_CAPTURE_COUNT_EXCEEDED, $previous); case 'MAX_NUMBER_OF_REFUNDS_EXCEEDED': - throw new PayPalException('You have exceeded the number of refunds that can be processed per capture. Please contact customer support or your account manager to review the number of refunds that can be processed per capture.', PayPalException::MAX_NUMBER_OF_REFUNDS_EXCEEDED); + throw new PayPalException('You have exceeded the number of refunds that can be processed per capture. Please contact customer support or your account manager to review the number of refunds that can be processed per capture.', PayPalException::MAX_NUMBER_OF_REFUNDS_EXCEEDED, $previous); case 'PARTIAL_REFUND_NOT_ALLOWED': - throw new PayPalException('You cannot do a refund for an amount less than the original capture amount. Specify an amount equal to the capture amount or omit the amount object from the request. Then, try the request again.', PayPalException::PARTIAL_REFUND_NOT_ALLOWED); + throw new PayPalException('You cannot do a refund for an amount less than the original capture amount. Specify an amount equal to the capture amount or omit the amount object from the request. Then, try the request again.', PayPalException::PARTIAL_REFUND_NOT_ALLOWED, $previous); case 'PENDING_CAPTURE': - throw new PayPalException('Cannot initiate a refund as the capture is pending. Capture is typically pending when the payer has funded the transaction by using an e-check or bank account.', PayPalException::PENDING_CAPTURE); + throw new PayPalException('Cannot initiate a refund as the capture is pending. Capture is typically pending when the payer has funded the transaction by using an e-check or bank account.', PayPalException::PENDING_CAPTURE, $previous); case 'PERMISSION_NOT_GRANTED': - throw new PayPalException('Payee of the authorization has not granted permission to perform capture on the authorization. To make API calls on behalf of a merchant, ensure that you have sufficient permissions to capture the authorization.', PayPalException::PERMISSION_NOT_GRANTED); + throw new PayPalException('Payee of the authorization has not granted permission to perform capture on the authorization. To make API calls on behalf of a merchant, ensure that you have sufficient permissions to capture the authorization.', PayPalException::PERMISSION_NOT_GRANTED, $previous); case 'PREVIOUSLY_CAPTURED': - throw new PayPalException('Authorization has been previously captured and hence cannot be voided. This authorized payment was already captured. You cannot capture it again.', PayPalException::PREVIOUSLY_CAPTURED); + throw new PayPalException('Authorization has been previously captured and hence cannot be voided. This authorized payment was already captured. You cannot capture it again.', PayPalException::PREVIOUSLY_CAPTURED, $previous); case 'PREVIOUSLY_VOIDED': - throw new PayPalException('Authorization has been previously voided and hence cannot be voided again. This authorized payment was already voided. You cannot void it again.', PayPalException::PREVIOUSLY_VOIDED); + throw new PayPalException('Authorization has been previously voided and hence cannot be voided again. This authorized payment was already voided. You cannot void it again.', PayPalException::PREVIOUSLY_VOIDED, $previous); case 'REFUND_AMOUNT_EXCEEDED': - throw new PayPalException('The refund amount must be less than or equal to the capture amount that has not yet been refunded. Verify the refund amount and try the request again.', PayPalException::REFUND_AMOUNT_EXCEEDED); + throw new PayPalException('The refund amount must be less than or equal to the capture amount that has not yet been refunded. Verify the refund amount and try the request again.', PayPalException::REFUND_AMOUNT_EXCEEDED, $previous); case 'REFUND_CAPTURE_CURRENCY_MISMATCH': - throw new PayPalException('Refund must be in the same currency as the capture. Verify the currency of the refund and try the request again.', PayPalException::REFUND_CAPTURE_CURRENCY_MISMATCH); + throw new PayPalException('Refund must be in the same currency as the capture. Verify the currency of the refund and try the request again.', PayPalException::REFUND_CAPTURE_CURRENCY_MISMATCH, $previous); case 'REFUND_FAILED_INSUFFICIENT_FUNDS': - throw new PayPalException('Capture could not be refunded due to insufficient funds. Verify that either you have sufficient funds in your PayPal account or the bank account that is linked to your PayPal account is verified and has sufficient funds.', PayPalException::REFUND_FAILED_INSUFFICIENT_FUNDS); + throw new PayPalException('Capture could not be refunded due to insufficient funds. Verify that either you have sufficient funds in your PayPal account or the bank account that is linked to your PayPal account is verified and has sufficient funds.', PayPalException::REFUND_FAILED_INSUFFICIENT_FUNDS, $previous); case 'REFUND_NOT_ALLOWED': - throw new PayPalException('Full refund refused - partial refund has already been done on this payment. You cannot refund this capture.', PayPalException::REFUND_NOT_ALLOWED); + throw new PayPalException('Full refund refused - partial refund has already been done on this payment. You cannot refund this capture.', PayPalException::REFUND_NOT_ALLOWED, $previous); case 'REFUND_TIME_LIMIT_EXCEEDED': - throw new PayPalException('You are over the time limit to perform a refund on this capture. The refund cannot be issued at this time.', PayPalException::REFUND_TIME_LIMIT_EXCEEDED); + throw new PayPalException('You are over the time limit to perform a refund on this capture. The refund cannot be issued at this time.', PayPalException::REFUND_TIME_LIMIT_EXCEEDED, $previous); case 'NO_EXTERNAL_FUNDING_DETAILS_FOUND': - throw new PayPalException('External funding details not found.', PayPalException::NO_EXTERNAL_FUNDING_DETAILS_FOUND); + throw new PayPalException('External funding details not found.', PayPalException::NO_EXTERNAL_FUNDING_DETAILS_FOUND, $previous); case 'PAYMENT_DENIED': - throw new PayPalException('Payment denied.', PayPalException::PAYMENT_DENIED); + throw new PayPalException('Payment denied.', PayPalException::PAYMENT_DENIED, $previous); case 'CARD_BRAND_NOT_SUPPORTED': - throw new PayPalException('Processing of this card brand is not supported. Use another type of card.', PayPalException::CARD_BRAND_NOT_SUPPORTED); + throw new PayPalException('Processing of this card brand is not supported. Use another type of card.', PayPalException::CARD_BRAND_NOT_SUPPORTED, $previous); case 'RESOURCE_NOT_FOUND': - throw new PayPalException('The specified resource does not exist.', PayPalException::RESOURCE_NOT_FOUND); + throw new PayPalException('The specified resource does not exist.', PayPalException::RESOURCE_NOT_FOUND, $previous); case 'PAYMENT_SOURCE_CANNOT_BE_USED': - throw new PayPalException('The provided payment source cannot be used to pay for the order. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED); + throw new PayPalException('The provided payment source cannot be used to pay for the order. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED, $previous); + case 'PAYPAL_REQUEST_ID_REQUIRED': + throw new PayPalException('A PayPal-Request-Id is required if you are trying to process payment for an Order. Please specify a PayPal-Request-Id or Create the Order without a payment_source specified.', PayPalException::PAYPAL_REQUEST_ID_REQUIRED, $previous); + case 'MALFORMED_REQUEST_JSON': + throw new PayPalException('The request JSON is not well formed.', PayPalException::MALFORMED_REQUEST_JSON, $previous); + case 'PERMISSION_DENIED_FOR_DONATION_ITEMS': + throw new PayPalException('The API caller or payee have not been granted appropriate permissions to send items.category as DONATION. Speak to your account manager if you want to process these type of items.', PayPalException::PERMISSION_DENIED_FOR_DONATION_ITEMS, $previous); + case 'MALFORMED_REQUEST': + throw new PayPalException('You\'ve sent a request that our server could not understand.', PayPalException::MALFORMED_REQUEST, $previous); + case 'BILLING_ADDRESS_INVALID': + throw new PayPalException('Provided billing address is invalid.', PayPalException::BILLING_ADDRESS_INVALID, $previous); + case 'CARD_EXPIRED': + throw new PayPalException('The payment card provided is expired.', PayPalException::CARD_EXPIRED, $previous); + case 'DONATION_ITEMS_NOT_SUPPORTED': + throw new PayPalException('If "purchase_unit" has "DONATION" as the "items.category" then the Order can at most have one purchase_unit. Multiple purchase_units are not supported if either of them have at least one items with category as "DONATION".', PayPalException::DONATION_ITEMS_NOT_SUPPORTED, $previous); + case 'MISSING_PICKUP_ADDRESS': + throw new PayPalException('A pickup address (shipping.address) is required for the provided shipping.type. Possible error location: /purchase_units/0/shipping/type', PayPalException::MISSING_PICKUP_ADDRESS, $previous); + case 'MULTIPLE_ITEM_CATEGORIES': + throw new PayPalException('For a given purchase unit, the items.category could be either PHYSICAL_GOODS and\/or DIGITAL_GOODS or just DONATION. items.category as DONATION cannot be combined with items with either PHYSICAL_GOODS or DIGITAL_GOODS.', PayPalException::MULTIPLE_ITEM_CATEGORIES, $previous); + case 'MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED': + throw new PayPalException('Different shipping.type are not supported across purchase units.', PayPalException::MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED, $previous); + case 'PAYMENT_SOURCE_DECLINED_BY_PROCESSOR': + throw new PayPalException('The provided payment source is declined by the processor. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_DECLINED_BY_PROCESSOR, $previous); + case 'PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED': + throw new PayPalException('The provided payment source is declined by the processor. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED, $previous); + case 'SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT': + throw new PayPalException('The API Caller account is not setup to be able to support a shipping.type=PICKUP_IN_PERSON. This feature is only supported for PayPal Commerce Platform for Platforms and Marketplaces.', PayPalException::SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT, $previous); + case 'UNSUPPORTED_SHIPPING_TYPE': + throw new PayPalException('The provided shipping.type is only supported for application_context.shipping_preference=SET_PROVIDED_ADDRESS or NO_SHIPPING. Possible error location: /purchase_units/0/shipping/type.', PayPalException::UNSUPPORTED_SHIPPING_TYPE, $previous); + case 'CARD_CLOSED': + throw new PayPalException('The card is closed with the issuer.', PayPalException::CARD_CLOSED, $previous); + case 'SAVE_ORDER_NOT_SUPPORTED': + throw new PayPalException('The API caller account is setup in a way that does not allow it to be used for saving the order. This functionality is not available for PayPal Commerce Platform for Platforms & Marketplaces.', PayPalException::SAVE_ORDER_NOT_SUPPORTED, $previous); + case 'PUI_DUPLICATE_ORDER': + throw new PayPalException('A Pay Upon Invoice (Rechnungskauf) order with the same payload has already been successfully processed in the last few seconds. To process a new order, please try again in a few seconds.', PayPalException::PUI_DUPLICATE_ORDER, $previous); + case 'CANNOT_PROCESS_REFUNDS': + throw new PayPalException('We can\'t process any refund at this moment due to technical reasons. Please try again later.', PayPalException::CANNOT_PROCESS_REFUNDS, $previous); + case 'INVALID_REFUND_AMOUNT': + throw new PayPalException('The refund amount is invalid. Please check the refund amount and try again.', PayPalException::INVALID_REFUND_AMOUNT, $previous); default: - throw new PayPalException($this->message, PayPalException::UNKNOWN); + throw new PayPalException($this->message, PayPalException::UNKNOWN, $previous); } } } diff --git a/src/PaypalOrder.php b/src/PaypalOrder.php index 831f6c80d..4cc0e6462 100644 --- a/src/PaypalOrder.php +++ b/src/PaypalOrder.php @@ -20,7 +20,11 @@ namespace PrestaShop\Module\PrestashopCheckout; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; +use Module; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use Ps_checkout; /** * Allow to instantiate a paypal order @@ -47,20 +51,32 @@ public function __construct($id) */ private function loadOrder($id) { - $response = (new Order(\Context::getContext()->link))->fetch($id); + /** @var Ps_checkout $module */ + $module = Module::getInstanceByName('ps_checkout'); - if (false === $response['status'] && ((isset($response['body']['message']) && $response['body']['message'] === 'INVALID_RESOURCE_ID') || $response['exceptionCode'] === 404)) { - \Db::getInstance()->update( - \PsCheckoutCart::$definition['table'], - [ - 'paypal_status' => \PsCheckoutCart::STATUS_CANCELED, - ], - 'paypal_order = "' . pSQL($id) . '"' - ); - } + /** @var CheckoutHttpClient $checkoutHttpClient */ + $checkoutHttpClient = $module->getService('ps_checkout.http.client.checkout'); + + try { + $response = $checkoutHttpClient->fetchOrder([ + 'orderId' => $id, + ]); + $responseHandler = new ResponseApiHandler(); + $response = $responseHandler->handleResponse($response); - if (true === $response['status'] && !empty($response['body'])) { - $this->setOrder($response['body']); + if (true === $response['status'] && !empty($response['body'])) { + $this->setOrder($response['body']); + } + } catch (PayPalException $exception) { + if ($exception->getCode() === PayPalException::INVALID_RESOURCE_ID) { + \Db::getInstance()->update( + \PsCheckoutCart::$definition['table'], + [ + 'paypal_status' => \PsCheckoutCart::STATUS_CANCELED, + ], + 'paypal_order = "' . pSQL($id) . '"' + ); + } } } diff --git a/tests/Unit/Http/CheckoutHttpClientTest.php b/tests/Unit/Http/CheckoutHttpClientTest.php new file mode 100644 index 000000000..3944e6b34 --- /dev/null +++ b/tests/Unit/Http/CheckoutHttpClientTest.php @@ -0,0 +1,417 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Tests\Unit\Http; + +use Http\Client\Exception\HttpException; +use PHPUnit\Framework\TestCase; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\Http\HttpClientInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +class CheckoutHttpClientTest extends TestCase +{ + /** + * @dataProvider invalidRequestErrorsProvider + * + * @throws PayPalException + */ + public function testInvalidRequestErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(400, $errorName, $errorCode); + } + + /** + * @dataProvider notAuthorizedErrorsProvider + * + * @throws PayPalException + */ + public function testNotAuthorizedErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(401, $errorName, $errorCode); + } + + /** + * @dataProvider unprocessableEntityErrorsProvider + * + * @throws PayPalException + */ + public function testUnprocessableEntityErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(422, $errorName, $errorCode); + } + + /** + * @dataProvider notFoundErrorsProvider + * + * @throws PayPalException + */ + public function testNotFoundErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(404, $errorName, $errorCode); + } + + /** + * @dataProvider invalidRequestErrorsProvider + * + * @throws PayPalException + */ + public function testInvalidRequestErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(400, $errorName, $errorCode, true); + } + + /** + * @dataProvider notAuthorizedErrorsProvider + * + * @throws PayPalException + */ + public function testNotAuthorizedErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(401, $errorName, $errorCode, true); + } + + /** + * @dataProvider unprocessableEntityErrorsProvider + * + * @throws PayPalException + */ + public function testUnprocessableEntityErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(422, $errorName, $errorCode, true); + } + + /** + * @dataProvider notFoundErrorsProvider + * + * @throws PayPalException + */ + public function testNotFoundErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(404, $errorName, $errorCode, true); + } + + /** + * @param int $statusCode + * @param string $errorName + * @param int $errorCode + * @param bool $legacy + * + * @throws PayPalException + */ + private function handleTestErrorsCreateOrder($statusCode, $errorName, $errorCode, $legacy = false) + { + $error = $this->getErrorResponse($statusCode, $errorName, $legacy); + $requestMock = $this->createMock(RequestInterface::class); + $streamMock = $this->createMock(StreamInterface::class); + $streamMock->method('__toString')->willReturn(json_encode($error)); + $responseMock = $this->createMock(ResponseInterface::class); + $responseMock->method('getStatusCode')->willReturn($statusCode); + $responseMock->method('getBody')->willReturn($streamMock); + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient->method('sendRequest')->willThrowException(new HttpException('An error occurred', $requestMock, $responseMock)); + $this->expectExceptionCode($errorCode); + $this->expectException(PayPalException::class); + $checkoutHttpClient = new CheckoutHttpClient($httpClient); + $checkoutHttpClient->createOrder([]); + } + + private function getInvalidRequestError($issueError) + { + return [ + 'name' => 'INVALID_REQUEST', + 'message' => 'Request is not well-formed, syntactically incorrect, or violates schema.', + 'debug_id' => 'b6b9a374802ea', + 'details' => [ + [ + 'field' => '', + 'value' => '', + 'location' => 'body', + 'issue' => $issueError, + 'description' => '', + ], + ], + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/orders/v2/#error-INVALID_PARAMETER_VALUE', + 'rel' => 'information_link', + 'encType' => 'application/json', + ], + ], + ]; + } + + /** + * @param int $statusCode + * @param string $errorName + * @param bool $legacy + * + * @return array + */ + private function getErrorResponse($statusCode, $errorName, $legacy) + { + if ($legacy) { + switch ($statusCode) { + case 400: + return $this->getInvalidRequestErrorLegacy($errorName); + case 401: + return $this->getNotAuthorizedErrorLegacy($errorName); + case 404: + return $this->getNotFoundErrorLegacy($errorName); + case 422: + return $this->getUnprocessableEntityErrorLegacy($errorName); + } + } + + switch ($statusCode) { + case 400: + return $this->getInvalidRequestError($errorName); + case 401: + return $this->getNotAuthorizedError($errorName); + case 404: + return $this->getNotFoundError($errorName); + case 422: + return $this->getUnprocessableEntityError($errorName); + } + + return []; + } + + private function getInvalidRequestErrorLegacy($issueError) + { + return [ + 'statusCode' => 400, + 'error' => 'Bad Request', + 'message' => $issueError, + ]; + } + + private function getNotAuthorizedError($issueError) + { + return [ + 'name' => $issueError, + 'message' => 'Authentication failed due to invalid authentication credentials or a missing Authorization header.', + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/overview/#error', + 'rel' => 'information_link', + ], + ], + ]; + } + + private function getNotAuthorizedErrorLegacy($issueError) + { + return [ + 'statusCode' => 401, + 'error' => 'Unprocessable Entity', + 'message' => $issueError, + ]; + } + + private function getUnprocessableEntityError($issueError) + { + return [ + 'name' => 'UNPROCESSABLE_ENTITY', + 'details' => [ + [ + 'field' => '', + 'value' => '', + 'issue' => $issueError, + 'description' => '', + ], + ], + 'message' => 'The requested action could not be performed, semantically incorrect, or failed business validation.', + 'debug_id' => 'c9a75b43fc807', + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/orders/v2/#error-MAX_VALUE_EXCEEDED', + 'rel' => 'information_link', + 'method' => 'GET', + ], + ], + ]; + } + + private function getUnprocessableEntityErrorLegacy($issueError) + { + return [ + 'statusCode' => 422, + 'error' => 'Unprocessable Entity', + 'message' => $issueError, + ]; + } + + /** + * @param string $errorName + * + * @return array + */ + private function getNotFoundError($errorName) + { + return [ + 'name' => $errorName, + 'message' => 'The specified resource does not exist.', + 'debug_id' => 'b6b9a374802ea', + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/orders/v2/#error-' . $errorName, + 'rel' => 'information_link', + 'encType' => 'application/json', + ], + ], + ]; + } + + private function getNotFoundErrorLegacy($issueError) + { + return [ + 'statusCode' => 404, + 'error' => 'Not found', + 'message' => $issueError, + ]; + } + + public function notFoundErrorsProvider() + { + return [ + ['INVALID_RESOURCE_ID', PayPalException::INVALID_RESOURCE_ID], + ]; + } + + public function invalidRequestErrorsProvider() + { + return [ + ['INVALID_ARRAY_MAX_ITEMS', PayPalException::INVALID_ARRAY_MAX_ITEMS], + ['INVALID_ARRAY_MIN_ITEMS', PayPalException::INVALID_ARRAY_MIN_ITEMS], + ['INVALID_COUNTRY_CODE', PayPalException::INVALID_COUNTRY_CODE], + ['INVALID_PARAMETER_SYNTAX', PayPalException::INVALID_PARAMETER_SYNTAX], + ['INVALID_STRING_LENGTH', PayPalException::INVALID_STRING_LENGTH], + ['INVALID_PARAMETER_VALUE', PayPalException::INVALID_PARAMETER_VALUE], + ['MISSING_REQUIRED_PARAMETER', PayPalException::MISSING_REQUIRED_PARAMETER], + ['NOT_SUPPORTED', PayPalException::NOT_SUPPORTED], + ['PAYPAL_REQUEST_ID_REQUIRED', PayPalException::PAYPAL_REQUEST_ID_REQUIRED], + ['MALFORMED_REQUEST_JSON', PayPalException::MALFORMED_REQUEST_JSON], + ['FIELD_NOT_PATCHABLE', PayPalException::FIELD_NOT_PATCHABLE], + ['AMOUNT_NOT_PATCHABLE', PayPalException::AMOUNT_NOT_PATCHABLE], + ['INVALID_PATCH_OPERATION', PayPalException::INVALID_PATCH_OPERATION], + ]; + } + + public function notAuthorizedErrorsProvider() + { + return [ + ['PERMISSION_DENIED', PayPalException::PERMISSION_DENIED], + ['PERMISSION_DENIED_FOR_DONATION_ITEMS', PayPalException::PERMISSION_DENIED_FOR_DONATION_ITEMS], + ['MALFORMED_REQUEST', PayPalException::MALFORMED_REQUEST], + ['PAYEE_ACCOUNT_NOT_SUPPORTED', PayPalException::PAYEE_ACCOUNT_NOT_SUPPORTED], + ['PAYEE_ACCOUNT_NOT_VERIFIED', PayPalException::PAYEE_ACCOUNT_NOT_VERIFIED], + ['PAYEE_NOT_CONSENTED', PayPalException::PAYEE_NOT_CONSENTED], + ['CONSENT_NEEDED', PayPalException::CONSENT_NEEDED], + ['INVALID_ACCOUNT_STATUS', PayPalException::INVALID_ACCOUNT_STATUS], + ]; + } + + public function unprocessableEntityErrorsProvider() + { + return [ + ['AMOUNT_MISMATCH', PayPalException::AMOUNT_MISMATCH], + ['BILLING_ADDRESS_INVALID', PayPalException::BILLING_ADDRESS_INVALID], + ['CANNOT_BE_NEGATIVE', PayPalException::CANNOT_BE_NEGATIVE], + ['CANNOT_BE_ZERO_OR_NEGATIVE', PayPalException::CANNOT_BE_ZERO_OR_NEGATIVE], + ['CARD_EXPIRED', PayPalException::CARD_EXPIRED], + ['CITY_REQUIRED', PayPalException::CITY_REQUIRED], + ['DECIMAL_PRECISION', PayPalException::DECIMAL_PRECISION], + ['DONATION_ITEMS_NOT_SUPPORTED', PayPalException::DONATION_ITEMS_NOT_SUPPORTED], + ['DUPLICATE_REFERENCE_ID', PayPalException::DUPLICATE_REFERENCE_ID], + ['INVALID_CURRENCY_CODE', PayPalException::INVALID_CURRENCY_CODE], + ['INVALID_PAYER_ID', PayPalException::INVALID_PAYER_ID], + ['ITEM_TOTAL_MISMATCH', PayPalException::ITEM_TOTAL_MISMATCH], + ['ITEM_TOTAL_REQUIRED', PayPalException::ITEM_TOTAL_REQUIRED], + ['MAX_VALUE_EXCEEDED', PayPalException::MAX_VALUE_EXCEEDED], + ['MISSING_PICKUP_ADDRESS', PayPalException::MISSING_PICKUP_ADDRESS], + ['MULTIPLE_ITEM_CATEGORIES', PayPalException::MULTIPLE_ITEM_CATEGORIES], + ['MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED', PayPalException::MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED], + ['MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED', PayPalException::MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED], + ['PAYEE_ACCOUNT_INVALID', PayPalException::PAYEE_ACCOUNT_INVALID], + ['PAYEE_ACCOUNT_LOCKED_OR_CLOSED', PayPalException::PAYEE_ACCOUNT_LOCKED_OR_CLOSED], + ['PAYEE_ACCOUNT_RESTRICTED', PayPalException::PAYEE_ACCOUNT_RESTRICTED], + ['REFERENCE_ID_REQUIRED', PayPalException::REFERENCE_ID_REQUIRED], + ['PAYMENT_SOURCE_CANNOT_BE_USED', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED], + ['PAYMENT_SOURCE_DECLINED_BY_PROCESSOR', PayPalException::PAYMENT_SOURCE_DECLINED_BY_PROCESSOR], + ['PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED', PayPalException::PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED], + ['POSTAL_CODE_REQUIRED', PayPalException::POSTAL_CODE_REQUIRED], + ['SHIPPING_ADDRESS_INVALID', PayPalException::SHIPPING_ADDRESS_INVALID], + ['TAX_TOTAL_MISMATCH', PayPalException::TAX_TOTAL_MISMATCH], + ['TAX_TOTAL_REQUIRED', PayPalException::TAX_TOTAL_REQUIRED], + ['UNSUPPORTED_INTENT', PayPalException::UNSUPPORTED_INTENT], + ['UNSUPPORTED_PAYMENT_INSTRUCTION', PayPalException::UNSUPPORTED_PAYMENT_INSTRUCTION], + ['SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT', PayPalException::SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT], + ['UNSUPPORTED_SHIPPING_TYPE', PayPalException::UNSUPPORTED_SHIPPING_TYPE], + ['SHIPPING_OPTION_NOT_SELECTED', PayPalException::SHIPPING_OPTION_NOT_SELECTED], + ['SHIPPING_OPTIONS_NOT_SUPPORTED', PayPalException::SHIPPING_OPTIONS_NOT_SUPPORTED], + ['MULTIPLE_SHIPPING_OPTION_SELECTED', PayPalException::MULTIPLE_SHIPPING_OPTION_SELECTED], + ['PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH', PayPalException::PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH], + ['CARD_CLOSED', PayPalException::CARD_CLOSED], + ['ORDER_CANNOT_BE_SAVED', PayPalException::ORDER_CANNOT_BE_SAVED], + ['SAVE_ORDER_NOT_SUPPORTED', PayPalException::SAVE_ORDER_NOT_SUPPORTED], + ['PUI_DUPLICATE_ORDER', PayPalException::PUI_DUPLICATE_ORDER], + ['INVALID_JSON_POINTER_FORMAT', PayPalException::INVALID_JSON_POINTER_FORMAT], + ['INVALID_PARAMETER', PayPalException::INVALID_PARAMETER], + ['NOT_PATCHABLE', PayPalException::NOT_PATCHABLE], + ['UNSUPPORTED_PATCH_PARAMETER_VALUE', PayPalException::UNSUPPORTED_PATCH_PARAMETER_VALUE], + ['PATCH_VALUE_REQUIRED', PayPalException::PATCH_VALUE_REQUIRED], + ['PATCH_PATH_REQUIRED', PayPalException::PATCH_PATH_REQUIRED], + ['REFERENCE_ID_NOT_FOUND', PayPalException::REFERENCE_ID_NOT_FOUND], + ['MULTI_CURRENCY_ORDER', PayPalException::MULTI_CURRENCY_ORDER], + ['ORDER_ALREADY_COMPLETED', PayPalException::ORDER_ALREADY_COMPLETED], + ['AGREEMENT_ALREADY_CANCELLED', PayPalException::AGREEMENT_ALREADY_CANCELLED], + ['BILLING_AGREEMENT_NOT_FOUND', PayPalException::BILLING_AGREEMENT_NOT_FOUND], + ['COMPLIANCE_VIOLATION', PayPalException::COMPLIANCE_VIOLATION], + ['DOMESTIC_TRANSACTION_REQUIRED', PayPalException::DOMESTIC_TRANSACTION_REQUIRED], + ['DUPLICATE_INVOICE_ID', PayPalException::DUPLICATE_INVOICE_ID], + ['INSTRUMENT_DECLINED', PayPalException::INSTRUMENT_DECLINED], + ['ORDER_NOT_APPROVED', PayPalException::ORDER_NOT_APPROVED], + ['MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED', PayPalException::MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED], + ['PAYEE_BLOCKED_TRANSACTION', PayPalException::PAYEE_BLOCKED_TRANSACTION], + ['PAYER_ACCOUNT_LOCKED_OR_CLOSED', PayPalException::PAYER_ACCOUNT_LOCKED_OR_CLOSED], + ['PAYER_ACCOUNT_RESTRICTED', PayPalException::PAYER_ACCOUNT_RESTRICTED], + ['PAYER_CANNOT_PAY', PayPalException::PAYER_CANNOT_PAY], + ['TRANSACTION_LIMIT_EXCEEDED', PayPalException::TRANSACTION_LIMIT_EXCEEDED], + ['TRANSACTION_RECEIVING_LIMIT_EXCEEDED', PayPalException::TRANSACTION_RECEIVING_LIMIT_EXCEEDED], + ['TRANSACTION_REFUSED', PayPalException::TRANSACTION_REFUSED], + ['REDIRECT_PAYER_FOR_ALTERNATE_FUNDING', PayPalException::REDIRECT_PAYER_FOR_ALTERNATE_FUNDING], + ['ORDER_ALREADY_CAPTURED', PayPalException::ORDER_ALREADY_CAPTURED], + ['TRANSACTION_BLOCKED_BY_PAYEE', PayPalException::TRANSACTION_BLOCKED_BY_PAYEE], + ['ORDER_ALREADY_CAPTURED', PayPalException::ORDER_ALREADY_CAPTURED], + ['AUTH_CAPTURE_NOT_ENABLED', PayPalException::AUTH_CAPTURE_NOT_ENABLED], + ['NOT_ENABLED_FOR_CARD_PROCESSING', PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING], + ['PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING', PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING], + ['INVALID_PICKUP_ADDRESS', PayPalException::INVALID_PICKUP_ADDRESS], + ['SHIPPING_ADDRESS_INVALID', PayPalException::SHIPPING_ADDRESS_INVALID], + ['CANNOT_PROCESS_REFUNDS', PayPalException::CANNOT_PROCESS_REFUNDS], + ['INVALID_REFUND_AMOUNT', PayPalException::INVALID_REFUND_AMOUNT], + ['PAYMENT_DENIED', PayPalException::PAYMENT_DENIED], + ]; + } +}