Repository navigation
Apple Pay and Google Pay
The SDK supports two ways to accept Apple Pay and Google Pay:
| Token payment (recommended) | Widget | |
|---|---|---|
| Button | Official Apple Pay / Google Pay button on your own page | Epoint page inside an iframe / webview |
| When the Epoint record is created | When you call tokenPayment(), ideally right after the customer authorizes in the wallet sheet |
When you create the widget, before the customer pays |
| Result | Synchronous response from applePay() / googlePay()
|
postMessage from the iframe (no redirect) |
| Status lookup | Record is bound to the merchant immediately; checkStatus()->orderId() always finds it |
Record is bound to the merchant only after it is paid |
| Charged at most once | Yes: Epoint charges a record only while it is NEW
|
One widget can be paid only once, but the same order_id can get new records |
Browser (your page) Your server Epoint
------------------- ----------- ------
click Apple Pay button
new ApplePaySession(...).begin()
onvalidatemerchant ───────────────────▶ applePaySession()->origin(...) ─────▶ /token/apple/session
completeMerchantValidation(session) ◀─ getSession()
onpaymentauthorized(token) ───────────▶ 1. re-validate order, amount
2. tokenPayment()->create() ───────▶ /token/payment
3. applePay()->paymentId()->pay() ──▶ /token/apple/pay
4. checkStatus()->orderId() ───────▶ /get-status
completePayment(SUCCESS/FAILURE) ◀─ result
$session = $client->applePaySession()
->origin('https://shop.example.az') // origin of the page that shows the button
->create();
if (! $session->isSuccess()) {
// abort the Apple Pay sheet
}
return json_encode(['session' => $session->getSession()]); // -> completeMerchantValidation()The origin's domain must be registered for Apple Pay on Epoint's side. Apple also requires the domain association file to be served from https://<domain>/.well-known/apple-developer-merchantid-domain-association.
Create the record when the customer has authorized in the wallet sheet, after re-validating the order on your server. Then the charged amount always matches the order the customer confirmed.
use Epoint\Enums\Language;
$tokenPayment = $client->tokenPayment()
->amount(25.50)
->orderId('ORDER-001-1') // unique per attempt, max 250 characters
->description('Order #ORDER-001')
->language(Language::AZ)
->create();
$paymentId = $tokenPayment->getPaymentId();If the merchant has unique order IDs enabled, Epoint rejects a second record with the same order_id (Duplicate order_id value). Use an attempt suffix such as ORDER-001-1, ORDER-001-2.
// Apple Pay: event.payment.token.paymentData from onpaymentauthorized
$result = $client->applePay()
->paymentId($paymentId)
->applePaymentData($paymentData) // array, encoded to base64 JSON for you
->billingContact($billingContact)
->pay();
// Google Pay: paymentMethodData.tokenizationData.token from loadPaymentData()
$result = $client->googlePay()
->paymentId($paymentId)
->googleRawToken($rawToken) // string, encoded to base64 for you
->billingContact($billingAddress + ['email' => $email])
->pay();
if (! $result->isSuccess()) {
// declined or already processed: $result->getMessage(), $result->getCode()
}A declined charge is returned as an unsuccessful TokenPayResponse, not as an exception: Epoint answers it with HTTP 400 and a JSON body. Network errors and non-JSON responses still throw EpointException.
If you already have a base64 token in Epoint's format, use ->token($base64Token) instead.
Treat the pay() response as a hint and confirm the payment before marking an order paid:
$status = $client->checkStatus()->orderId('ORDER-001-1')->get();
$paid = $status->isSuccess()
&& $status->getOrderId() === 'ORDER-001-1'
&& (int) round($status->getAmount() * 100) === 2550;If the request times out or the customer closes the page, keep the attempt and check its status again later (for example from a scheduled job). Do not create a new record for the same attempt.
button.addEventListener('click', () => {
const session = new ApplePaySession(3, {
countryCode: 'AZ',
currencyCode: 'AZN',
supportedNetworks: ['visa', 'masterCard'],
merchantCapabilities: ['supports3DS'],
total: { type: 'final', label: 'Shop name', amount: '25.50' },
});
session.onvalidatemerchant = async () => {
const res = await fetch('/api/apple-pay/session', { method: 'POST', headers: { 'X-CSRF-Token': csrf } });
const body = await res.json();
body.session ? session.completeMerchantValidation(body.session) : session.abort();
};
session.onpaymentauthorized = async (event) => {
const res = await fetch('/api/apple-pay/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
body: JSON.stringify({ paymentData: event.payment.token.paymentData, billingContact: event.payment.billingContact }),
});
const body = await res.json();
session.completePayment(body.paid ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE);
};
session.oncancel = () => { /* customer closed the sheet: not a failed payment */ };
session.begin(); // must be called synchronously inside the click handler
});Google Pay tokenization uses Epoint's gateway settings:
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: { gateway: 'cybersource', gatewayMerchantId: '<your gateway merchant id from Epoint>' },
},
merchantInfo: { merchantId: '<Google merchant id from Epoint>', merchantName: 'Shop name' },Ask Epoint for the gatewayMerchantId of your merchant account and the Google merchantId.
- Never log wallet tokens,
paymentDataor full billing contacts. - Re-calculate the amount on your server. Never trust an amount sent by the browser.
- Protect your session and pay endpoints with CSRF and rate limiting.
- Apple Pay / Google Pay must be enabled for API use on your merchant account, otherwise Epoint answers
You don't have access to this operation.
$widget = $client->widget()
->amount(75.00)
->orderId('ORDER-005')
->description('Digital wallet payment')
->create();
echo '<iframe src="' . $widget->getWidgetUrl() . '" allow="payment"></iframe>';The widget reports its result with window.parent.postMessage({status: 'success' | 'error', ...}). It does not redirect. Confirm the payment with checkStatus()->orderId() before marking the order paid.