> ## Documentation Index
> Fetch the complete documentation index at: https://paypal-sb-staging.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Save PayPal with and without purchase with the JavaScript SDK

This guide demonstrates how to integrate PayPal save payment functionality using the JavaScript SDK v6, allowing customers to securely save their PayPal payment methods for future transactions without making an immediate purchase.

The PayPal Save Payment integration enables you to:

* Collect and securely store PayPal payment methods without charging the customer
* Create payment tokens for future use in subsequent transactions
* Provide a streamlined checkout experience for returning customers
* Maintain PCI compliance by leveraging PayPal's secure vault system

## Prerequisites

Set up your PayPal account:

* Create a PayPal developer, personal, or business account
* Visit the [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/)
* Create a sandbox application to obtain **Client ID** and **Secret**
* Ensure your application has **Vault** permissions enabled

Enable vaulting:

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.

Configure your environment:

In your root folder, create an `.env` file with your PayPal credentials:

```bash theme={null}
PAYPAL_SANDBOX_CLIENT_ID=your_client_id
PAYPAL_SANDBOX_CLIENT_SECRET=your_client_secret
```

## Key concepts

Setup token vs payment token:

* **Setup Token**: Temporary token used during the save payment flow
* **Payment Token**: Permanent token stored in PayPal's vault for future use
* **Conversion**: Setup tokens are converted to payment tokens after customer approval

Payment flows:

* **VAULT\_WITHOUT\_PAYMENT**: Save payment method without making a purchase
* **VAULT\_WITH\_PAYMENT**: Save payment method while making a purchase

Usage patterns:

* **IMMEDIATE**: Token will be used right away
* **DEFERRED**: Token will be used at a future date

## Integration flow

The PayPal save payment integration follows a specific flow:

1. **Initialize PayPal SDK** with vault-specific configuration
2. **Check eligibility** for save payment functionality
3. **Create setup token** on your server
4. **Start save payment session** to collect payment method
5. **Create payment token** from vault setup token for future use

## Set up your front end

Build an HTML page and a JavaScript file to set up your front end.

### Build an HTML page

```html theme={null}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Save Payment - PayPal JavaScript SDK</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      .buttons-container {
        display: flex;
        flex-direction: column;
        gap: 12px;
      }
    </style>
  </head>
  <body>
    <h1>Save Payment Integration</h1>

    <div class="buttons-container">
      <!-- PayPal save payment button, initially hidden -->
      <paypal-button id="paypal-button" hidden></paypal-button>
    </div>
    
    <script src="app.js"></script>

    <!-- Load PayPal SDK -->
    <script
      async
      src="https://www.sandbox.paypal.com/web-sdk/v6/core"
      onload="onPayPalWebSdkLoaded()"
    ></script>
  </body>
</html>
```

### Initialize the SDK to save payment methods

```javascript theme={null}
async function onPayPalWebSdkLoaded() {
  try {
    // Get client token for authentication
    const clientToken = await getBrowserSafeClientToken();
    
    // Create PayPal SDK instance
    const sdkInstance = await window.paypal.createInstance({
      clientToken,
      components: ["paypal-payments"],
      pageType: "checkout",
    });

    // Check eligibility for save payment with vault flow
    const paymentMethods = await sdkInstance.findEligibleMethods({
      currencyCode: "USD",
      paymentFlow: "VAULT_WITHOUT_PAYMENT", // Specify vault flow without payment
    });

    // Setup save payment button if eligible
    if (paymentMethods.isEligible("paypal")) {
      setupPayPalButton(sdkInstance);
    } else {
      console.log("PayPal save payment is not eligible for this session");
      showNotEligibleMessage();
    }
  } catch (error) {
    console.error("SDK initialization error:", error);
    handleInitializationError(error);
  }
}
```

### Configure the payment session

```javascript theme={null}
const paymentSessionOptions = {
  // Called when customer approves saving their payment method
  async onApprove(data) {
    console.log("Save payment approved:", data);
    
    try {
      // Create payment token from vault setup token
      const createPaymentTokenResponse = await createPaymentToken(
        data.vaultSetupToken,
      );
      
      console.log("Payment token created:", createPaymentTokenResponse);
      
      // Handle successful save payment
      handleSavePaymentSuccess(createPaymentTokenResponse);
      
    } catch (error) {
      console.error("Payment token creation failed:", error);
      handleSavePaymentError(error);
    }
  },
  
  // Called when customer cancels the save payment flow
  onCancel(data) {
    console.log("Save payment cancelled:", data);
    handleSavePaymentCancellation();
  },
  
  // Called when an error occurs during save payment
  onError(error) {
    console.error("Save payment error:", error);
    handleSavePaymentError(error);
  },
};
```

### Set up button to save payments

```javascript theme={null}
async function setupPayPalButton(sdkInstance) {
  // Create PayPal save payment session
  const paypalPaymentSession = sdkInstance.createPayPalSavePaymentSession(
    paymentSessionOptions,
  );

  // Get reference to PayPal button
  const paypalButton = document.querySelector("#paypal-button");
  paypalButton.removeAttribute("hidden");

  // Add click handler to start save payment flow
  paypalButton.addEventListener("click", async () => {
    try {
      // Start the save payment session
      await paypalPaymentSession.start(
        { 
          presentationMode: "auto" // Auto-detects best presentation mode
        },
        createSetupToken() // Create setup token for vault
      );
    } catch (error) {
      console.error("Save payment start error:", error);
      handleSavePaymentError(error);
    }
  });
}
```

## Set up your backend

The save payment integration requires these server-side endpoints:

### Client Token endpoint

```javascript theme={null}
// GET /paypal-api/auth/browser-safe-client-token
async function getBrowserSafeClientToken() {
  const response = await fetch("/paypal-api/auth/browser-safe-client-token", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const { accessToken } = await response.json();
  return accessToken;
}
```

### Create Setup Token endpoint

```javascript theme={null}
// POST /paypal-api/vault/setup-token/create
async function createSetupToken() {
  const response = await fetch("/paypal-api/vault/setup-token/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const { id } = await response.json();
  
  return { setupToken: id };
}
```

### Create Payment Token endpoint

```javascript theme={null}
// POST /paypal-api/vault/payment-token/create
async function createPaymentToken(vaultSetupToken) {
  const response = await fetch("/paypal-api/vault/payment-token/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ vaultSetupToken }),
  });
  const data = await response.json();
  
  return data;
}
```

### Set up token creation on your server

On your server, create a setup token for vault operations:

```javascript theme={null}
// Example Node.js server endpoint
app.post('/paypal-api/vault/setup-token/create', async (req, res) => {
  try {
    const setupTokenPayload = {
      payment_source: {
        paypal: {
          description: "Save PayPal payment method",
          usage_pattern: "IMMEDIATE", // or "DEFERRED"
          usage_type: "MERCHANT", // or "PLATFORM"
          customer_type: "CONSUMER", // or "BUSINESS"
          permit_multiple_payment_tokens: false,
        }
      }
    };

    const setupTokenResponse = await paypalClient.vaultSetupTokensCreate({
      body: setupTokenPayload
    });

    res.json({ 
      id: setupTokenResponse.result.id 
    });
  } catch (error) {
    console.error('Setup token creation error:', error);
    res.status(500).json({ error: 'Failed to create setup token' });
  }
});
```

### Set up Payment Token creation on your server

Convert the vault setup token to a reusable payment token:

```javascript theme={null}
// Example Node.js server endpoint
app.post('/paypal-api/vault/payment-token/create', async (req, res) => {
  try {
    const { vaultSetupToken } = req.body;

    const paymentTokenPayload = {
      payment_source: {
        token: {
          id: vaultSetupToken,
          type: "SETUP_TOKEN"
        }
      }
    };

    const paymentTokenResponse = await paypalClient.vaultPaymentTokensCreate({
      body: paymentTokenPayload
    });

    res.json({
      id: paymentTokenResponse.result.id,
      status: paymentTokenResponse.result.status,
      payment_source: paymentTokenResponse.result.payment_source
    });
  } catch (error) {
    console.error('Payment token creation error:', error);
    res.status(500).json({ error: 'Failed to create payment token' });
  }
});
```

## Use saved payment tokens

Once you have a payment token, you can use it for future transactions:

### Create order with saved payment methods

```javascript theme={null}
async function createOrderWithSavedPayment(paymentTokenId, orderAmount) {
  const orderPayload = {
    intent: "CAPTURE",
    purchase_units: [{
      amount: {
        currency_code: "USD",
        value: orderAmount
      }
    }],
    payment_source: {
      paypal: {
        vault_id: paymentTokenId, // Use the saved payment token
        stored_in_vault: "ON_SUCCESS"
      }
    }
  };

  const response = await fetch("/paypal-api/checkout/orders/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(orderPayload),
  });

  const { id } = await response.json();
  return { orderId: id };
}
```

### Create a server-side order with a vault ID

```javascript theme={null}
// Example server endpoint for orders using saved payment methods
app.post('/paypal-api/checkout/orders/create-with-vault', async (req, res) => {
  try {
    const { vaultId, amount } = req.body;

    const orderPayload = {
      intent: "CAPTURE",
      purchase_units: [{
        amount: {
          currency_code: "USD",
          value: amount
        }
      }],
      payment_source: {
        paypal: {
          vault_id: vaultId,
          stored_in_vault: "ON_SUCCESS"
        }
      }
    };

    const orderResponse = await paypalClient.ordersCreate({
      body: orderPayload
    });

    res.json({ 
      id: orderResponse.result.id 
    });
  } catch (error) {
    console.error('Order creation error:', error);
    res.status(500).json({ error: 'Failed to create order' });
  }
});
```

## Advanced Features

The following code samples demonstrate how to configure a custom setup token and manage payment tokens.

### Configure custom setup token

```javascript theme={null}
async function createCustomSetupToken(customerInfo = {}) {
  const setupTokenPayload = {
    payment_source: {
      paypal: {
        description: customerInfo.description || "Save PayPal payment method",
        usage_pattern: customerInfo.usagePattern || "IMMEDIATE",
        usage_type: customerInfo.usageType || "MERCHANT",
        customer_type: customerInfo.customerType || "CONSUMER",
        permit_multiple_payment_tokens: customerInfo.allowMultiple || false,
      }
    },
    // Optional: Add customer information
    ...(customerInfo.customerId && {
      customer: {
        id: customerInfo.customerId
      }
    })
  };

  const response = await fetch("/paypal-api/vault/setup-token/create", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(setupTokenPayload),
  });

  const { id } = await response.json();
  return { setupToken: id };
}
```

### Manage payment token

```javascript theme={null}
// Get payment token details
async function getPaymentTokenDetails(paymentTokenId) {
  const response = await fetch(`/paypal-api/vault/payment-token/${paymentTokenId}`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  
  return await response.json();
}

// Delete payment token
async function deletePaymentToken(paymentTokenId) {
  const response = await fetch(`/paypal-api/vault/payment-token/${paymentTokenId}`, {
    method: "DELETE",
    headers: {
      "Content-Type": "application/json",
    },
  });
  
  return response.ok;
}

// List customer payment tokens
async function listCustomerPaymentTokens(customerId) {
  const response = await fetch(`/paypal-api/vault/payment-tokens?customer_id=${customerId}`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  
  return await response.json();
}
```

## Error handling

```javascript theme={null}
function handleSavePaymentError(error) {
  console.error("Save payment error details:", error);
  
  // Show user-friendly error messages based on error type
  const errorMessage = getSavePaymentErrorMessage(error);
  showErrorToUser(errorMessage);
  
  // Optionally provide retry mechanism
  showRetryOption();
}

function getSavePaymentErrorMessage(error) {
  switch (error.code) {
    case 'VAULT_NOT_ENABLED':
      return 'Payment saving is not available. Please contact support.';
    case 'CUSTOMER_NOT_ELIGIBLE':
      return 'Your account is not eligible for saving payment methods.';
    case 'SETUP_TOKEN_EXPIRED':
      return 'Setup session expired. Please try again.';
    case 'NETWORK_ERROR':
      return 'Network error occurred. Please check your connection and try again.';
    default:
      return 'Unable to save payment method. Please try again.';
  }
}

function handleSavePaymentSuccess(paymentTokenResponse) {
  console.log("Payment method saved successfully:", paymentTokenResponse);
  
  // Store payment token ID securely (server-side recommended)
  const paymentTokenId = paymentTokenResponse.id;
  savePaymentTokenToDatabase(paymentTokenId);
  
  // Show success message
  showSuccessMessage('Payment method saved successfully!');
  
  // Update UI to reflect saved payment method
  updatePaymentMethodsUI(paymentTokenResponse);
  
  // Track conversion for analytics
  trackSavePaymentSuccess(paymentTokenResponse);
}

function handleSavePaymentCancellation() {
  console.log("Save payment cancelled by user");
  
  // Show cancellation message
  showInfoMessage('Payment method saving was cancelled.');
  
  // Return user to appropriate flow
  returnToPaymentOptions();
}
```

## Security best practices

Follow best practices for client and server-side security.

### Client-side security

```javascript theme={null}
// Never store payment tokens in client-side storage
// Always validate on server-side
function savePaymentTokenSecurely(paymentToken) {
  // ❌ NEVER do this
  // localStorage.setItem('paymentToken', paymentToken.id);
  
  // ✅ Always send to server for secure storage
  return fetch('/api/customer/payment-methods', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${userToken}`, // Use proper authentication
    },
    body: JSON.stringify({
      paymentTokenId: paymentToken.id,
      customerId: getCurrentCustomerId(),
    }),
  });
}
```

### Server-side security

```javascript theme={null}
// Example secure payment token storage
async function storePaymentTokenSecurely(req, res) {
  try {
    // Validate user authentication
    const userId = validateUserToken(req.headers.authorization);
    if (!userId) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    const { paymentTokenId } = req.body;
    
    // Validate payment token with PayPal
    const tokenDetails = await validatePaymentToken(paymentTokenId);
    if (!tokenDetails.valid) {
      return res.status(400).json({ error: 'Invalid payment token' });
    }

    // Store securely in database with encryption
    await database.paymentTokens.create({
      user_id: userId,
      token_id: encrypt(paymentTokenId),
      token_details: encrypt(JSON.stringify(tokenDetails)),
      created_at: new Date(),
    });

    res.json({ success: true });
  } catch (error) {
    console.error('Token storage error:', error);
    res.status(500).json({ error: 'Failed to store payment method' });
  }
}
```

## Integration patterns

Common integration patterns include checkout with a save payment option and customer payment method management.

### Checkout with save payment option

```javascript theme={null}
function createCheckoutWithSaveOption() {
  return `
    <div class="checkout-options">
      <div class="payment-section">
        <h3>Payment Method</h3>
        
        <!-- Existing saved payment methods -->
        <div id="saved-payment-methods"></div>
        
        <!-- Option to use new payment method -->
        <div class="new-payment-method">
          <label>
            <input type="radio" name="payment-option" value="new">
            Use a new payment method
          </label>
          
          <div class="new-payment-controls" style="display: none;">
            <label>
              <input type="checkbox" id="save-payment-method">
              Save this payment method for future purchases
            </label>
            
            <div class="payment-buttons">
              <paypal-button id="paypal-button" hidden></paypal-button>
            </div>
          </div>
        </div>
      </div>
    </div>
  `;
}
```

### Manage customer payment methods

```javascript theme={null}
async function loadCustomerPaymentMethods() {
  try {
    const response = await fetch('/api/customer/payment-methods', {
      headers: {
        'Authorization': `Bearer ${userToken}`,
      },
    });
    
    const paymentMethods = await response.json();
    renderPaymentMethods(paymentMethods);
  } catch (error) {
    console.error('Failed to load payment methods:', error);
  }
}

function renderPaymentMethods(paymentMethods) {
  const container = document.getElementById('saved-payment-methods');
  
  paymentMethods.forEach(method => {
    const methodElement = document.createElement('div');
    methodElement.className = 'saved-payment-method';
    methodElement.innerHTML = `
      <label>
        <input type="radio" name="payment-option" value="${method.id}">
        <span class="method-info">
          PayPal ending in ${method.last4 || '****'}
          <small>Expires ${method.expiry || 'N/A'}</small>
        </span>
      </label>
      <button onclick="deletePaymentMethod('${method.id}')" class="delete-btn">
        Remove
      </button>
    `;
    container.appendChild(methodElement);
  });
}
```

## Test

Use PayPal sandbox accounts for testing:

* Create test business and personal accounts
* Test with different countries and currencies
* Verify vault permissions are enabled

Test the following scenarios:

1. **Successful Save**: Complete save payment flow successfully
2. **User Cancellation**: Test cancellation handling
3. **Network Errors**: Simulate network failures
4. **Invalid Tokens**: Test with expired or invalid setup tokens
5. **Multiple Saves**: Test saving multiple payment methods
6. **Token Usage**: Test using saved tokens for payments

## Production checklist

* [ ] Replace sandbox URLs with production URLs
* [ ] Update environment configuration for production
* [ ] Verify vault permissions are enabled in production
* [ ] Implement secure payment token storage
* [ ] Add comprehensive error handling
* [ ] Set up monitoring and alerting
* [ ] Test payment token usage workflows
* [ ] Implement token lifecycle management
* [ ] Add customer payment method management UI
* [ ] Verify PCI compliance requirements
* [ ] Test across different browsers and devices
* [ ] Implement proper authentication and authorization

## Technical limitations

* Setup tokens have limited lifetime (typically 3 hours)
* Payment tokens are specific to the merchant account
* Vault functionality requires special permissions
* Not all payment methods support vaulting

## Business considerations

* Consider customer consent and privacy regulations
* Implement proper data retention policies
* Provide clear opt-in/opt-out mechanisms
* Handle payment method updates and expiration

## Resources

* [PayPal Payment Method Tokens (vault) API reference](https://developer.paypal.com/docs/api/payment-tokens/v3/)
* [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/)
* [PCI Compliance Guidelines](https://www.pcisecuritystandards.org/)

## Support

For additional support and questions:

* Visit [PayPal Developer Community](https://developer.paypal.com/community/)
* Check [PayPal Developer Documentation](https://developer.paypal.com/docs/)
* Review the complete implementation in `./client/components/paypalPayments/savePayment/html/`
