Skip to main content

Sessions and events

Learn how to create payment request sessions, attach event listeners, and handle the payment lifecycle with the Shop Pay Wallet API.


Use the shopId and clientId from your onboarding email to configure the API. For the full list of options, see configure parameters.

window.ShopPay.PaymentRequest.configure({
shopId: 1,
clientId: "[REPLACE-ME]",
});

Create a session to make a payment request. shop_id (integer) can be retrieved from the shop object in the Admin API.

PaymentRequest fields are defined here.

const initialPaymentRequest = window.ShopPay.PaymentRequest.build({
lineItems: [
{
label: "T-Shirt",
originalItemPrice: {
amount: 10.00,
currencyCode: "USD"
},
itemDiscounts: [
{
label: "10% off",
amount: {
amount: 1.00,
currencyCode: "USD"
}
}
],
finalItemPrice: {
amount: 9.00,
currencyCode: "USD"
},
quantity: 2,
sku: "t-shirt",
requiresShipping: true,
originalLinePrice: {
amount: 20.00,
currencyCode: "USD"
},
lineDiscounts: [
{
label: "10% off",
amount: {
amount: 2.00,
currencyCode: "USD"
}
}
],
finalLinePrice: {
amount: 18.00,
currencyCode: "USD"
},
}
],
discountCodes: [],
deliveryMethods: [],
subtotal: {
amount: 18.00,
currencyCode: "USD"
},
totalTax: {
amount: 1.25,
currencyCode: "USD"
},
total: {
amount: 19.25,
currencyCode: "USD"
},
presentmentCurrency: "USD",
locale: 'en',
});

const session = window.ShopPay.PaymentRequest.createSession({
paymentRequest: initialPaymentRequest
});

Anchor to Attach event listenersAttach event listeners

Use ShopPayPaymentRequestSessionCreate on your server to create a session.

session.addEventListener("sessionrequested", (ev) => {
// Shop Pay Payment Request Session on your server
const response = fetch('/replace_with_your_endpoint', {
method: 'POST',
body: JSON.stringify({
payment_request: initialPaymentRequest
}),
headers: {
'Content-Type': 'application/json',
},
}).then(response => response.json()).then(data => {
const {token, checkoutUrl, sourceIdentifier} = data;
// optionally update the payment request if it has changed since it was created
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({YOUR_UPDATED_PAYMENT_REQUEST});
session.completeSessionRequest({token, checkoutUrl, sourceIdentifier, updatedPaymentRequest});
});
});

Listen to events that may change calculations such as when a delivery method type, shipping address, delivery method, pickup location, pickup location filter, or discount code changes. Recalculate the payment request and update the session.

session.addEventListener("deliverymethodtypechanged", async (ev) => {
const currentPaymentRequest = session.paymentRequest;
const deliveryMethodType = ev.deliveryMethodType;

let pickupLocations = [];
if (deliveryMethodType === 'PICKUP') {
pickupLocations = await fetchPickupLocations();
}

// Update the payment request based on the delivery method type change
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({
...currentPaymentRequest,
pickupLocations,
});

session.completeDeliveryMethodTypeChange({ updatedPaymentRequest: updatedPaymentRequest });
});
session.addEventListener("shippingaddresschanged", async (ev) => {
const currentPaymentRequest = session.paymentRequest;
const selectedAddress = ev.shippingAddress;

// Update the payment request based on the shipping address change
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({
...currentPaymentRequest,
deliveryMethods: [
{
label: "Standard",
amount: {
amount: 10.00,
currencyCode: "USD"
},
code: "STANDARD",
minDeliveryDate: '2024-01-01',
maxDeliveryDate: '2027-01-01',
},
{
label: "Express",
amount: {
amount: 20.00,
currencyCode: "USD"
},
code: "EXPRESS",
minDeliveryDate: '2024-01-01',
maxDeliveryDate: '2026-01-01',
}
]
});

session.completeShippingAddressChange({ updatedPaymentRequest: updatedPaymentRequest });
});
session.addEventListener("deliverymethodchanged", async (ev) => {
const currentPaymentRequest = session.paymentRequest;
const selectedDeliveryMethod = ev.deliveryMethod;

let updatedRequestValues;

if (selectedDeliveryMethod) {
updatedRequestValues = {shippingLines: [{
label: selectedDeliveryMethod.label,
amount: selectedDeliveryMethod.amount,
code: selectedDeliveryMethod.code
}],
totalShippingPrice: {
finalTotal: {
amount: selectedDeliveryMethod.amount.amount,
currencyCode: "USD",
},
},
total: {
amount: 20 + selectedDeliveryMethod.amount.amount,
currencyCode: "USD"
}}
} else {
updatedRequestValues= {total: {
amount: 20,
currencyCode: "USD"
}}
}

// Update the payment request based on the delivery method change
// and update the totals accordingly
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({
...currentPaymentRequest,
...updatedRequestValues,
});

session.completeDeliveryMethodChange({ updatedPaymentRequest: updatedPaymentRequest });
});
session.addEventListener("pickuplocationchanged", async (ev) => {
const currentPaymentRequest = session.paymentRequest;
const pickupLocation = ev.pickupLocation;

// Update the payment request based on the pickup location change
// and update the totals accordingly
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({
...currentPaymentRequest,
totalShippingPrice: {
finalTotal: {
amount: pickupLocation.amount.amount,
currencyCode: "USD",
},
},
total: {
amount: 20 + pickupLocation.amount.amount,
currencyCode: "USD"
},
});

session.completePickupLocationChange({ updatedPaymentRequest: updatedPaymentRequest });
});
session.addEventListener("pickuplocationfilterchanged", async (ev) => {
const currentPaymentRequest = session.paymentRequest;
const buyerLocation = ev.buyerLocation;

// Update the payment request based on the pickup location filter change
// by filtering the available pickup locations based on a customer's location
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({
...currentPaymentRequest,
pickupLocations: [{
label: "620 King Street West",
code: "PICK_UP-KING-STREET-WEST",
detail: "620 King Street West, Toronto, ON",
amount: {
amount: 10.00,
currencyCode: "USD"
},
readyExpectationLabel: "Ready in 1 hour",
proximityLabel: "Less than 1 km away",
}]
});

session.completePickupLocationFilterChange({ updatedPaymentRequest: updatedPaymentRequest });
});
session.addEventListener("discountcodechanged", async (ev) => {
const currentPaymentRequest = session.paymentRequest;
const selectedDiscountCodes = ev.discountCodes; // Array of discount codes ["example-code-1"]

// Update the payment request based on the discount code change
// Let's assume the discount code is valid and the discount is 15% off
const updatedPaymentRequest = window.ShopPay.PaymentRequest.build({
...currentPaymentRequest,
discountCodes: selectedDiscountCodes,
lineItems: [
{
label: "T-Shirt",
finalItemPrice: {
amount: 10.00,
currencyCode: "USD"
},
quantity: 2,
sku: "t-shirt",
requiresShipping: true,
finalLinePrice: {
amount: 20.00,
currencyCode: "USD"
},
}
],
subtotal: {
amount: 20.00,
currencyCode: "USD"
},
discounts: [
{
label: "example-code-1",
amount: {
amount: 3.00, // Discounts must be passed to Shopify as a positive value
currencyCode: "USD"
}
}
],
totalTax: {
amount: 1.06,
currencyCode: "USD"
},
total: {
amount: 18.06,
currencyCode: "USD"
}
});

session.completeDiscountCodeChange({ updatedPaymentRequest: updatedPaymentRequest });
});

Confirm the payment once the user clicks the Pay now button in the Shop Pay popup.

The server confirmation must invoke the ShopPayPaymentRequestSessionSubmit mutation to confirm that the payment is to be processed. Use ShopPayPaymentRequestSessionSubmit on your server to submit the session.

session.addEventListener("paymentconfirmationrequested", async (ev) => {
// The customer's billing address contains relevant contact details such as email & phone number (if available)
const billingAddress = ev.billingAddress;

// Before submitting the payment request for processing, a final check should be done on your server
// to make sure the payment request (total price, inventory available, etc.) is still valid.
const response = fetch('/replace_with_your_endpoint', {
method: 'POST',
body: JSON.stringify({
token: session.token,
payment_request: session.paymentRequest
}),
headers: {
'Content-Type': 'application/json',
},
}).then(response => response.json()).then(data => {
if (data.errors) {
// Handle errors here.
// For example: if an item is no longer in stock, you can send a new paymentRequest without that lineItem by building a new payment request and including it in completePaymentConfirmationRequest along with the errors.
session.completePaymentConfirmationRequest({
errors: [
{
"type": "generalError",
"message": "Something went wrong. Please try again."
}
// Optionally build an updated paymentRequest and include it here
]
})
} else {
// confirm the payment request is processing
session.completePaymentConfirmationRequest();
}
});
});

This event is dispatched when the payment is complete. Close the Shop Pay popup and redirect the user to the order confirmation page.

session.addEventListener("paymentcomplete", async (ev) => {
console.log(ev.processingStatus.status);

session.close(); // close the Shop Pay popup
window.location.href = "/thank-you";
});

This event is dispatched when a payment attempt fails. The event contains information about why the payment failed.

session.addEventListener("paymentattemptfailed", async (ev) => {
const { reason, errorCode } = ev.error;
console.log(`Payment attempt failed: ${reason}`);
console.log(`Error code: ${errorCode}`);
});
Caution

Buyers have the option to select a different payment method to retry their checkout after a failure. Don't call session.close() so that users can retry with a new payment method without disrupting their session.

This event is dispatched when the checkout window is closed.

session.addEventListener("windowclosed", async () => {
// handle window closed event
});
Warning

Only call the corresponding complete call once for each event.


Was this page helpful?