Hi, we are setting up Apple Pay on our website which sells only digital goods. We don't collect a shipping address because we aren't shipping anything. We want to use the user's billing address in order to show them the total amount (including sales tax) before they authorize the purchase. However, it seems that the billing address isn't always provided by Apple Pay before the payment is authorized.
With Apple Pay, what is the recommended way of acquiring the user's billing address before they authorize the purchase?
--
More details about our setup:
We are using the Apple Pay JS API.
In createPaymentRequest, we specify requiredBillingContactFields: ['postalAddress'], but per the docs, the address is provided after the user authorizes the transaction. That is too late for us because we want to show the sales tax before the user authorizes the purchase.
We have attempted to work around this by getting the billing contact details in session.onpaymentmethodselected. For example:
session.onpaymentmethodselected = function (event) {
const billingContact = event.paymentMethod.billingContact;
// Sometimes `billingContact` exists, but other times it does not
}
This doc states:
Before the user authorizes the transaction, you receive redacted billing contact information in a callback event. The redacted information includes only the necessary data for completing transaction tasks, such as calculating taxes or shipping costs.
But in practice, we've observed that sometimes no billing contact information is provided. When a user switches from one card to another, we seem to never get the billing contact associated with the newly selected card.
Is there something we're missing?
Hi @laura01,
You wrote:
[...] But in practice, we've observed that sometimes no billing contact information is provided. When a user switches from one card to another, we seem to never get the billing contact associated with the newly selected card. [...] Is there something we're missing? [...]
The behavior you're observing is by design. However, I'd suggest for you to submit a report via Feedback Assistant with your use case and concerns. Once submitted, please reply here with the Feedback ID so I may escalate to the Apple Pay engineering team directly.
As a workaround, you may use a redacted postal code when available; otherwise, provide a graceful fallback. Use onpaymentmethodselected to update the total when you get a postal code, and handle the absent case explicitly.
session.onpaymentmethodselected = function(event) {
const billingContact = event.paymentMethod.billingContact;
if (billingContact?.postalCode && billingContact?.countryCode) {
// You have enough for a tax lookup (postal code + country)
fetchTaxRate(billingContact.postalCode, billingContact.countryCode)
.then(taxRate => {
const tax = basePrice * taxRate;
session.completePaymentMethodSelection({
newTotal: {
label: 'Your Company',
amount: String((basePrice + tax).toFixed(2))
},
newLineItems: [
{ label: 'Subtotal', amount: String(basePrice.toFixed(2)) },
{ label: 'Sales Tax', amount: String(tax.toFixed(2)) }
]
});
});
} else {
// Fallback: show total without tax, or with estimated tax
session.completePaymentMethodSelection({
newTotal: {
label: 'Your Company',
amount: String(basePrice.toFixed(2))
},
newLineItems: [
{ label: 'Subtotal', amount: String(basePrice.toFixed(2)) },
{ label: 'Sales Tax', amount: '0.00', type: 'pending' } // <-- key technique
]
});
}
};
The type: 'pending' line item is the critical piece most implementations miss. It signals to your customer that a line item amount is not yet finalized and will be determined before the transaction completes. Apple Pay renders this visually differently from a 0.00 final amount—it shows a dash or a "Pending" label rather than a dollar amount, which is transparent to your customers.
Then, in onpaymentauthorized, you have the full validated billing contact and can compute the exact tax, and critically—you can still abort the transaction here if the tax calculation requires it:
session.onpaymentauthorized = function(event) {
const billingContact = event.payment.billingContact; // Full address now guaranteed
const postalCode = billingContact.postalCode;
const countryCode = billingContact.countryCode;
fetchTaxRate(postalCode, countryCode).then(taxRate => {
const tax = basePrice * taxRate;
const finalTotal = basePrice + tax;
// Send finalTotal to your backend for charge
processPayment(event.payment.token, finalTotal)
.then(() => {
session.completePayment(ApplePaySession.STATUS_SUCCESS);
})
.catch(() => {
session.completePayment(ApplePaySession.STATUS_FAILURE);
});
});
};
The key shift in thinking here is that onpaymentauthorized isn't too late to calculate the real tax—it is too late to show it before the biometric prompt, but it isn't too late to charge the correct amount.
The pending line item pattern bridges the UX gap honestly, and the two-phase compute (estimate pre-auth, exact post-auth) is the best practice for this scenario.
Cheers,
Paris X Pinkney | WWDR | DTS Engineer