Skip to main content
Apple Pay and Google Pay let a customer pay with a card kept on their device. Instead of typing a number, they confirm with Face ID, Touch ID, a fingerprint or the passcode. The wallet hands over a token encrypted for Payra’s processor, so your page never sees card data and Payra never reads the token. The result is an ordinary card payment with the same declines, 3-D Secure, refunds and webhooks. Only card.wallet tells it apart. Payra enables wallets per workspace. They work through Payra Elements on your own page and on the hosted checkout. The first part of this page applies to both wallets. Google Pay and Apple Pay cover what is specific to each.

Before you start

  • Keep a card form on the page. Elements draws a wallet’s button only where the device can pay with it. A device that cannot pay with one wallet may pay with the other, so offer both and show what comes back available, as the hosted checkout does.
  • A wallet already holds the customer’s saved cards. Apple Pay and Google Pay keep them on the customer’s device and account, so a returning customer pays by tapping the button again. A wallet card pays once: the charge refuses save_payment_method, and POST /payment-methods refuses usage: "saved". To save a card, collect it with a card session.
  • The amount is fixed when the session is created. The sheet (the wallet’s own payment screen) shows it, the customer confirms it, and the charge must be for exactly that amount and currency.

Accept a wallet payment

1

Create a session for each wallet

One session collects one wallet and carries the amount its sheet shows, in the smallest unit of currency (USD when omitted, or CAD). To show both buttons, create two sessions.
Response (abbreviated)
apple_pay works the same way. Three things are refused before any page opens: a session without amount (400 parameter_invalid on amount), a currency other than USD or CAD (400 parameter_invalid on currency), and a wallet your workspace does not offer (400 payment_provider_not_configured on payment_method_types). Hand the page each session’s client_secret and nothing more.
2

Mount the buttons

Each wallet gets its own elements() call, with its own session.
There is no form to submit. The customer confirms in the sheet, which shows the amount and your business name. Elements then confirms the session with the wallet’s token and fires confirmed, the moment to charge. After a decline the page swaps the spent session for a new one, since a session pays once.
3

Charge on your server

Send the session as payment_method_session, with its amount.
Response (abbreviated)
The payment answers succeeded, a decline, or requires_action, as a typed card does. An amount or currency other than the session’s is 400 parameter_invalid on amount, and nothing reaches the processor.A session has two 30-minute windows. The customer has 30 minutes from its creation to pay in the sheet, and you have 30 minutes from confirmed to charge it. A later charge answers payment_method_session_inactive. A session pays once, approved or declined, and a second charge answers payment_method_session_used.requires_action means the card’s bank wants to verify the customer. Send return_url on every wallet charge: on a workspace that offers wallets it always takes effect, and without it the customer cannot be sent to the bank. handleNextAction follows the payment’s redirect, and the customer comes back to your return_url. The rest of 3-D Secure applies.

The button

Each wallet draws its own button with Google’s and Apple’s own libraries, so it always matches their brand rules.
The button fills the width of the container you mount into, so size the container. For anything the theme does not cover, the container has the classes PayraElement and PayraElement--wallet, and the button has PayraElement-wallet-button plus PayraElement-wallet-button--google_pay or --apple_pay. Place the buttons where the customer expects an express option, above or beside the card fields, and follow Apple’s and Google’s guidelines for the space around them.

Events and errors

payra.confirmPaymentMethodSession(elements) is not needed with a wallet. On a wallet element it submits nothing, and answers the session confirmed carried, or wallet_not_authorized before the customer pays. Apple Pay adds three codes of its own. Errors from the charge are under Payments.

What you read back

The payment, the payment method it made and every payment.* webhook carry the card as the wallet disclosed it:
exp_month and exp_year are always null on a wallet card. brand and last4 are null only when the wallet did not disclose them. billing_details.name is null, since a wallet does not hand over the name. Read wallet as an open string, since the list may grow. Refund a wallet payment as any card payment.

On the hosted checkout

When your workspace offers wallets and a checkout session offers card, the page shows the wallet buttons above the payment form, Apple Pay first, and only those the customer’s device can pay with. There is nothing to send. payment_method_types never lists wallets, and a checkout without card shows none. The customer taps, confirms on the device, and the page charges and finishes as with a card. The customer lands on success_url with ?checkout_session=cs_…, the session’s payment carries card.wallet, and checkout_session.completed and payment.succeeded are sent. A refused wallet payment shows its reason under the buttons, and the customer can tap again or pay with the form.

Testing

In sandbox the sheets are real and no money moves. Test Google Pay and Test Apple Pay say how to pay in each wallet’s test environment. Check the payment for card.wallet, and that payment.succeeded reached you. A wallet never asks for a name, so the name-on-card refusals on Testing do not apply. Try your decline path with a typed card.

Content Security Policy

If your page sets one, add https://pay.google.com (Google Pay) and https://applepay.cdn-apple.com (Apple Pay) to script-src, on top of what Elements needs. Then try one payment with each wallet under your policy. A blocked host shows in the browser console as a Content Security Policy violation.