Apple Pay
Apple Pay works differently from the other demos here: the SDK renders the button and owns the click, because Apple requires the payment sheet to open in the same tick as the tap. So there is no promise to await — results arrive through config.events.payment — and a payment function is called at tap time and must not be async.
Open this page in Safari on macOS, iOS or iPadOS. Anywhere else isApplePayAvailable() reports { available: false, code: 'APPLE_PAY_UNSUPPORTED_BROWSER', message } and you should offer another payment method. The panel below prints whatever reason it gets, because APPLE_PAY_NOT_ENABLED and NOT_INITIALIZED are your problems to fix, not the shopper’s browser.
This demo runs on the page itself rather than in the editable sandbox the other demos use — Apple Pay needs the top-level document and a domain registered with Apple, neither of which a sandbox iframe can provide. It initializes as soon as the page loads. The code below is the code running above.
Loading the SDK…
The code
<div id="tonder-apple-pay-button"></div>
<script src="https://zplit-stage.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js"></script>
<script type="module">
let clientReference = 'order_' + crypto.randomUUID()
let idempotencyKey = 'demo_' + crypto.randomUUID()
const tonder = Tonder.createTonder({
api_key: 'YOUR_PUBLIC_API_KEY',
environment: 'stage',
session: {
customer: {
email: 'jane.doe@example.com',
first_name: 'Jane',
last_name: 'Doe',
},
},
// Apple Pay results arrive here, not from a return value: the SDK owns
// the click, so there is no promise to await. These same callbacks fire
// for pay() as well, so one handler covers every payment.
events: {
payment: {
on_completed: (transaction) => {
// A declined payment arrives here too. Continue your flow based on
// transaction.status — 'Declined' is a result, not an error.
console.log('completed', transaction)
// A settled attempt consumes its references, whatever the outcome.
// Mint fresh ones so the next tap is a new order with its own
// idempotency scope.
clientReference = 'order_' + crypto.randomUUID()
idempotencyKey = 'demo_' + crypto.randomUUID()
},
on_error: (error) => console.error(error.code, error.message),
on_cancel: () => console.log('shopper dismissed the sheet'),
},
},
// Apple allows four things to change — call to action, color, size and
// corner radius — plus the label's language. Safari draws the control
// natively, so anything else you add here never reaches it.
customization: {
apple_pay_button: {
type: 'check-out',
style: 'black',
locale: 'es-MX',
width: '100%',
height: '48px',
border_radius: '8px',
},
},
})
await tonder.init()
// Ask before you render. The answer is an OBJECT, never a bare boolean:
// { available: true }, or { available: false, code, message }. Reading the
// object itself as a condition would always be truthy — read .available.
const availability = tonder.isApplePayAvailable()
if (availability.available) {
const applePay = tonder.create('apple_pay_button', {
// Called SYNCHRONOUSLY when the shopper taps, so it reads whatever the
// cart holds at that moment — amount, currency and references can all
// change after mount without remounting the button. It must not be
// async: Apple requires the sheet to open in the same tick as the tap.
payment: () => ({
amount: 150,
currency: 'MXN',
return_url: window.location.href,
client_reference: clientReference,
idempotency_key: idempotencyKey,
}),
})
await applePay.mount()
} else {
// Do not guess at the reason. code is NOT_INITIALIZED,
// APPLE_PAY_UNSUPPORTED_BROWSER, or APPLE_PAY_NOT_ENABLED — only the
// middle one means "offer another method"; the others are yours to fix.
console.info('Apple Pay hidden:', availability.code, availability.message)
}
// The SDK captures api_key and the customer at createTonder() time, so
// changing either one means building a new instance and mounting again.
</script>
Full API reference, options and error codes: Web SDK README on GitHub .