Applies to: Web Payments SDK
Learn how to create a digital wallet payment request using the Web Payments SDK.
You can create a digital wallet payment request, handle payment request events, and update the payment request after the buyer makes changes to the shipping address or shipping options.
If your application recalculates fees, taxes, and charges based on the buyer shipping location chosen in the Google Pay, Apple Pay, or Afterpay payment sheet, use the PaymentRequest endpoint to set the initial shipping options and shipping charges in the payment sheet and react to the buyer's shipping-related choices.
The PaymentRequestUpdate object has properties that correspond to PaymentRequestOptions properties that are set when you create the payment request. When the callback update
parameter is invoked, the PaymentRequestUpdate
properties replace the property values in the initial request.
The following steps add code to the application you created from the quickstart project sample. If you haven't created an application from using the quickstart project sample or from completing the tasks in Web Payments SDK Quickstart, you need to do so before completing these steps. You should also have completed the Apple Pay, Google Pay, or Afterpay example before continuing.
You can find a complete example of the PaymentRequest code on GitHub.
Note
A payment request update (PaymentRequest.update) can only succeed when the expected Apple Pay, Google Pay, or Afterpay payment sheet isn't open. Otherwise, the update
method fails and returns a false
value.
Your application doesn't need to gather shipping address information from the buyer who pays with a digital wallet. Google Pay and Apple Pay collect and store shipping addresses for the buyer and return the buyer's preferred shipping address to your application in the shippingcontactchanged
event. This event provides a contact
parameter of the ShippingContact type. Be sure to set requestShippingContact: true
in the payment request so that the buyer is prompted to choose a shipping address.
Shipping options let buyers choose a shipping method and cost. These options must be provided by your application with each payment request because the digital wallet providers don't store a seller's shipping options.
It might also be necessary to give a buyer a different set of shipping options after the buyer changes the shipping address on the digital wallet page. In this case, your application updates the ShippingOption array with new shipping choices. When the buyer selects a different shipping option, your application handles another event to update a LineItem to show the updated line item and total amount paid.
The shippingoptionchanged
event provides an option
parameter of the ShippingOption type. Use this parameter to get the shipping option selected by the buyer.
The PaymentRequest
object exposes the addEventListener
function so you can provide your own callback logic to be invoked by the digital wallet on the following actions:
- The digital wallet page is loaded.
- The buyer selects a shipping address.
- The buyer selects a shipping option.
Any of these actions might require your application to recalculate the fees, shipping charges, or shipping options shown on the digital wallet payment page. If a buyer selects a shipping address to a location where you have no shipping, your application should react by updating the payment page with an error message and removing any initial options offered.
These are the typical events that your application should handle:
- The buyer changes the shipping address to a location with different shipping fees. The
shippingcontactchanged
event is fired. Your application recalculates the shipping fees for each shipping option offered and updates the payment request. - The buyer selects one of the updated shipping options. The
shippingoptionchanged
event is fired. Your application updates the shipping fee line item in the payment request.
Depending on the payment method, the shipping address information might be redacted — for example, Apple Pay's shipping information includes only the necessary data for completing transaction tasks, such as calculating taxes or shipping costs — and shouldn't be considered complete. The TokenResult
object contains the full shipping address.
Important
When updating a line item in the payment request, be sure to provide the complete set of LineItem
objects (including the updated line item). Otherwise, the initial set of line items is replaced by the single line item that your application updated.
A new PaymentRequest is created by providing a PaymentRequestOptions object to the payments.paymentRequest
function as an argument.
This example creates a PaymentRequest
with the following details:
- The payment is in the United States and in USD.
- The payment consists of a purchase of $2, shipping charges of $0, and taxes of $.00 (taxes are updated in the following step and when the shipping address is selected).
- The digital wallet page shows billing and shipping addresses for the buyer.
- There are two shipping options: free and expedited. Each option is shown with a cost to the buyer.
- The total purchase amount is the sum of the purchase amount + .00 tax + free shipping.
Create a payment request
Replace the current
buildPaymentRequest
function code with the following code:function buildPaymentRequest(payments) { // Checkpoint 1 const defaultShippingOptions = [ { amount: '0.00', id: 'shipping-option-1', label: 'Free', }, { amount: '10.00', id: 'shipping-option-2', label: 'Expedited', }, ]; let lineItems = [ {amount: '2.00', label: 'Item Cost'}, {amount: '0.00', label: 'Shipping'}, {amount: '0.00', label: 'Tax'}, ]; let total = calculateTotal(lineItems); const paymentRequestDetails = { countryCode: 'US', currencyCode: 'USD', lineItems, requestBillingContact: true, requestShippingContact: true, shippingOptions: defaultShippingOptions, total, }; const req = payments.paymentRequest(paymentRequestDetails); // Checkpoint 2 return req; }Add the following helper function to calculate the total payment:
function calculateTotal(lineItems) { const amount = lineItems .reduce((total, lineItem) => { return total + parseFloat(lineItem.amount); }, 0.0) .toFixed(2); return { amount, label: 'Total' }; }
The PaymentRequest
created by the example sets the details that are shown when the digital wallet payment page is opened for the buyer. The digital wallet sheet doesn't recalculate shipping charges or the total amount based on buyer actions, such as updating the shipping address or selecting a shipping option. You implement the ability to recalculate amounts and update the payment page in the next steps.
Test the application
Open a browser to http://localhost:3000 to verify that the server is running.
Choose a digital wallet button, such as the Google Pay button.
Your web page looks like the following page:
Success
If the digital wallet page shows line amounts, shipping and billing addresses, and a total payment amount, you're ready to write the code that reacts to payment request events.
If you change the shipping option to Expedited in the user interface, the line items and total don't change at this point.
The buyer might select the non-default shipping option, which requires your application to recalculate the total payment based on the newly selected shipping option.
Did you know?
You should get the selected shipping option from this event when it's invoked when loading the payment form. The buyer might accept the default shipping option and close the form without selecting a different option. In this case, the shippingoptionchanged
event is only invoked once, when the form is loaded.
The following example updates the shipping line item with the amount of the selected option and recalculates the total amount. The selected shipping option is provided as the option
parameter in the callback signature.
Add a
shippingoptionchanged
event listener to yourPaymentRequest
in thebuildPaymentRequest
function at checkpoint 2.// Checkpoint 2 req.addEventListener('shippingoptionchanged', (option) => { // Add your business logic here. // This gives you the shipping option the buyer selected and allows // you to update totals based on the price of each shipping option. const newLineItems = updateOrAddLineItems(lineItems, { Shipping: option.amount, }); const total = calculateTotal(newLineItems); // Update the line items so that they can be referenced later in other // eventListener calls. lineItems = newLineItems; // Calling update is required. return { lineItems, total, }; }); // Checkpoint 3Add the
updateOrAddLineItems
helper function to the script tag. This function updates the amounts for changed line items (in this case Shipping) or adds the line item to the list if it doesn't exist.// newAmountByLabel will be in the format of { labelName: amount }, e.g. // { 'Shipping':'10.00', 'Tax':'3.00' } function updateOrAddLineItems(currentLineItems, newAmountsByLabel) { // A list of which newAmounts labels exist in the current line items const updatedLineItem = new Set(); const newLineItems = currentLineItems.map((lineItem) => { updatedLineItem.add(lineItem.label) if (newAmountsByLabel[lineItem.label] !== undefined) { return Object.assign({}, lineItem, { amount: newAmountsByLabel[lineItem.label] }); } return lineItem; }); Object.entries(newAmountsByLabel).forEach(([label, amount]) => { if (!updatedLineItem.has(label)) { newLineItems.push({ label, amount, pending: false }); } }); return newLineItems; }
Test the application
Open a browser to http://localhost:3000 to verify that the server is running.
Choose the digital wallet button to open the digital wallet payment.
Choose the second shipping option.
Your web page looks like the following page:
Success
When selecting the second shipping option on the digital wallets page, the total should update to include that cost. For digital wallets that show line items, you should also see an updated shipping amount.
If your application initiates shipping a product to the buyer, you should handle the shippingcontactchanged
event. This event provides a contact
parameter of the ShippingContact type that includes any relevant contact information, including the shipping address.
Did you know?
You should get the shipping address from this event when it's invoked when loading the payment form. The buyer might accept the default shipping address and close the form without selecting a different address. In this case, the shippingcontactchanged
event is only invoked once, when the form is loaded.
Your application might have different shipping options and charges based on the buyer's shipping address. If the buyer changes the address, your application reacts by recalculating charges or offering different shipping options.
This example changes the shipping charges for the two shipping options depending on what state is being shipped to. If the state is California, free shipping is offered. In California, express shipping has a lower charge. Elsewhere, the fees are higher. The tax is calculated based on what state the buyer lives in.
The example calculates the new shipping option charges and calculates a new total payment amount.
Add a
shippingcontactchanged
event listener to yourPaymentRequest
in thebuildPaymentRequest
function at checkpoint 3.// Checkpoint 3 req.addEventListener('shippingcontactchanged', (contact) => { // Add your business logic here. // This tells you the address of the buyer, and allows you to update your // shipping options and pricing based on their location. const isCA = contact.state === 'CA'; const newShippingOptions = isCA ? defaultShippingOptions : [ { id: 'shipping-options-3', label: 'Standard Shipping', amount: '15.00', }, { id: 'shipping-options-4', label: 'Express Shipping', amount: '25.00', }, ]; const taxableItem = lineItems.find((lineItem) => { return lineItem.label === 'Item Cost'; }); const tax = calculateTax(taxableItem.amount, contact.state); // Whenever the shipping contact is changed, the shipping option defaults // to the first option. This leads to the shippingoptionchanged event // being emitted for each contact change if a shipping address is // required. const newLineItems = updateOrAddLineItems(lineItems, { Tax: tax }); total = calculateTotal(newLineItems); lineItems = newLineItems; return { lineItems: newLineItems, shippingOptions: newShippingOptions, total, }; });Add the following helper method to calculate a new tax amount:
function calculateTax(amount, state) { let taxPercent = 0.06; // These aren't accurate and are used just for the example. switch (state) { case 'CA': taxPercent = 0.1; break; case 'GA': taxPercent = 0.075; break; case 'MI': taxPercent = 0.05; break; } const taxAmount = parseFloat(amount) * taxPercent; // returns a string that truncates to 2 digits, matching the format of // '1.25' return taxAmount.toFixed(2); }
The total payment amount shown is based on a default choice of the first shipping option.
Test the application
Open a browser to http://localhost:3000 to verify that the server is running.
Choose the digital wallet button to open the digital wallet payment.
Change the shipping address to any non-California address or the following address.
Your web page looks like the following page:
Success
If the digital wallet page is updated, the shipping amount of the select option changes to the higher amount for a non-California shipping address.