diff --git a/Api/CheckoutSessionManagementInterface.php b/Api/CheckoutSessionManagementInterface.php index 4d42b102..c5e0d04e 100755 --- a/Api/CheckoutSessionManagementInterface.php +++ b/Api/CheckoutSessionManagementInterface.php @@ -21,9 +21,10 @@ interface CheckoutSessionManagementInterface { /** + * @param mixed|null $cartId * @return mixed */ - public function getConfig(); + public function getConfig($cartId = null); /** * @param mixed $amazonSessionId @@ -45,13 +46,15 @@ public function getPaymentDescriptor($amazonSessionId); /** * @param mixed $amazonSessionId + * @param mixed|null $cartId * @return string */ - public function updateCheckoutSession($amazonSessionId); + public function updateCheckoutSession($amazonSessionId, $cartId = null); /** * @param mixed $amazonSessionId + * @param mixed|null $cartId * @return int */ - public function completeCheckoutSession($amazonSessionId); + public function completeCheckoutSession($amazonSessionId, $cartId = null); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 250e2d3c..43178434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 5.7.1 +* Fixed issue when phone number not required and entered in Magento +* Updated API calls to take in a masked cart ID so they can be used without relying on Magento sessions +* Updated logging to sanitize some data + ## 5.7.0 * Changed the response of completeCheckoutSession API call to include both increment ID and order ID * Fixed issue with logging in when a customer has an empty password hash (thanks @rafczow!) @@ -8,7 +13,6 @@ * Fixed issue where using Amazon Pay in the Payment Methods section did not work on one step checkouts * Fixed issue where using Amazon Pay in the Payment Methods section could bypass agreeing to Terms and Conditions * Removed usage of isPlaceOrderActionAllowed in js components -* Updated API calls to take in a masked cart ID so they can be used without relying on Magento sessions * Updated response validators to look for specific response code and states ## 5.6.0 diff --git a/CustomerData/CheckoutSession.php b/CustomerData/CheckoutSession.php index 91bb3004..25ec4ef8 100755 --- a/CustomerData/CheckoutSession.php +++ b/CustomerData/CheckoutSession.php @@ -48,6 +48,6 @@ public function __construct( */ public function getConfig() { - return $this->checkoutSessionManagement->getConfig($this->session->getQuote()); + return $this->checkoutSessionManagement->getConfig(); } } diff --git a/Model/Adapter/AmazonPayAdapter.php b/Model/Adapter/AmazonPayAdapter.php index 2d38e814..426d668f 100755 --- a/Model/Adapter/AmazonPayAdapter.php +++ b/Model/Adapter/AmazonPayAdapter.php @@ -459,18 +459,36 @@ protected function processResponse($clientResponse, $functionName) // Log $isError = !in_array($response['status'], [200, 201]); if ($isError || $this->amazonConfig->isLoggingEnabled()) { - $debugBackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); - $this->logger->debug($functionName . ' <- ', $debugBackTrace[1]['args']); - if ($isError) { - $this->logger->error($functionName . ' -> ', $response); - } else { - $this->logger->debug($functionName . ' -> ', $response); - } + $this->logSanitized($functionName, $response, $isError); } return $response; } + protected function logSanitized($functionName, $response, $isError) + { + $debugBackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 3); + $buyerKeys = ['buyerId' => '', 'primeMembershipTypes' => '', 'status' => '']; + + if ($functionName == 'getBuyer') { + $response = array_intersect_key($response, $buyerKeys); + $debugBackTrace[2]['args'] = []; + } + + if (isset($response['buyer'])) { + $response['buyer'] = array_intersect_key($response['buyer'], $buyerKeys); + } + + unset($response['shippingAddress'], $response['billingAddress']); + + $this->logger->debug($functionName . ' <- ', $debugBackTrace[2]['args']); + if ($isError) { + $this->logger->error($functionName . ' -> ', $response); + } else { + $this->logger->debug($functionName . ' -> ', $response); + } + } + /** * Generate idempotency header * @@ -568,7 +586,15 @@ public function generatePayNowButtonPayload(Quote $quote, $paymentIntent = Payme $addressData[$addressKey] = $streetLine; } - $addressData = array_filter($addressData); + // Remove empty fields, or ones that contain only "-" + $addressData = array_filter($addressData, function ($val) { + return !empty($val) && $val != "-"; + }); + + // Make sure phone number is set for PayNow button + if (!array_key_exists('phoneNumber', $addressData)) { + $addressData['phoneNumber'] = "0"; + } $payload['addressDetails'] = $addressData; } diff --git a/Model/CheckoutSessionManagement.php b/Model/CheckoutSessionManagement.php index 11afe738..f68d6e54 100755 --- a/Model/CheckoutSessionManagement.php +++ b/Model/CheckoutSessionManagement.php @@ -22,12 +22,15 @@ use Amazon\Pay\Model\Config\Source\PaymentAction; use Amazon\Pay\Model\AsyncManagement; use http\Exception\UnexpectedValueException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\NotFoundException; use Magento\Quote\Api\Data\CartInterface; use Magento\Framework\Validator\Exception as ValidatorException; use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order\Invoice; use Magento\Sales\Model\Order\Payment; +use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; use Magento\Sales\Api\Data\TransactionInterface as Transaction; class CheckoutSessionManagement implements \Amazon\Pay\Api\CheckoutSessionManagementInterface @@ -137,6 +140,11 @@ class CheckoutSessionManagement implements \Amazon\Pay\Api\CheckoutSessionManage */ private $orderCollectionFactory; + /** + * @var MaskedQuoteIdToQuoteIdInterface + */ + private $maskedQuoteIdConverter; + /** * CheckoutSessionManagement constructor. * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -158,6 +166,7 @@ class CheckoutSessionManagement implements \Amazon\Pay\Api\CheckoutSessionManage * @param \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder * @param \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory + * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdConverter */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, @@ -178,7 +187,8 @@ public function __construct( \Amazon\Pay\Model\AsyncManagement\Charge $asyncCharge, \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository, \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder, - \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory + \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory, + MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdConverter ) { $this->storeManager = $storeManager; $this->quoteIdMaskFactory = $quoteIdMaskFactory; @@ -199,6 +209,7 @@ public function __construct( $this->transactionRepository = $transactionRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->orderCollectionFactory = $orderCollectionFactory; + $this->maskedQuoteIdConverter = $maskedQuoteIdConverter; } /** @@ -219,10 +230,10 @@ protected function getAmazonSession($amazonSessionId) /** * @return bool */ - protected function canCheckoutWithAmazon() + protected function canCheckoutWithAmazon($quote) { return $this->amazonConfig->isEnabled() && - !$this->amazonHelper->hasRestrictedProducts($this->magentoCheckoutSession->getQuote()); + !$this->amazonHelper->hasRestrictedProducts($quote); } /** @@ -303,22 +314,47 @@ protected function convertToMagentoAddress(array $address, $isShippingAddress = return [$this->addressHelper->convertToArray($magentoAddress)]; } + /** + * Load quote from provided masked quote ID or falls back to loading from the session + * @param $cartId null|string + * @return false|CartInterface|\Magento\Quote\Model\Quote + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getQuoteFromIdOrSession($cartId = null) + { + try { + if (empty($cartId)) { + $quote = $this->magentoCheckoutSession->getQuote(); + } else { + $quoteId = $this->maskedQuoteIdConverter->execute($cartId); + $quote = $this->cartRepository->get($quoteId); + } + } catch (NoSuchEntityException $e) { + return false; + } + + return $quote; + } + /** * {@inheritdoc} */ - public function getConfig() + public function getConfig($cartId = null) { + if (!$quote = $this->getQuoteFromIdOrSession($cartId)) { + return []; + } + // Ensure the totals are up to date, in case the checkout does something to update qty or shipping without // collecting totals - $this->magentoCheckoutSession->getQuote()->collectTotals(); + $quote->collectTotals(); $result = []; - if ($this->canCheckoutWithAmazon()) { - $magentoQuote = $this->magentoCheckoutSession->getQuote(); + if ($this->canCheckoutWithAmazon($quote)) { $loginButtonPayload = $this->amazonAdapter->generateLoginButtonPayload(); $checkoutButtonPayload = $this->amazonAdapter->generateCheckoutButtonPayload(); $payNowButtonPayload = $this->amazonAdapter->generatePayNowButtonPayload( - $magentoQuote, + $quote, $this->amazonConfig->getPaymentAction() ); @@ -327,7 +363,7 @@ public function getConfig() 'currency' => $this->amazonConfig->getCurrencyCode(), 'button_color' => $this->amazonConfig->getButtonColor(), 'language' => $this->amazonConfig->getLanguage(), - 'pay_only' => $this->amazonHelper->isPayOnly($magentoQuote), + 'pay_only' => $this->amazonHelper->isPayOnly($quote), 'sandbox' => $this->amazonConfig->isSandboxEnabled(), 'login_payload' => $loginButtonPayload, 'login_signature' => $this->amazonAdapter->signButton($loginButtonPayload), @@ -346,13 +382,9 @@ public function getConfig() */ public function getShippingAddress($amazonSessionId) { - $result = false; - - if ($this->canCheckoutWithAmazon()) { - $result = $this->fetchAddress($amazonSessionId, true, function ($session) { - return $session['shippingAddress'] ?? []; - }); - } + $result = $this->fetchAddress($amazonSessionId, true, function ($session) { + return $session['shippingAddress'] ?? []; + }); return $result; } @@ -362,13 +394,9 @@ public function getShippingAddress($amazonSessionId) */ public function getBillingAddress($amazonSessionId) { - $result = false; - - if ($this->canCheckoutWithAmazon()) { - $result = $this->fetchAddress($amazonSessionId, false, function ($session) { - return $session['billingAddress'] ?? []; - }); - } + $result = $this->fetchAddress($amazonSessionId, false, function ($session) { + return $session['billingAddress'] ?? []; + }); return $result; } @@ -385,13 +413,16 @@ public function getPaymentDescriptor($amazonSessionId) /** * {@inheritdoc} */ - public function updateCheckoutSession($amazonCheckoutSessionId) + public function updateCheckoutSession($amazonCheckoutSessionId, $cartId = null) { - $quote = $this->magentoCheckoutSession->getQuote(); + if (!$quote = $this->getQuoteFromIdOrSession($cartId)) { + return []; + } + $result = null; $paymentIntent = Adapter\AmazonPayAdapter::PAYMENT_INTENT_AUTHORIZE; - if ($this->canCheckoutWithAmazon()) { + if ($this->canCheckoutWithAmazon($quote)) { $response = $this->amazonAdapter->updateCheckoutSession( $quote, $amazonCheckoutSessionId, @@ -399,6 +430,8 @@ public function updateCheckoutSession($amazonCheckoutSessionId) ); if (!empty($response['webCheckoutDetails']['amazonPayRedirectUrl'])) { $result = $response['webCheckoutDetails']['amazonPayRedirectUrl']; + } elseif (!empty($response) && $response['status'] == 404) { + $result = ['status' => $response['reasonCode']]; } } return $result; @@ -487,13 +520,13 @@ protected function setProcessing($payment) * Add capture comment to order * * @param Payment $payment - * @param $cart + * @param $quote * @param $chargeId */ - protected function addCaptureComment($payment, $cart, $chargeId) + protected function addCaptureComment($payment, $quote, $chargeId) { $order = $payment->getOrder(); - $formattedAmount = $order->getBaseCurrency()->formatTxt($cart->getBaseGrandTotal()); + $formattedAmount = $order->getBaseCurrency()->formatTxt($quote->getBaseGrandTotal()); if ($order->getBaseCurrencyCode() != $order->getOrderCurrencyCode()) { $formattedAmount = $formattedAmount . ' [' . $order->formatPriceTxt($payment->getAmountOrdered()) . ']'; } @@ -538,24 +571,26 @@ private function cancelOrder($order) /** * {@inheritdoc} */ - public function completeCheckoutSession($amazonSessionId) + public function completeCheckoutSession($amazonSessionId, $cartId = null) { - $cart = $this->magentoCheckoutSession->getQuote(); + if (!$quote = $this->getQuoteFromIdOrSession($cartId)) { + return ['success' => false]; + } - if (empty($amazonSessionId) || !$this->canCheckoutWithAmazon() || !$this->canSubmitQuote($cart)) { + if (empty($amazonSessionId) || !$this->canCheckoutWithAmazon($quote) || !$this->canSubmitQuote($quote)) { return [ 'success' => false, 'message' => __("Unable to complete Amazon Pay checkout"), ]; } try { - if (!$cart->getCustomer()->getId()) { - $cart->setCheckoutMethod(\Magento\Quote\Api\CartManagementInterface::METHOD_GUEST); + if (!$quote->getCustomer()->getId()) { + $quote->setCheckoutMethod(\Magento\Quote\Api\CartManagementInterface::METHOD_GUEST); } // check the Amazon session one last time before placing the order $amazonSession = $this->amazonAdapter->getCheckoutSession( - $cart->getStoreId(), + $quote->getStoreId(), $amazonSessionId ); if ($amazonSession['statusDetails']['state'] == 'Canceled') { @@ -578,15 +613,15 @@ public function completeCheckoutSession($amazonSessionId) $amazonAddress = $this->amazonAddressFactory->create(['address' => $address]); $customerAddress = $this->addressHelper->convertToMagentoEntity($amazonAddress); - $cart->getBillingAddress()->importCustomerAddressData($customerAddress); - if (empty($cart->getCustomerEmail())) { - $cart->setCustomerEmail($amazonSession['buyer']['email']); + $quote->getBillingAddress()->importCustomerAddressData($customerAddress); + if (empty($quote->getCustomerEmail())) { + $quote->setCustomerEmail($amazonSession['buyer']['email']); } } // get payment to load it in the session, so that a salesrule that relies on payment method conditions // can work as expected - $payment = $this->magentoCheckoutSession->getQuote()->getPayment(); + $payment = $quote->getPayment(); // Some checkout flows (especially 3rd party) could get to this point without setting payment method if (empty($payment->getMethod())) { @@ -598,9 +633,9 @@ public function completeCheckoutSession($amazonSessionId) // collect quote totals before placing order (needed for 2.3.0 and lower) // https://github.com/amzn/amazon-payments-magento-2-plugin/issues/992 - $this->magentoCheckoutSession->getQuote()->collectTotals(); + $quote->collectTotals(); - $orderId = $this->cartManagement->placeOrder($cart->getId()); + $orderId = $this->cartManagement->placeOrder($quote->getId()); $order = $this->orderRepository->get($orderId); $result = [ 'success' => true, @@ -609,10 +644,10 @@ public function completeCheckoutSession($amazonSessionId) ]; $amazonCompleteCheckoutResult = $this->amazonAdapter->completeCheckoutSession( - $cart->getStoreId(), + $quote->getStoreId(), $amazonSessionId, - $cart->getGrandTotal(), - $cart->getQuoteCurrencyCode() + $quote->getGrandTotal(), + $quote->getQuoteCurrencyCode() ); $completeCheckoutStatus = $amazonCompleteCheckoutResult['status'] ?? '404'; if (!preg_match('/^2\d\d$/', $completeCheckoutStatus)) { @@ -620,12 +655,12 @@ public function completeCheckoutSession($amazonSessionId) $this->cancelOrder($order); $session = $this->amazonAdapter->getCheckoutSession( - $cart->getStoreId(), + $quote->getStoreId(), $amazonSessionId ); if (isset($session['chargePermissionId'])) { $this->amazonAdapter->closeChargePermission( - $cart->getStoreId(), + $quote->getStoreId(), $session['chargePermissionId'], 'Canceled due to checkout session failed to complete', true @@ -648,15 +683,15 @@ public function completeCheckoutSession($amazonSessionId) $this->amazonConfig->getPaymentAction() == PaymentAction::AUTHORIZE_AND_CAPTURE) { // capture on Amazon Pay $this->amazonAdapter->captureCharge( - $cart->getStoreId(), + $quote->getStoreId(), $chargeId, - $cart->getGrandTotal(), - $cart->getQuoteCurrencyCode() + $quote->getGrandTotal(), + $quote->getQuoteCurrencyCode() ); // capture and invoice on the Magento side - $this->asyncCharge->capture($order, $chargeId, $cart->getGrandTotal()); + $this->asyncCharge->capture($order, $chargeId, $quote->getGrandTotal()); } - $amazonCharge = $this->amazonAdapter->getCharge($cart->getStoreId(), $chargeId); + $amazonCharge = $this->amazonAdapter->getCharge($quote->getStoreId(), $chargeId); //Send merchantReferenceId to Amazon $this->amazonAdapter->updateChargePermission( @@ -677,7 +712,7 @@ public function completeCheckoutSession($amazonSessionId) case 'Authorized': if ($this->amazonConfig->getAuthorizationMode() == AuthorizationMode::SYNC_THEN_ASYNC) { $this->setProcessing($payment); - $this->addCaptureComment($payment, $cart, $amazonCharge['chargePermissionId']); + $this->addCaptureComment($payment, $quote, $amazonCharge['chargePermissionId']); } break; case 'Captured': @@ -686,7 +721,7 @@ public function completeCheckoutSession($amazonSessionId) if ($this->amazonConfig->getAuthorizationMode() == AuthorizationMode::SYNC_THEN_ASYNC) { $this->setProcessing($payment); - $this->addCaptureComment($payment, $cart, $chargeId); + $this->addCaptureComment($payment, $quote, $chargeId); } break; } @@ -700,13 +735,13 @@ public function completeCheckoutSession($amazonSessionId) } catch (\Exception $e) { $session = $this->amazonAdapter->getCheckoutSession( - $cart->getStoreId(), + $quote->getStoreId(), $amazonSessionId ); if (isset($session['chargePermissionId'])) { $this->amazonAdapter->closeChargePermission( - $cart->getStoreId(), + $quote->getStoreId(), $session['chargePermissionId'], 'Canceled due to technical issue: ' . $e->getMessage(), true diff --git a/README.md b/README.md index 4c811151..a4221e8e 100755 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The following table provides an overview on which Git branch is compatible to wh Magento Version | Github Branch | Latest release ---|---|--- 2.2.6 - 2.2.11 (EOL) | [V2checkout-1.2.x](https://github.com/amzn/amazon-payments-magento-2-plugin/tree/V2checkout-1.2.x) | 1.20.0 (EOL) -2.3.0 - 2.4.x | [master](https://github.com/amzn/amazon-payments-magento-2-plugin/tree/master) | 5.7.0 +2.3.0 - 2.4.x | [master](https://github.com/amzn/amazon-payments-magento-2-plugin/tree/master) | 5.7.1 ## Release Notes See [CHANGELOG.md](/CHANGELOG.md) diff --git a/composer.json b/composer.json index bd24ba35..cf1e4bad 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "amzn/amazon-pay-magento-2-module", "description": "Official Magento2 Plugin to integrate with Amazon Pay", "type": "magento2-module", - "version": "5.7.0", + "version": "5.7.1", "license": [ "Apache-2.0" ], diff --git a/etc/webapi.xml b/etc/webapi.xml index 41e54b31..351e305a 100755 --- a/etc/webapi.xml +++ b/etc/webapi.xml @@ -22,6 +22,12 @@ + + + + + + @@ -46,10 +52,22 @@ + + + + + + + + + + + +