Save Cards During Purchase

CurrentLast updated: September 6th 2023, @ 3:43:42 pm


After customers save their credit or debit card, they can select it for faster checkout. Customers won't have to enter payment details for future transactions.

Use the JavaScript SDK to save a payer's card if you aren't PCI Compliant - SAQ A but want to save credit or debit cards during checkout.

Know before you code

How it works

PayPal encrypts payment method information and stores it in a digital vault for that customer.

  1. The payer saves their payment method.
  2. For a first-time payer, PayPal creates a customer ID. Store this within your system for future use.
  3. When the customer returns to your website and is ready to check out, pass their PayPal-generated customer ID to the JavaScript SDK. The customer ID tells the JavaScript SDK to save or reuse a saved payment method.
  4. The payer completes a billing agreement.
  5. The JavaScript SDK populates the checkout page with each saved payment method. Each payment method appears as a one-click button next to other ways to pay.

The checkout process is now shorter because it uses saved payment information.

Use cases

Businesses save payment methods if they want customers to:

  • Check out without re-entering a payment method
  • Pay after use, for example, ride-sharing and food delivery

1. Set up sandbox to save payment methods

Set up your sandbox and live business accounts to save payment methods:

  1. Log in to the Developer Dashboard.
  2. Under REST API apps, select your app name.
  3. Under Sandbox App Settings > App Feature Options, check Accept payments.
  4. Expand Advanced options. Confirm that Vault is selected.

2. Add checkbox for payers to save card

Add a checkbox element grouped with your card collection fields to give payers the option to save their card.

1<div>
2 <input type="checkbox" id="save" name="save">
3 <label for="save">Save your card</label>
4</div>

3. Pass checkbox value

Pass document.getElementById("save").checked to your server with the following details in the createOrder() method:

  • Value of the checkbox
  • Optional: Card name
  • Optional: Billing address

1const cardFields = paypal.CardFields({
2 createOrder: (data) => {
3 // Create the order on your server and return the order ID
4 const saveCheckbox = document.getElementById("save");
5 return fetch("/api/paypal/order/create/", {
6 method: "POST",
7 body: JSON.stringify({
8 // Include the saveCheckbox.checked value
9 // Optionally, include the card name and billing address
10 }),
11 }).then((res) => {
12 return res.json();
13 }).then((orderData) => {
14 // Return the order ID that was created on your server
15 return orderData.id;
16 });
17 },
18 onApprove: function (data) {
19 // Authorize or capture the order on your server
20 const { orderID } = data;
21 return fetch('/api/paypal/orders/${orderID}/capture/', {
22 method: "POST"
23 }).then((res) => {
24 return res.json();
25 }).then((orderData) => {
26 // Retrieve vault details from the response
27 const vault = orderData?.paymentSource?.card?.attributes?.vault;
28 if (vault) {
29 // Save the vault.id and vault.customer.id for future use
30 }
31 // Handle successful transaction
32 });
33 },
34 onError: function (error) {
35 // Handle any error that occurs
36 }
37 });
38 if (cardFields.isEligible()) {
39 cardFields.NameField().render("#card-name-field-container");
40 cardFields.NumberField().render("#card-number-field-container");
41 cardFields.ExpiryField().render("#card-expiry-field-container");
42 cardFields.CVVField().render("#card-cvv-field-container");
43 } else {
44 // Handle workflow when credit and debit cards aren't available
45 }
46 const submitButton = document.getElementById("submit-button");
47 submitButton.addEventListener("click", () => {
48 cardFields.submit().then(() => {
49 // Handle successful transaction
50 }).catch((error) => {
51 // Handle any error that occurs
52 });
53 });

4. Create order and save card

Server side

Set up your server to call the Create Order API. The button that the payer selects determines the payment_source sent in the following sample.

This SDK uses the Orders v2 API to save payment methods in the background. Use the following request using the Orders API to add attributes needed to save a card.

  1. Merchant
  2. Platform

Save payment method for first-time payers

This request is for payers who:

  • Don't have a payment source saved into the vault.
  • Selected the save checkbox. The document.querySelector('#save').checked value is true.

Note: In the following requests, the payment_source.attributes.vault.store_in_vault with the value ON_SUCCESS means the card is saved with a successful authorization or capture.

1curl -v -X POST 'https://api-m.sandbox.paypal.com/v2/checkout/orders' \
2 -H 'Content-Type: application/json' \
3 -H 'Authorization: Bearer ACCESS-TOKEN' \
4 -H 'PayPal-Partner-Attribution-ID: BN-CODE' \
5 -H 'PayPal-Auth-Assertion: AUTH-ASSERTION-TOKEN' \
6 -d '{
7 "intent": "CAPTURE",
8 "purchase_units": [{
9 "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
10 "amount": {
11 "currency_code": "USD",
12 "value": "100.00"
13 }
14 }],
15 "payment_source": {
16 "card": {
17 "name": "Firstname Lastname",
18 "billing_address": {
19 "address_line_1": "123 Main Street",
20 "address_line_2": "Unit B",
21 "admin_area_2": "Anytown",
22 "admin_area_1": "CA",
23 "postal_code": "12345",
24 "country_code": "US"
25 },
26 "attributes": {
27 "vault": {
28 "store_in_vault": "ON_SUCCESS"
29 }
30 }
31 }
32 }
33 }

Response

Pass the order.id to the JavaScript SDK. The SDK updates the order with the new card details. PayPal handles any PCI compliance issues.

Note: The request to save the payment method is made when the order is created through payment_source.attributes.vault.store_in_vault. Vault details are available only after an order is authorized or captured.

1{
2 "id": "5O190127TN364715T",
3 "status": "CREATED",
4 "intent": "CAPTURE",
5 "payment_source": {
6 "card": {
7 "brand": "VISA",
8 "last-digits": "1881",
9 "billing_address": {
10 "address_line_1": "123 Main Street",
11 "address_line_2": "Unit B",
12 "admin_area_2": "Anytown",
13 "admin_area_1": "CA",
14 "postal_code": "12345",
15 "country_code": "US"
16 }
17 }
18 },
19 "purchase_units": [{
20 "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
21 "amount": {
22 "currency_code": "USD",
23 "value": "100.00"
24 }
25 }],
26 "create_time": "2021-10-28T21:18:49Z",
27 "links": [{
28 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
29 "rel": "self",
30 "method": "GET"
31 },
32 {
33 "href": "https://www.sandbox.paypal.com/checkoutnow?token=5O190127TN364715T",
34 "rel": "approve",
35 "method": "GET"
36 },
37 {
38 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
39 "rel": "update",
40 "method": "PATCH"
41 },
42 {
43 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T/capture",
44 "rel": "capture",
45 "method": "POST"
46 }
47 ]
48 }

5. Authorize or capture order and save card

Server side

Set up your server to call the v2 Orders API:

Authorize order request

  1. Platform
  2. Merchant
1curl -v -X POST 'https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T/authorize' \
2 -H 'Content-Type: application/json' \
3 -H 'Authorization: Bearer ACCESS-TOKEN' \
4 -H 'PayPal-Partner-Attribution-ID: BN-CODE' \
5 -d '{}'

Capture order request

  1. Platform
  2. Merchant
1curl -v -X POST 'https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T/capture' \
2 -H 'Content-Type: application/json' \
3 -H 'Authorization: Bearer ACCESS-TOKEN' \
4 -H 'PayPal-Partner-Attribution-ID: BN-CODE' \
5 -d '{}'

Response

1{
2 "id": "6XH94174N5355381V",
3 "intent": "CAPTURE",
4 "status": "COMPLETED",
5 "payment_source": {
6 "card": {
7 "last_digits": "1881",
8 "brand": "VISA",
9 "type": "UNKNOWN",
10 "attributes": {
11 "vault": {
12 "id": "1j4a89ge",
13 "status": "VAULTED",
14 "customer": {
15 "id": "100161227"
16 },
17 "links": [
18 {
19 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/1j4a89ge",
20 "rel": "self",
21 "method": "GET"
22 },
23 {
24 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/1j4a89ge",
25 "rel": "delete",
26 "method": "DELETE"
27 },
28 {
29 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/6XH94174N5355381V",
30 "rel": "up",
31 "method": "GET"
32 }
33 ]
34 }
35 }
36 }
37 },
38 "purchase_units": [
39 {
40 "reference_id": "5a69b6e3-1916-4269-87b9-358516da5102",
41 "amount": {
42 "currency_code": "USD",
43 "value": "101.00"
44 },
45 "payee": {
46 "email_address": "[email protected]",
47 "merchant_id": "BADP95SC2GDE6"
48 },
49 "soft_descriptor": "PP*TEST STORE",
50 "payments": {
51 "captures": [
52 {
53 "id": "9J594045WV338432T",
54 "status": "COMPLETED",
55 "amount": {
56 "currency_code": "USD",
57 "value": "101.00"
58 },
59 "final_capture": true,
60 "disbursement_mode": "INSTANT",
61 "seller_protection": {
62 "status": "NOT_ELIGIBLE"
63 },
64 "seller_receivable_breakdown": {
65 "gross_amount": {
66 "currency_code": "USD",
67 "value": "101.00"
68 },
69 "paypal_fee": {
70 "currency_code": "USD",
71 "value": "3.11"
72 },
73 "net_amount": {
74 "currency_code": "USD",
75 "value": "97.89"
76 }
77 },
78 "links": [
79 {
80 "href": "https://api-m.sandbox.paypal.com/v2/payments/captures/9J594045WV338432T",
81 "rel": "self",
82 "method": "GET"
83 },
84 {
85 "href": "https://api-m.sandbox.paypal.com/v2/payments/captures/9J594045WV338432T/refund",
86 "rel": "refund",
87 "method": "POST"
88 },
89 {
90 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/6XH94174N5355381V",
91 "rel": "up",
92 "method": "GET"
93 }
94 ],
95 "create_time": "2022-09-23T20:17:31Z",
96 "update_time": "2022-09-23T20:17:31Z",
97 "processor_response": {
98 "avs_code": "A",
99 "cvv_code": "M",
100 "response_code": "0000"
101 }
102 }
103 ]
104 }
105 }
106 ],
107 "create_time": "2022-09-23T20:17:31Z",
108 "update_time": "2022-09-23T20:17:31Z",
109 "links": [
110 {
111 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/6XH94174N5355381V",
112 "rel": "self",
113 "method": "GET"
114 }
115 ]
116}

In the response from the Authorize or Capture request, the Orders v2 API interacts with the Payment Method Tokens v3 API to save the card.

The payment_source.card.attributes.vault stores the card information as the vault.id, which can be used for future payments when the vault.status is VAULTED.

Save approved payment source

If the payment has been authorized or captured, the payer does not need to be present to save a payment_source. To keep checkout times as short as possible, the Orders API responds as soon as payment is captured.

If the attributes.vault.status returned after payment is APPROVED, you won't have a vault.id yet. An example of the attributes object from this scenario is in the following sample:

1"attributes": {
2 "vault": {
3 "status": "APPROVED",
4 "links": [
5 {
6 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/5O190127TN364715T",
7 "rel": "up",
8 "method": "GET"
9 }
10 ]
11 }
12 }

The Payment Method Tokens API still saves the payment source even after the Orders API returns its response and sends a webhook after the payment source is saved.

In order to retrieve a vault_id when an APPROVED status is returned, you'll need to subscribe to the VAULT.PAYMENT-TOKEN.CREATED webhook.

The Payment Method Tokens API sends a webhook after the payment source is saved. An example of the VAULT.PAYMENT-TOKEN.CREATED webhook payload is shown in the following sample:

1{
2 "id":"WH-1KN88282901968003-82E75604WM969463F",
3 "event_version":"1.0",
4 "create_time":"2022-08-15T14:13:48.978Z",
5 "resource_type":"payment_token",
6 "resource_version":"3.0",
7 "event_type":"VAULT.PAYMENT-TOKEN.CREATED",
8 "summary":"A payment token has been created.",
9 "resource":{
10 "time_created":"2022-08-15T07:13:48.964PDT",
11 "links":[
12 {
13 "href":"https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/9n6724m",
14 "rel":"self",
15 "method":"GET",
16 "encType":"application/json"
17 },
18 {
19 "href":"https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/9n6724m",
20 "rel":"delete",
21 "method":"DELETE",
22 "encType":"application/json"
23 }
24 ],
25 "id":"nkq2y9g",
26 "payment_source":{
27 "card":{
28 "last_digits":"1111",
29 "brand":"VISA",
30 "expiry":"2027-02",
31 "billing_address":{
32 "address_line_1":"123 Main Street",
33 "address_line_2":"Unit B",
34 "admin_area_2":"Anytown",
35 "admin_area_1":"CA",
36 "postal_code":"12345",
37 "country_code":"US"
38 }
39 }
40 },
41 "customer":{
42 "id":"695922590"
43 }
44 },
45 "links":[
46 {
47 "href":"https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/WH-1KN88282901968003-82E75604WM969463F",
48 "rel":"self",
49 "method":"GET"
50 },
51 {
52 "href":"https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/WH-1KN88282901968003-82E75604WM969463F/resend",
53 "rel":"resend",
54 "method":"POST"
55 }
56 ]
57 }

In this example, the resource.id field is the vault ID, and resource.customer.id is the PayPal-generated customer ID.

Payment processor codes

Payment processors return the following codes when they receive a transaction request. For advanced card payments, the code displays in the authorization object under the response_code field.

The following sample shows the processor response codes returned in an authorization (avs_code) and capture call (cvv_code) response:

1"processor_response": {
2 "avs_code": "Y",
3 "cvv_code": "S",
4 "response_code": "0000"
5 }

See the Orders API response_code object to get processor response codes for non-PayPal payment processor errors.

6. Pay with saved payment methods

When a payer returns to your site, you can show the payer's saved payment methods with the Payment Method Tokens API.

Create UI

Create your own UI to show saved payment methods for returning payers.

List all saved payment methods

Make the server-side list all payment tokens API call to retrieve payment methods saved to a payer's PayPal-generated customer ID. Based on this list, you can show all saved payment methods to a payer to select during checkout.

  1. Merchant
  2. Platform

Sample request: list all payment tokens

1curl -L -X GET 'https://api-m.sandbox.paypal.com/v3/vault/payment-tokens?customer_id=CUSTOMER-ID' \
2 -H 'Content-Type: application/json' \
3 -H 'Accept-Language: en_US' \
4 -H 'PayPal-Auth-Assertion: AUTH-ASSERTION-TOKEN' \
5 -H 'Authorization: Bearer ACCESS-TOKEN'

Sample response: list all payment tokens

1{
2 "customer": {
3 "id": "100161227"
4 },
5 "payment_tokens": [
6 {
7 "id": "1j4a89ge",
8 "customer": {
9 "id": "100161227"
10 },
11 "payment_source": {
12 "card": {
13 "last_digits": "1881",
14 "brand": "VISA",
15 "expiry": "2027-04",
16 "billing_address": {
17 "address_line_1": "123 Main Street",
18 "address_line_2": "Unit B",
19 "admin_area_2": "Anytown",
20 "admin_area_1": "CA",
21 "postal_code": "12345",
22 "country_code": "US"
23 },
24 "verification_status": "NOT_VERIFIED"
25 }
26 },
27 "links": [
28 {
29 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/1j4a89ge",
30 "rel": "self",
31 "method": "GET",
32 "encType": "application/json"
33 },
34 {
35 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/1j4a89ge",
36 "rel": "delete",
37 "method": "DELETE",
38 "encType": "application/json"
39 }
40 ]
41 }
42 ],
43 "links": [
44 {
45 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens?pageNumber=1&totalRequired=false&customer_id=100161227&pageSizeInternal=5",
46 "rel": "self",
47 "method": "GET",
48 "encType": "application/json"
49 },
50 {
51 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens?pageNumber=1&totalRequired=false&customer_id=100161227&pageSizeInternal=5",
52 "rel": "first",
53 "method": "GET",
54 "encType": "application/json"
55 },
56 {
57 "href": "https://api-m.sandbox.paypal.com/v3/vault/payment-tokens?pageNumber=1&totalRequired=false&customer_id=100161227&pageSizeInternal=5",
58 "rel": "last",
59 "method": "GET",
60 "encType": "application/json"
61 }
62 ]
63}

Show saved card to payer

Display the saved card to the payer and use the Orders API to make another transaction. Use the vault ID the payer selects as an input to the Orders API to capture the payment.

Use supported CSS properties to style the card fields.

Sample request: create order API with payment tokens

  1. Platform
  2. Merchant
1curl -L -X POST 'https://api-m.sandbox.paypal.com/v2/checkout/orders' \
2 -H 'Accept: application/json' \
3 -H 'Content-Type: application/json' \
4 -H 'Accept-Language: en_US' \
5 -H 'PayPal-Partner-Attribution-Id: BN_CODE ' \
6 -H 'PayPal-Request-Id: PAYPAL-REQUEST-ID ' \
7 -H 'Authorization: Bearer ACCESS-TOKEN' {
8 "intent": "CAPTURE",
9 "purchase_units": [
10 {
11 "amount": {
12 "currency_code": "USD",
13 "value": "50.00"
14 },
15 "payee": {
16 "merchant_id": "MERCHANT-ID"
17 }
18 }
19 ],
20 "payment_source": {
21 "card": {
22 "vault_id":"1j4a89ge"
23 }
24 }
25 }'

Sample response: create order API with payment tokens

1{
2 "id": "3W828367JC8220640",
3 "status": "COMPLETED",
4 "payment_source": {
5 "card": {
6 "last_digits": "1881",
7 "expiry": "2027-04",
8 "brand": "VISA",
9 "type": "UNKNOWN"
10 }
11 },
12 "purchase_units": [
13 {
14 "reference_id": "default",
15 "payments": {
16 "captures": [
17 {
18 "id": "65L4242534916851F",
19 "status": "COMPLETED",
20 "amount": {
21 "currency_code": "USD",
22 "value": "50.00"
23 },
24 "final_capture": true,
25 "disbursement_mode": "INSTANT",
26 "seller_protection": {
27 "status": "NOT_ELIGIBLE"
28 },
29 "seller_receivable_breakdown": {
30 "gross_amount": {
31 "currency_code": "USD",
32 "value": "50.00"
33 },
34 "paypal_fee": {
35 "currency_code": "USD",
36 "value": "1.79"
37 },
38 "net_amount": {
39 "currency_code": "USD",
40 "value": "48.21"
41 }
42 },
43 "links": [
44 {
45 "href": "https://api-m.sandbox.paypal.com/v2/payments/captures/65L4242534916851F",
46 "rel": "self",
47 "method": "GET"
48 },
49 {
50 "href": "https://api-m.sandbox.paypal.com/v2/payments/captures/65L4242534916851F/refund",
51 "rel": "refund",
52 "method": "POST"
53 },
54 {
55 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/3W828367JC8220640",
56 "rel": "up",
57 "method": "GET"
58 }
59 ],
60 "create_time": "2022-10-14T23:25:36Z",
61 "update_time": "2022-10-14T23:25:36Z",
62 "processor_response": {
63 "avs_code": "A",
64 "cvv_code": "M",
65 "response_code": "0000"
66 }
67 }
68 ]
69 }
70 }
71 ],
72 "links": [
73 {
74 "href": "https://api-m.sandbox.paypal.com/v2/checkout/orders/3W828367JC8220640",
75 "rel": "self",
76 "method": "GET"
77 }
78 ]
79}

7. Test your integration

Test your integration in the PayPal sandbox.

  1. Copy the sample request code.
  2. Change 'ACCESS_TOKEN' to your access token.

Save payment method

  1. On the checkout page, enter the card information and select the option to save the card. You can use test card numbers for testing.
  2. Capture the transaction.
  3. Log in to sandbox with your merchant account and verify the transaction.

Pay with a saved payment method

  1. Use the list all payment tokens API to retrieve all the payment methods saved for the customer.
  2. Capture the payment by passing the payer-selected vault_id to the Orders API.
  3. Log in to the sandbox with your merchant account and verify the transaction.

Test cards

Use the following card numbers to test transactions in the sandbox:

Test numberCard type
371449635398431American Express
376680816376961American Express
36259600000004Diners Club
6304000000000000Maestro
5063516945005047Maestro
2223000048400011Mastercard
4005519200000004Visa
4012000033330026Visa
4012000077777777Visa
4012888888881881Visa
4217651111111119Visa
4500600000000061Visa
4772129056533503Visa
4915805038587737Visa

Next steps

See also

Provide customer authentication with 3D Secure.