Wallets: Google Pay and Apple Pay from your own page
PayLolly takes Google Pay and Apple Pay on its hosted payment page. This page is for merchants who show the wallet button on their own page and send the payment from their own server: the payer pays with a wallet, PayLolly decrypts the token and sends the card's network token and cryptogram to the acquirer. Your servers never see the card.
There are three ways to take wallet payments: on the hosted page, on your own page with PayLolly's Apple and Google registrations (this page), or on your own page with your own registrations. Setting up your own Apple Pay and Google Pay helps you choose and describes the third.
The two wallets work the same way from your server: you ask PayLolly for what the page needs, the page shows the wallet's sheet and gets an encrypted token, and your server sends the token with the order to PayLolly and gets the outcome in the response.
sequenceDiagram
Browser->>+Website: Open the checkout
Website->>+Api: Payment/WalletConfig (wallet, amount, currency)
Api->>+Website: What the page needs for the sheet
Website->>+Browser: Page with the wallet button
Browser->>+Wallet: Payer chooses a card in the sheet
Note over Browser,Api: Apple Pay only: the sheet asks for a merchant session
Browser->>+Website: Validation url (Apple Pay only)
Website->>+Api: Payment/ApplePayValidate
Api->>+Website: Apple's merchant session
Wallet->>+Browser: Encrypted payment token
Browser->>+Website: Token
Website->>+Api: Payment/PayWithGoogle or Payment/PayWithApple (token, amount, merchantRef)
Api->>+Website: Response with the outcome
Website->>+Browser: Result page
What you need
- The permissions on your merchant: the Wallet API permission (to send wallet payments from your server), and the permission for the wallet: Google Pay, Apple Pay, or both (ask PayLolly to switch them on). The Wallet API permission is separate from the permission to send sync payments: you do not need that one for a wallet payment. Without the Wallet API permission every call is refused with
WAL07. The permission for the wallet also switches the wallet on for the hosted payment page, where it additionally needsWithApplePayorWithGooglePayin themodeof the request. - A merchant account whose processor has been enabled for the wallet. Today only simulated (test) accounts take wallet payments. For a live acquirer account, ask PayLolly whether and when the wallet is enabled for it before you build anything for production. A request that has no such account is refused with
RTE05. - The wallet set up by PayLolly. With PayLolly's registrations (the usual case, described on this page) you need no Google merchant id, Apple merchant id or keys of your own: PayLolly's are in what
Payment/WalletConfiggives you. If you want your own registrations instead, see Setting up your own Apple Pay and Google Pay. - Your websites.
- Google Pay: in production Google shows the sheet only on websites it has approved for the merchant id. With PayLolly's registration, give PayLolly the websites that will show the button before you go live. In the TEST environment no approval is needed. (With your own Google merchant id you register your websites with Google yourself.)
- Apple Pay: every website that shows the button must be registered with Apple for the Apple merchant id in use. With PayLolly's registration, give PayLolly the website (for a page in an iframe, the top-level website). PayLolly adds it to its Apple Pay configuration and gives you the domain association file that Apple provides. Serve it from your website at
/.well-known/apple-developer-merchantid-domain-association(the path Apple reads; serving the same file at the same path with.txtadded does no harm) so Apple can verify the website.Payment/ApplePayValidateonly answers for registered websites (WAL05). (With your own Apple merchant id you register and verify your websites with Apple yourself.)
How the payment is taken
It is a sync payment: you send one request and get the final outcome in the response, with no redirect, no returnUrl and no Async status. The wallet's cryptogram and ECI stand in for the 3D Secure step, which a call from your server could not do. The consequences are:
- The payer is present and the wallet has authenticated them: the payment is customer initiated (
CIT) and one time. You cannot sendinitiation,recurrence, anagreementor anagreementId. - It only goes to merchant accounts whose processor takes the wallet and synchronous payments (the processors that take a card from your server directly).
- There is no 3D Secure step: the cryptogram and ECI of the token are sent to the acquirer where a 3D Secure result would be.
- The amount is the amount you send, and it must be the amount the payer was shown in the sheet. Apple's token carries the amount and currency, and the payment is refused if they differ from your request (
WAL02): a token cannot pay another amount. Google's tokens carry none, so the amount you send is what is authorised. - The
webHooksare those of a sync payment (approved,completed,txUpdate). capture,refund,voidand the other payment actions work on the payment as for any payment.- Only Visa and Mastercard are accepted.
Payment/WalletConfig
Call it from your server with your MerchantKey, for the wallet, and the amount and currency of the payment that will follow, and hand the response to your page.
request:
{
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"wallet": "GooglePay",
"amount": 10.5,
"currency": "EUR",
"withAddress": false,
"withPhoneNumber": false
}
wallet:GooglePayorApplePay.withAddress: ask for the full billing address in the sheet (the minimum otherwise).withPhoneNumber: ask for a phone number.
Google Pay
Step 1: the config
response of Payment/WalletConfig for GooglePay:
{
"wallet": "GooglePay",
"environment": "TEST",
"sdkUrl": "https://pay.google.com/gp/p/js/pay.js",
"paymentDataRequest": {
"apiVersion": 2,
"apiVersionMinor": 0,
"allowedPaymentMethods": [
{
"type": "CARD",
"parameters": {
"allowedAuthMethods": ["CRYPTOGRAM_3DS", "PAN_ONLY"],
"allowedCardNetworks": ["MASTERCARD", "VISA"],
"billingAddressRequired": true,
"billingAddressParameters": { "format": "MIN", "phoneNumberRequired": false }
},
"tokenizationSpecification": {
"type": "DIRECT",
"parameters": { "protocolVersion": "ECv2", "publicKey": "BNPf..." }
}
}
],
"merchantInfo": { "merchantId": "BCR2DN4T...", "merchantName": "Test Shop Ltd" },
"transactionInfo": {
"countryCode": "MT",
"currencyCode": "EUR",
"totalPriceStatus": "FINAL",
"totalPrice": "10.50",
"totalPriceLabel": "Test Shop Ltd"
},
"emailRequired": true
},
"buttonColorLight": "black",
"buttonColorDark": "white"
}
environment is TEST or PRODUCTION. PAN_ONLY in allowedAuthMethods appears in the TEST environment only: it lets Google's test cards (a card number, no cryptogram) be used. Production accepts network tokens with a cryptogram only.
Warning
Use the paymentDataRequest exactly as it is given. It holds the merchant id and the public key the token is encrypted with: a request built by hand with another merchant id or key gives a token PayLolly cannot decrypt (WAL01). (A merchant with its own Google Pay registration may build the request itself with its own merchant id and the public key PayLolly gives it: see Setting up your own Apple Pay and Google Pay.)
Step 2: show the sheet
On your page load Google's script from sdkUrl and use Google's own button. Pass environment to the payments client and paymentDataRequest to loadPaymentData. The token to send to your server is paymentData.paymentMethodData.tokenizationData.token (a string).
<script async src="https://pay.google.com/gp/p/js/pay.js" onload="start()"></script>
<div id="gpay"></div>
<script>
// config is the response of Payment/WalletConfig, given to the page by your server
function start() {
const client = new google.payments.api.PaymentsClient({ environment: config.environment });
const method = config.paymentDataRequest.allowedPaymentMethods[0];
client.isReadyToPay({
apiVersion: config.paymentDataRequest.apiVersion,
apiVersionMinor: config.paymentDataRequest.apiVersionMinor,
allowedPaymentMethods: [{ type: method.type, parameters: {
allowedAuthMethods: method.parameters.allowedAuthMethods,
allowedCardNetworks: method.parameters.allowedCardNetworks } }]
}).then(r => {
if (!r.result) return; // keep the card form: this payer cannot use Google Pay
document.getElementById('gpay').appendChild(client.createButton({
buttonColor: config.buttonColorLight,
buttonType: 'pay',
onClick: () => client.loadPaymentData(config.paymentDataRequest).then(data =>
fetch('/pay-with-google', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({
token: data.paymentMethodData.tokenizationData.token,
billing: data.paymentMethodData.info.billingAddress,
email: data.email }) }))
}));
});
}
</script>
Tip
Send the token to your server at once: a token expires. Never send it, or the sheet's data, anywhere else, and never call Payment/PayWithGoogle from the browser: it needs your MerchantKey.
Step 3: Payment/PayWithGoogle
Call it from your server with your MerchantKey. The fields are those of Payment/PayWithApple below, with token set to the token string of the sheet. The response is the same.
Apple Pay
Step 1: the config
response of Payment/WalletConfig for ApplePay:
{
"wallet": "ApplePay",
"paymentRequest": {
"countryCode": "MT",
"currencyCode": "EUR",
"supportedNetworks": ["visa", "masterCard"],
"merchantCapabilities": ["supports3DS"],
"requiredBillingContactFields": ["name", "email"],
"total": { "label": "Test Shop Ltd", "amount": "10.50" }
}
}
paymentRequest is what to give to new ApplePaySession(3, paymentRequest). requiredBillingContactFields adds postalAddress and phone when you ask for them. A currency that Apple has no number for is refused (PAB04).
Step 2: show the sheet and have the session validated
Show Apple's button only where window.ApplePaySession exists and ApplePaySession.canMakePayments() is true (Safari, and other browsers on Apple devices). When the sheet opens Apple gives the page a validation url: your server sends it to Payment/ApplePayValidate and the page hands the answer back to the sheet. When the payer authorises, the page sends event.payment.token.paymentData, as a json string, to your server.
<button id="apple-pay" style="-webkit-appearance: -apple-pay-button; -apple-pay-button-type: buy; height: 44px; width: 100%" hidden></button>
<script>
// config is the response of Payment/WalletConfig, given to the page by your server
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
const button = document.getElementById('apple-pay');
button.hidden = false;
button.onclick = () => {
const session = new ApplePaySession(3, config.paymentRequest);
session.onvalidatemerchant = async event => {
// your server calls Payment/ApplePayValidate and returns Apple's answer unchanged
const r = await fetch('/apple-pay-session', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ validationUrl: event.validationURL, domain: location.hostname }) });
if (r.ok) session.completeMerchantValidation(await r.json()); else session.abort();
};
session.onpaymentauthorized = async event => {
// your server calls Payment/PayWithApple
const r = await fetch('/pay-with-apple', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: JSON.stringify(event.payment.token.paymentData), billingContact: event.payment.billingContact }) });
const result = r.ok ? await r.json() : null;
session.completePayment(result && result.approved ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE);
};
session.begin();
};
}
</script>
Payment/ApplePayValidate
Call it from your server with your MerchantKey.
request:
{
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"validationUrl": "https://apple-pay-gateway.apple.com/paymentservices/startSession",
"domain": "shop.example.com"
}
validationUrl: the url the sheet gave. Only Apple's own gateways are called (WAL06otherwise).domain: the website the button is on (for an iframe, the top-level website). It must be registered (WAL05otherwise).
The response is Apple's merchant session, a json object that you give to session.completeMerchantValidation unchanged. Apple's answer is not kept.
Payment/ApplePayValidate validates sessions with PayLolly's Apple merchant id. A merchant that has its own Apple Pay configuration validates its own sessions with Apple, from its own server and with its own Merchant Identity certificate: for such a merchant this call is refused with WAL09. See Setting up your own Apple Pay and Google Pay.
Step 3: Payment/PayWithApple
Call it from your server with your MerchantKey.
- Populate
merchantId, andaccountIdif you want to force a particular account. - Populate
currencyandamount: the amount the payer was shown. They must be those of the token (WAL02otherwise). It must be above zero. - Create a
merchantRef: it must be unique (see Sync Payment), suggest a Guid. - Populate
tokenwithpaymentDataof the payment token, as a json string. - Populate
authorisationType:AuthorAuthCapture. Anything else (OCT) is refused withWAL08: a wallet token pays for something, it is not a way to credit a card. - Optionally populate
billingContactwith the billing details of the sheet (name, email, address lines, city, region, postal code, two letter country, phone). They fill whatcardHolderdoes not give. - Optionally populate
cardHolder(reference,name,email,phone,address): what you give wins over the sheet. - Optionally populate
ipAddresswith the payer's address, for the fraud checks. It is never taken from the connection: that is your server. - Optionally
webHooks,descriptorandremarks.
request:
{
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"merchantRef": "9e31818e-407b-48d8-8bbe-eb19fa565733",
"currency": "EUR",
"amount": 10.5,
"authorisationType": "AuthCapture",
"token": "{\"version\":\"EC_v1\",\"data\":\"...\",\"signature\":\"...\",\"header\":{...}}",
"billingContact": {
"givenName": "Cikku",
"familyName": "Fenek",
"emailAddress": "cikku@example.com",
"addressLines": ["1 High St"],
"locality": "Valletta",
"postalCode": "VLT1111",
"countryCode": "MT"
},
"ipAddress": "203.0.113.7"
}
response (the same as a sync payment):
{
"paymentId": "5a1e7d0e-63d7-4f46-a0f5-0ad1e51d2f42",
"merchantId": "dbdab964-82ee-4b09-a26b-68403c31afa7",
"accountId": "81de44cb-4b70-4207-9f5e-54355491a5cc",
"currency": "EUR",
"amount": 10.5,
"cardData": {
"maskedPAN": "400000*0002",
"wallet": "ApplePay"
},
"result": {
"status": "Approved",
"processorId": "eea02b23-9069-4f95-85a3-9a28ad7b97c7",
"message": "Dummy success",
"authCode": "123456"
},
"merchantRef": "9e31818e-407b-48d8-8bbe-eb19fa565733"
}
Payment/PayWithGoogle takes the same fields and answers the same way (cardData.wallet is GooglePay).
A decline is an answer, not an error: the response has result.status Declined. The payment is reported with cardData.wallet by Payment/GetPaymentStatus and on the payment page of the Portal. Tell the sheet the outcome: STATUS_SUCCESS for Approved, STATUS_FAILURE otherwise.
Fraud rules
The fraud rules of your merchant and accounts apply as they do to a sync payment. A rule that reads data a call from your server cannot give, such as the per device rule (it reads the device of the payer's browser), has nothing to read and allows the payment, as it does for every sync payment. The other rules work on what you send: send the payer's ipAddress so the per IP rule can work. The rules about cards see the device card number of the wallet, not the card inside the wallet.
A payment that a fraud rule (or the blacklist) stops is answered as it is for any other payment, and it is stopped before the acquirer is asked: the token is not used up, so the same token can be sent again, for example after the rule's window has passed.
A token pays once
PayLolly does not keep a record of the tokens it has seen. A wallet token pays once because the card network refuses a wallet cryptogram that is used again, and because a merchantRef can be used once.
- Send a new
merchantReffor every payment. A call repeated with the samemerchantRefis refused withVAL07: it does not get the first outcome back. If a call timed out and you do not know what happened, ask for the status of the payment withgetPaymentStatusbymerchantRefbefore you do anything else. - A payment that was stopped before the acquirer was asked (for example
RTE05, no account can take the wallet payment, or a fraud rule) frees itsmerchantRef: put the cause right and send the same token again, if it has not expired. - A payment that fails after the acquirer may have been asked is kept as an error and its
merchantRefstays used.
Errors
| Code | Meaning |
|---|---|
WAL01 |
The token was refused: it is not genuine, has expired, or was not made with what Payment/WalletConfig gave (Google Pay: the paymentDataRequest) |
WAL02 |
The token cannot be used for this payment: not Visa or Mastercard, no cryptogram (a card number without one is accepted for Google Pay in the TEST environment only), or, for Apple Pay, the amount or currency is not that of the request |
WAL03 |
Wallet payments are not available for this merchant (no permission, or the wallet is not set up; with your own configuration of the wallet: it is switched off, not complete or cannot be read) |
WAL04 |
The amount must be above zero |
WAL05 |
Apple Pay: the website is not registered with PayLolly for Apple Pay (Payment/ApplePayValidate) |
WAL06 |
Apple Pay: the validation url is not one of Apple's, or Apple refused the session or did not answer |
WAL07 |
The Wallet API is not available for this merchant (the permission is not switched on) |
WAL08 |
A wallet payment can only be an Auth or an AuthCapture |
WAL09 |
Apple Pay: Payment/ApplePayValidate is not available for this merchant, because it has its own Apple Pay configuration: validate the session on your own server. The usual checks come first (WAL07, WAL03 without the Apple Pay permission, RTE05), so WAL09 is returned only once they pass |
EPR01 |
The acquirer asked for the payer's browser, which it should not for a wallet payment. The payment is kept as an error and the token stays used |
RTE05 |
None of your accounts can take this wallet payment: the account's processor does not take the wallet, or the request has something that account's processor does not support (for example a descriptor), or the currency does not match. Today only simulated (test) accounts take wallet payments |
VAL07 |
The merchantRef has already been used |
See Errors for the format of an error response.
Testing
- Google Pay: use
"environment": "TEST"(the default outside production) and Google's test cards in the sheet. They arrive as a card number without a cryptogram, which is accepted in TEST only. - Apple Pay: the sheet needs Safari (or another browser on an Apple device), a website registered with Apple for the Apple merchant id in use (PayLolly's, or your own), and in development Apple's sandbox. Ask PayLolly whether the sandbox gateways are switched on for the environment you test in.
- Ask PayLolly for a merchant account on the Dummy processor that takes wallets. It answers at once. The result is decided by the cents of the amount, as for the test cards:
.01declines,.02is an error, anything else is approved. The card the sheet gives does not matter. - Check the outcome with
Payment/GetPaymentStatus: it showscardData.wallet.
Your own Apple Pay and Google Pay registration
By default every merchant uses PayLolly's Google Pay and Apple Pay registrations, as described above. A merchant that shows the wallet on its own page can instead use its own Apple merchant id and Google merchant id: PayLolly then only decrypts the tokens and takes the payments, and for Apple Pay you validate the sessions on your own server (Payment/ApplePayValidate answers WAL09). The other calls on this page do not change. The hosted payment page always uses PayLolly's registrations.
See Setting up your own Apple Pay and Google Pay for which of the three paths you need, and how to set up the third.