An Apple Pay payment is a card payment. The buyer approves the payment on their device, your app or page sends Apple's payment data to Fluveo, and Fluveo turns it into a single-use card token. From there you pay, capture, refund and read events exactly as for any card. Every store uses the same calls.
The flow has four calls:
- Check that Apple Pay is on in your payment method configuration.
- Register each website where the Apple Pay button shows, as a payment method domain.
- Send the Apple Pay payment data to
POST /v1/tokensand get atok_back. - Pay with the token: make a PaymentMethod from it, or pass it straight to the PaymentIntent.
Turn Apple Pay on#
Your store has one payment method configuration. List it to find its id and see the switches. A new store has Apple Pay on and Google Pay off. Card stays on while either wallet is on.
curl https://api.fluveo.com/v1/payment_method_configurations \
-H "Authorization: Bearer sk_test_..."To turn Apple Pay on, or off, update that configuration with apple_pay[display_preference][preference] set to on or off.
curl https://api.fluveo.com/v1/payment_method_configurations/pmc_... \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: pmc-apple-pay-on" \
-d "apple_pay[display_preference][preference]=on"The answer is the configuration. Apple Pay on reads like this:
{
"apple_pay": {
"available": true,
"display_preference": {
"overridable": null,
"preference": "on",
"value": "on"
}
}
}Turned off, available is false and both preference and value are off. The switch says whether a checkout may show the Apple Pay button. A payment you make through the API with a token is not refused when the switch is off. Every parameter is on the configuration update.
Register your website#
A wallet button shows only on a website your store has registered. Create one payment method domain per host name, with the bare host: no scheme, port or path.
curl https://api.fluveo.com/v1/payment_method_domains \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: pmd-shop-example" \
-d domain_name=shop.example.comThe domain shows a status for each wallet. With a test key, apple_pay and google_pay are active on a new domain:
{
"id": "pmd_1Qx7bIcDeFgHiJkLmNoPqRsT",
"object": "payment_method_domain",
"domain_name": "shop.example.com",
"enabled": true,
"livemode": false,
"apple_pay": { "status": "active" },
"google_pay": { "status": "active" }
}When a wallet reads inactive, its status_details.error_message says why. Fix what it names, then ask again with POST /v1/payment_method_domains/{id}/validate, which returns the domain with each wallet's new status. POST /v1/payment_method_domains/{id} with enabled=false turns a domain off and enabled=true turns it back on. A domain's status does not turn a wallet on: the configuration's switch does. A store keeps at most 100 domains in each mode. See the domain calls.
Create a token#
When the buyer approves the payment, Apple Pay gives your app or page the payment data (PKPaymentToken's paymentData) and a few facts about the card. Send them to POST /v1/tokens:
Apple's payment data as the device returned it, as JSON. Fluveo reads it once to make the token and never stores it.
The card's name as Apple Pay shows it, such as
Visa 1881.The card network as Apple Pay names it, such as
VisaorMasterCard.Apple Pay's transaction identifier. Each one makes one token. See Test it for its test form.
curl https://api.fluveo.com/v1/tokens \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order-3001-token" \
--data-urlencode 'pk_token={"version":"EC_v1","data":"...","signature":"...","header":{...}}' \
--data-urlencode "pk_token_instrument_name=Visa 1881" \
--data-urlencode pk_token_payment_network=Visa \
--data-urlencode pk_token_transaction_id=2f1c...e9The answer is a token whose card names Apple Pay. used is false until a PaymentMethod takes it. GET /v1/tokens/{id} reads it again.
{
"id": "tok_1Qx7cJdEfGhIjKlMnOpQrStU",
"object": "token",
"type": "card",
"used": false,
"livemode": false,
"card": {
"brand": "Visa",
"last4": "4242",
"dynamic_last4": "4242",
"tokenization_method": "apple_pay",
"wallet": {
"apple_pay": { "type": "apple_pay" },
"type": "apple_pay"
}
}
}The create takes only these four parameters. A card number in any of them is refused with a 400 whose param names that parameter, and no token is made. Every parameter is on the token create.
Pay with the token#
Pick one of two ways. Both make a card PaymentMethod from the token.
Make the PaymentMethod first with card[token], then pay with it like any card:
curl https://api.fluveo.com/v1/payment_methods \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order-3001-pm" \
-d type=card \
-d "card[token]=tok_1Qx7cJdEfGhIjKlMnOpQrStU"
curl https://api.fluveo.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order-3001-pay" \
-d amount=1000 \
-d currency=usd \
-d "payment_method_types[]=card" \
-d payment_method=pm_1Qx7dKeFgHiJkLmNoPqRsTuV \
-d confirm=trueOr pass the token to the PaymentIntent in payment_method_data[card][token], and Fluveo makes the PaymentMethod for you:
curl https://api.fluveo.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order-3002-pay" \
-d amount=1000 \
-d currency=usd \
-d "payment_method_types[]=card" \
-d "payment_method_data[type]=card" \
-d "payment_method_data[card][token]=tok_1Qx7eLfGhIjKlMnOpQrStUvW" \
-d confirm=true1000 is $10.00, in the smallest unit of the currency. Send nothing else of a card beside the token: card[number] next to card[token] is a 400 parameter_unknown on card[number]. The payment then moves as any card payment does. capture_method=manual holds it for a later capture, and you fulfil on payment_intent.succeeded. See Cards.
What comes back#
The card shows its wallet in card.wallet on the PaymentMethod, and in payment_method_details.card.wallet on the Charge and in the charge.succeeded event:
{
"apple_pay": { "type": "apple_pay" },
"dynamic_last4": "4242",
"type": "apple_pay"
}last4 is the last four digits of the buyer's card. dynamic_last4 is the last four of the number the device used in its place. A card paid without a wallet shows wallet as null.
A token pays once#
A token makes one PaymentMethod. Sending it a second time, in card[token] or payment_method_data[card][token], is a 400 with code token_already_used on param token, and the token reads used: true.
The PaymentMethod made from it also pays once. Confirming another PaymentIntent with it is a 400 with no code and no param, whose message starts with "The provided PaymentMethod was previously used with a PaymentIntent without Customer attachment" and ends with "It may not be used again. To use a PaymentMethod multiple times, you must attach it to a Customer first." For the buyer's next purchase, ask Apple Pay for new payment data and make a new token.
Refunds#
Refund an Apple Pay payment as you refund any card payment: POST /v1/refunds with the payment_intent and, for part of it, an amount. See Refund a payment.
Test it#
With a test key, you need no device. Put a test form in pk_token_transaction_id and Fluveo makes the token of a test card, whatever pk_token holds:
ApplePayStubs~<test card number>~<amount>~<currency>~<anything>curl https://api.fluveo.com/v1/tokens \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: test-token-order-abc" \
--data-urlencode 'pk_token={"version":"EC_v1","data":"","signature":"","header":{}}' \
--data-urlencode "pk_token_instrument_name=Simulated Instrument" \
--data-urlencode pk_token_payment_network=Visa \
--data-urlencode "pk_token_transaction_id=ApplePayStubs~4242424242424242~1000~USD~order-abc"The token's card is the test card, and its dynamic_last4 is the card number's last four, 4242 here. Each transaction id makes one token, so give each test token a new last part. Write that part in letters: a run of digits there that forms a valid card number is refused. The test form works only with a test key.
Errors#
Every refusal below is a 400 with type invalid_request_error.
| When | code | param | message |
|---|---|---|---|
pk_token is not Apple Pay payment data Fluveo can read | none | none | "The Apple Pay payment data isn't formatted correctly. Create new payment data using a current Fluveo mobile SDK: https://docs.fluveo.com/apple-pay" |
A second token from the same pk_token_transaction_id | none | none | "This transaction approval has already been used to create a token." |
pk_token_payment_network sent empty | parameter_invalid_empty | pk_token_payment_network | "You passed an empty string for 'pk_token_payment_network'. We assume empty values are an attempt to unset a parameter; however 'pk_token_payment_network' cannot be unset. You should remove 'pk_token_payment_network' from your request or supply a non-empty value." |
| A token sent a second time | token_already_used | token | "The token tok_... has already been used. Each token can only be used once to create a source." |
card[token] or payment_method_data[card][token] names no token of your store | none | token | "Invalid token id: tok_..." |
GET /v1/tokens/{id} of a token your store does not have | resource_missing | token | "No such token: 'tok_...'" |
| A PaymentMethod made from a token, used again | none | none | "The provided PaymentMethod was previously used with a PaymentIntent without Customer attachment, ..." (see A token pays once) |
| Card turned off while Apple Pay or Google Pay is on | none | none | "Invalid configuration. Apple Pay and Google Pay require Card to be enabled." |
| A 101st domain in one mode | none | none | "You have reached the maximum of 100 payment method domains." |
Make a new token from new Apple Pay data after any of the token errors. See Errors for the error object.
Google Pay#
Google Pay has its own switch, google_pay[display_preference][preference] on the same configuration, off on a new store. Each domain shows its own google_pay status. Tokens read only Apple Pay data: POST /v1/tokens takes pk_token and nothing that carries Google Pay's.
Next#
- Cards, for how a card payment moves, 3D Secure and declines.
- Refund a payment
- Testing
- The wallets reference lists every parameter of the calls on this page.