chameleon-system/amazon-pay-bundle
Payment with Amazon Pay
Requires
- amzn/amazon-pay-sdk-php: ~3.4.0
- ext-json: *
README
This bundle enables payments with Amazon Pay, so customers can use their Amazon account to checkout. The bundle adds "Pay with Amazon" buttons to the checkout and renders login-, address- and wallet-widgets to select addresses and payment methods from the customer's Amazon account.
Payments via Amazon Pay have transaction support via the ShopPaymentTransactionsBundle.
Setup
Follow this step by step guide to setup Amazon Pay.
Prerequisites
Amazon Pay requires an Amazon seller account and a Login With Amazon account. Sign up at https://pay.amazon.com
Login With Amazon Settings
Register a new application and add your shop domains to "Allowed JavaScript Origins" and "Allowed Return URLs" under "Web Settings". The "Client ID" will be needed later for the payment handler configuration.
Install Bundle
In the projects composer.json, require chameleon-system/amazon-pay-bundle in the version that matches the Chameleon
version used and run composer update.
Add ChameleonSystem\AmazonPayBundle\AmazonPayBundle() to the AppKernel.
Run app/console assets:install.
Run CMS updates.
Basic Configuration
For Amazon Pay to work, we need to set a few parameters in the payment handler: merchantId, accessKey and secretKey
from the Amazon Seller Central and clientId from the "Login With Amazon" account.
Also make sure the region parameter fits your seller account.
This is the minimum amount of configuration for the payment handler, review other configuration parameters based on the section Handling Payments in the documentation.
For Amazon Pay to actually show up in the shop, add the shop payment method to the shipping groups where it applies and configure the payment method according to your needs (Allowed shipping countries, ...). Don't forget to enable the payment method by setting the "Active" field.
To receive notifications about declined transactions and errors from IPNs, set e-mail addresses in the e-mail profiles with IDs "AMAZON-PAY-STATE-WITH-ACTION-REQUIRED" and "AMAZON-PAY-IPN-FAILURE".
Templates and JavaScript
The bundle provides a basic set of templates and the required JavaScript.
Amazon Pay uses different templates for address-, shipping/payment- and confirm steps. Basic templates ship with the bundle, but have to be adjusted to fit the projects design and potential special features.
Note: The included JavaScript is designed to work with ECMA5 and does not require jQuery. We do use FormData and classList, so it won't work with IE < 10.
"Pay with Amazon" Buttons
Anywhere the Amazon Pay button should be rendered, include amazonPay/button/payWithAmazonButton.html.twig with mapper
\ChameleonSystem\AmazonPayBundle\Bridge\Chameleon\Mapper\AmazonPayButtonMapper. Buttons only show up if the Amazon Pay
payment method is enabled and available.
To change the appearance of the buttons, use your own template and adjust data-color and data-size according to [https://developer.amazon.com/docs/amazon-pay-onetime/button-widgets.html#color] and [https://developer.amazon.com/docs/amazon-pay-onetime/button-widgets.html#size]
Address Step (user.view.php)
Replaces all the address handling with a single widget by rendering amazonPay/addressBook/addressBookWidget.html.twig.
We need an additional hidden input:
<input type="hidden" name="amazonOrderReferenceId" value="<?= TGlobal::OutHTML($amazonOrderReferenceId) ?>" class="set-amazon-order-reference-id">
The "Next"-button to proceed to the next step should only be enabled when an address is selected. This will automatically work if the button has the ID "primarypaymentbutton" or the class "amazon-pay-enable-on-address-select".
It is recommended to use the template from the bundle and just change styles and button labels if needed.
Shipping/Payment Step (shipping.view.php)
The shipping steps need to show a wallet widget to select a payment method from Amazon by rendering
amazonPay/wallet/walletWidget.html.twig.
In the bundle, the template uses the original shipping view from the standard theme if it exists. We have the view in the bundle because there is a good chance we need to have a different view for Amazon Pay depending on the design and structure of the shipping step and we have to overwrite it for Amazon Pay specifically.
In the view from the standard theme, the Amazon Pay wallet widget is rendered by the view of the payment handler (ChameleonSystemAmazonPayPaymentHandler/standard.view.php), so the widget is rendered in the place where the additional inputs and description of the other payment methods would be and everything else is the same. All the other payment methods will be hidden automatically.
If the design or structure of your shipping step does not support this, use your own shipping template and render the widget directly. Make sure the selection of the shipping group is included if necessary.
Confirm Step (confirm.view.php)
Amazon Pay uses AJAX-requests when the "Buy now"-button is clicked, so we need a few changes for this to work:
Any button to confirm the order ("Buy now") need to have the class "amazon-pay-buy-now" and attributes "data-seller-id" and "data-order-reference-id".
Validation of anything specific for this step should also happen via JavaScript! In most cases, this will only be the
terms and conditions checkbox. This is needed because the form is submitted via AJAX. ProcessStep() and the validation
therein is still executed (so it is not possible to place an order without validation), but we don't get anything back
for our JavaScript to work with. We do check if there are validation errors in ProcessStep() and just reload if there
are any so the messages are displayed. But it's better to catch potential errors before we submit the form, so we don't
have to cancel the already running confirmationFlow process. To validate before submit, define a JavaScript callback
function window.amazonPayOnBuyButtonClicked. The function gets the clicked button as the only argument and needs to
display any validation errors directly and must return "true" if the validation was successful, or else "false". An
example callback is already shipped in amazonPay.js to handle the terms and conditions checkbox.
During the processing of the payment, the class amazon-pay-processing is added to the body. Use this to show visual
feedback like a spinner or an overlay so the customer is aware that the order is being processed.
We also need to display the selected shipping address and payment method. This is done by rendering the
amazonPay/addressBook/addressBookWidgetShowOnly.html.twig and amazonPay/wallet/walletWidgetShowOnly.html.twig.
It is recommended to copy the template from the bundle and adjust it to your design needs.
Setting up IPNs
The URL to handle Instant Payment Notifications is "https://yourdomain.com/_api_pkgshopipn_amazon-pay". Add it in the Amazon Seller Central, Settings -> Integration Settings -> Instant Notification Settings -> Merchant URL. The URLs needs to be publicly available and have a valid SSL certificate.
Testing the Integration in Sandbox-Mode
In dev-mode, Amazon Pay should already run in Sandbox-Mode. You can tell by a small s-Icon in the Buttons and Widgets. Sandbox-Mode can be forced by setting the environment setting for the payment handler.
How to use test accounts in Sandbox-Mode: https://developer.amazon.com/docs/amazon-pay-onetime/testing-in-sandbox.html
Upgrading from previous versions of Amazon Pay
The previous bundle to handle Amazon Pay payments has been deprecated. This bundle is designed to work alongside the previous bundle to allow switching at any time.
Important Changes
The underlying Amazon Pay SDK has been changed to the current, official Version. This implements the new Second Payments Services Directive (PSD2) and allows additional authentication like 3D-Secure (Strong Customer Authentication).
>The new bundle comes with a new payment handler and new payment method, so any order exports or places where the IDs or >system names are used have to be changed.
Multilangual buttons and widgets are now supported.
The payment is now executed after the "Buy now" button is clicked. This means there can be canceled orders or in the worst case, incomplete orders when the payment fails. So make sure fields "canceled" and "system_order_save_completed" are checked before shipping or exporting orders.
It is now possible to use asynchronous authorizations to give Amazon more time to handle an authorization and therefore reduce the amount of declined authorizations. See Handling Payments for more on this.
To comply with Amazon Pay policy, error messages have been changed and some errors (like rejected payment methods) now disable Amazon Pay for the user's session while some errors like "InvalidPaymentMethod" allow changing the payment method before re-submitting the order.
Configuration
There is a new payment group named "Amazon Pay" (with the system name "amazon-pay", whereas the previous version is just "amazon"). The configuration from the previous group is copied to the new one via bundle updates.
There is however one new obligatory parameter clientId. Get it from the "Login With Amazon" account. If you don't
already have an account, please see "Prerequisites" and "Login With Amazon Settings" in the Setup section.
New "Pay with Amazon" Buttons in Frontend
To add the new buttons, include 'amazonPay/button/payWithAmazonButton.html.twig' in custom templates and add the mapper
\ChameleonSystem\AmazonPayBundle\Bridge\Chameleon\Mapper\AmazonPayButtonMapper.
{% include 'amazonPay/button/payWithAmazonButton.html.twig' with {'amazonPayButton': amazonPayButton} %}
$viewRenderer->AddMapper(new \ChameleonSystem\AmazonPayBundle\Bridge\Chameleon\Mapper\AmazonPayButtonMapper());
Search for pkgshoppaymentamazon/amazonbutton.html.twig and AmazonButtonWidgetMapper to find occurrences of the
previous version.
It is recommended to add the new buttons and not replace the previous versions. So it is possible to switch between the two. Both versions only show up when the respecting payment is enabled.
Templates and JavaScript
Templates and JavaScript have changed, please follow the steps described in "Templates and JavaScript" from the Setup section.
Switching to the new version
Disable the old payment method and enable the new payment method.
Note: Due to JavaScript conflicts in the Amazon Pay widgets, both payment methods should not be active at the same time.
IPNs
The URL to handle Instant Payment Notifications has changed to "https://yourdomain.com/_api_pkgshopipn_amazon-pay". See "Setting up IPNs" in the Setup section.
Handling Payments
This section outlines the payment and shipping workflow.
Transactions
The bundle uses transactions as implemented in the ShopPaymentTransactionsBundle and therefore supports making captures and refunds.
Additionally, orders have a separate field for Amazon Pay transactions. Every transaction made by the shop shows up there with it's current status.
When Orders are Ready for Shipping and Paid
We use the field "is_paid" in the shop_order table to indicate if the order amount is completely authorized and paid.
When dealing with captureNow=0 or asynchronous orders, the field changes to "Yes" as soon as we receive the corresponding
IPNs and the event amazon_pay.order_is_paid is dispatched.
We use the additional field amazon_pay_ready_for_shipping to indicate if the order can be shipped. This is true when:
- captureNow=0: The order amount is completely authorized and no pending authorizations are left
- captureNow=1: The order amount is completely captured, no pending authorizations or captures are left
The field changes to "Yes" as soon as we receive the corresponding IPNs and the event amazon_pay.billing_address_updated
is dispatched.
Note: The events are only dispatched when there is a change (= an order becomes ready for shipping or changed to "paid"). If the order is already ready for shipping or paid as it is placed, the fields reflect this and no addtional events are dispatched. So in this case, you can handle shipping when exporting the order.
Synchronous vs. Asynchronous Payments
When the payment is authorized, Amazon has to decide if the authorization is accepted or declined. When it is made synchronously, a decision is made in the during the request within eight seconds. Often, a final decision cannot be made during this time and the authorization is declined. By making the authorization asynchronous, we don't wait for immediate feedback and give Amazon more time and so decrease the chances of the authorization being declined.
Using asynchronous payments
Set the parameter authorizationMode in the payment handler settings to "asynchronous". Now, when an order is placed,
the authorization is in state "Pending". We show the "Thank you" page with a notice that the payment is still being
processed. The notice also shows up in the order notification. It can be edited as a CMS text block with system name
"amazon-pay-asynchronous-notice". At this time, the authorization can still be declined. When there is a decision, the
shop is informed via IPN. If the authorization is declined, the shop owner is informed via e-mail. Else, the field
"Ready for shipping" changes to "Yes" (and "Is Paid" changes to "Yes" if the authorization was also captured) and we
dispatch the amazon_pay.order_ready_for_shipping event (and possibly amazon_pay.order_is_paid).
You probably shouldn't ship any goods until the authorization is accepted. Should the authorization be declined, you have to resolve the situation with the customer. It is also possible to handle this case automatically by using your own IPN handler, for example to cancel the order and inform the customer or to give the customer the opportunity to select a different payment method. See "Handling IPNs" for details.
Note: Setting captureNow to '1' does not mean the capture is confirmed when using asynchronous payments, because we have to wait for the pending authorization.
Using omnichronous payments
Omnichronous means, that a synchronous authorization is made first. When this authorization comes back with a timeout, we make another authorization, but asynchronous this time. So it's a combination of the two.
Set the parameter authorizationMode in the payment handler settings to "omnichronous" to use omnichronous payment.
Capture on Shipping vs. Capture Now
The parameter captureNow in the payment handler settings determines if payments should be captured as soon as the order
is placed or when the goods are shipped.
Note: Amazon strongly recommends to set captureNow to '0' (= capture on shipping).
Set this to '1' if you want to charge the customer and have the payment confirmed before shipping orders. The order amount is captured and charged right away with the authorization. If the authorization is asynchronous, the capture is made as soon as the authorization goes through, and therefore could still be declined.
Set this to '0' to only charge the customer as soon as goods are shipped. Only an authorization is made when placing orders. As soon as you ship goods, capture the amount of the shipment via the shop payment transaction bundle in the backend or programmatically:
try {
$transactionManager = new TPkgShopPaymentTransactionManager($shopOrder);
$paymentHandler = $shopOrder->GetPaymentHandler();
$paymentTransactionHandler = $paymentHandler->paymentTransactionHandlerFactory($shopOrder->fieldCmsPortalId);
$transaction = $paymentTransactionHandler->captureShipment(
$transactionManager,
$shopOrder,
$valueToCapture,
null,
null
);
} catch (TPkgCmsException_LogAndMessage $e) {
//handle error
}
Note: When capturing a shipment, only one capture can be charged on any authorization. So from the second capture on,
additional authorizations are created. Per default, these are made asynchronously. It is possible to use synchronous or
omnichronous authorizations by setting the authorizationModeForNewShipmentAuthorizations parameter in the payment handler
settings accordingly. It is also possible to use the mode from the order by setting the parameter to "fromOrder".
Downloads
Should orders contain downloads, these are always authorized and captured synchronously, because they can be downloaded right away. This means, there can be different transactions for downloads and other items.
To change the behaviour of splitting into multiple transactions, replace the service
chameleon_system_amazon_pay.shop_payment_transaction_items_factory with your own version.
Handling IPNs
We can handle all types of IPNs (authorization, capture, refund, order reference and chargeback) by using IPN-handlers. A set of handlers is already defined in the bundle config:
chameleon_system_amazon_pay:
ipn_handler: [
"chameleon_system_amazon_pay.ipn_handler.declined_transactions",
"chameleon_system_amazon_pay.ipn_handler.update_transactions",
"chameleon_system_amazon_pay.ipn_handler.asynchronous_order_becomes_ready_for_shipping",
"chameleon_system_amazon_pay.ipn_handler.order_is_paid",
"chameleon_system_amazon_pay.ipn_handler.chargeback",
"chameleon_system_amazon_pay.ipn_handler.closed_order_reference",
"chameleon_system_amazon_pay.ipn_handler.update_billing_address",
]
To add your own, replace or add in the config. An IPN handler must implement AmazonPayIpnHandlerInterface. It is
recommended to just extend AbstractIpnHandler and only implement the methods for the IPNs you want to handle. All
handlers are executed in the given order until one of them returns "false".
Technical debt
To use the ShopPaymentTransactionsBundle, we have to accept some technical debt. The bundle makes heavy use of the now deprecated "TPkgCmsException_LogAndMessage" exception and it is part of some of the interfaces. So the exception is used within the transaction handler and then handled outside where necessary.
Also, we don't use the monolog bundle for logging as we can't inject our logger via dependency injection.
This bundle needs to work with Chameleon 6.1, so a couple of newer features like mappers as services or the service locator are not used.