# Integration Guide

You'll learn how to:

* Set up environment credentials &#x20;
* Authenticate your server with PayPal &#x20;
* Create and capture orders securely from your backend

## Overview

PayPal Checkout uses a two-part integration:

1. **Client-side** — Renders Smart Payment Buttons &#x20;
2. **Server-side** — Handles secure API calls (create and capture orders) &#x20;

This guide focuses on the server-side logic using the [Orders v2 API](/online-payments/checkout/api-reference/orders.md).

## Prerequisites

* A PayPal developer account: <https://developer.paypal.com/> &#x20;
* A sandbox app created in the [Dashboard](https://developer.paypal.com/dashboard/applications) &#x20;
* Your **Client ID** and **Secret** &#x20;

{% hint style="warning" %}
All examples use `sandbox` endpoints. Swap in `api.paypal.com` for production.
{% endhint %}

## Implement PayPal Checkout

{% stepper %}
{% step %}

### Environment Setup

{% hint style="info" %}
[Log in](https://www.paypal.com/signin?returnUri=https%3A%2F%2Fdeveloper.paypal.com%2Fdashboard%2F\&intent=developer\&ctxId=ul1749482563440) to use your own API keys in these code examples.
{% endhint %}

Set these values as environment variables or securely in a config file:

```
PAYPAL_CLIENT_ID=your-sandbox-client-id
PAYPAL_CLIENT_SECRET=your-sandbox-client-secret
PAYPAL_API=https://api-m.sandbox.paypal.com
```

{% endstep %}

{% step %}

### Authentication

Get an access token from PayPal

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://api-m.sandbox.paypal.com/v1/oauth2/token \
  -H "Accept: application/json" \
  -H "Accept-Language: en_US" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "grant_type=client_credentials"
```

{% endtab %}

{% tab title="Node.js" %}

```js
const axios = require('axios');

async function generateAccessToken() {
  const auth = Buffer.from(`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`).toString("base64");

  const response = await axios.post(
    `${process.env.PAYPAL_API}/v1/oauth2/token`,
    "grant_type=client_credentials",
    {
      headers: {
        Authorization: `Basic ${auth}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
    }
  );

  return response.data.access_token;
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests
import base64
import os

def generate_access_token():
    client_id = os.getenv('PAYPAL_CLIENT_ID')
    client_secret = os.getenv('PAYPAL_CLIENT_SECRET')
    
    auth_string = f"{client_id}:{client_secret}"
    auth_bytes = auth_string.encode('ascii')
    auth_b64 = base64.b64encode(auth_bytes).decode('ascii')
    
    headers = {
        'Authorization': f'Basic {auth_b64}',
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    data = 'grant_type=client_credentials'
    
    response = requests.post(
        f"{os.getenv('PAYPAL_API')}/v1/oauth2/token",
        headers=headers,
        data=data
    )
    
    return response.json()['access_token']
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.http.*;
import java.net.URI;
import java.util.Base64;
import com.fasterxml.jackson.databind.ObjectMapper;

public class PayPalAuth {
    private static final String PAYPAL_API = System.getenv("PAYPAL_API");
    private static final String CLIENT_ID = System.getenv("PAYPAL_CLIENT_ID");
    private static final String CLIENT_SECRET = System.getenv("PAYPAL_CLIENT_SECRET");
    
    public static String generateAccessToken() throws Exception {
        String auth = CLIENT_ID + ":" + CLIENT_SECRET;
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
        
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(PAYPAL_API + "/v1/oauth2/token"))
            .header("Authorization", "Basic " + encodedAuth)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials"))
            .build();
            
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readTree(response.body()).get("access_token").asText();
    }
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
function generateAccessToken() {
    $clientId = $_ENV['PAYPAL_CLIENT_ID'];
    $clientSecret = $_ENV['PAYPAL_CLIENT_SECRET'];
    $paypalApi = $_ENV['PAYPAL_API'];
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $paypalApi . '/v1/oauth2/token');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
    curl_setopt($ch, CURLOPT_USERPWD, $clientId . ':' . $clientSecret);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $data = json_decode($response, true);
    return $data['access_token'];
}
?>
```

{% endtab %}

{% tab title=".NET" %}

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

public class PayPalAuth
{
    private static readonly HttpClient client = new HttpClient();
    private static readonly string PayPalApi = Environment.GetEnvironmentVariable("PAYPAL_API");
    private static readonly string ClientId = Environment.GetEnvironmentVariable("PAYPAL_CLIENT_ID");
    private static readonly string ClientSecret = Environment.GetEnvironmentVariable("PAYPAL_CLIENT_SECRET");
    
    public static async Task<string> GenerateAccessToken()
    {
        var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ClientId}:{ClientSecret}"));
        
        var request = new HttpRequestMessage(HttpMethod.Post, $"{PayPalApi}/v1/oauth2/token");
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authValue);
        request.Content = new StringContent("grant_type=client_credentials", Encoding.UTF8, "application/x-www-form-urlencoded");
        
        var response = await client.SendAsync(request);
        var responseContent = await response.Content.ReadAsStringAsync();
        
        var json = JObject.Parse(responseContent);
        return json["access_token"].ToString();
    }
}
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'net/http'
require 'uri'
require 'base64'
require 'json'

def generate_access_token
  client_id = ENV['PAYPAL_CLIENT_ID']
  client_secret = ENV['PAYPAL_CLIENT_SECRET']
  paypal_api = ENV['PAYPAL_API']
  
  uri = URI("#{paypal_api}/v1/oauth2/token")
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Post.new(uri)
  request['Authorization'] = "Basic #{Base64.strict_encode64("#{client_id}:#{client_secret}")}"
  request['Content-Type'] = 'application/x-www-form-urlencoded'
  request.body = 'grant_type=client_credentials'
  
  response = http.request(request)
  data = JSON.parse(response.body)
  
  data['access_token']
end
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Create Order

Create a new PayPal order

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -d '{
    "intent": "CAPTURE",
    "purchase_units": [{
      "amount": {
        "currency_code": "USD",
        "value": "10.00"
      }
    }]
  }'
```

{% endtab %}

{% tab title="Node.js" %}

```js
app.post("/api/orders", async (req, res) => {
  const accessToken = await generateAccessToken();

  const response = await axios.post(
    `${process.env.PAYPAL_API}/v2/checkout/orders`,
    {
      intent: "CAPTURE",
      purchase_units: [{
        amount: { currency_code: "USD", value: "10.00" }
      }]
    },
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      }
    }
  );

  res.json({ id: response.data.id });
});
```

{% endtab %}

{% tab title="Python" %}

```python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/orders', methods=['POST'])
def create_order():
    access_token = generate_access_token()
    
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    }
    
    order_data = {
        'intent': 'CAPTURE',
        'purchase_units': [{
            'amount': {
                'currency_code': 'USD',
                'value': '10.00'
            }
        }]
    }
    
    response = requests.post(
        f"{os.getenv('PAYPAL_API')}/v2/checkout/orders",
        headers=headers,
        json=order_data
    )
    
    return jsonify({'id': response.json()['id']})
```

{% endtab %}

{% tab title="Java" %}

```java
@PostMapping("/api/orders")
public ResponseEntity<Map<String, String>> createOrder() throws Exception {
    String accessToken = PayPalAuth.generateAccessToken();
    
    String orderData = """
        {
            "intent": "CAPTURE",
            "purchase_units": [{
                "amount": {
                    "currency_code": "USD",
                    "value": "10.00"
                }
            }]
        }
        """;
    
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(PAYPAL_API + "/v2/checkout/orders"))
        .header("Authorization", "Bearer " + accessToken)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(orderData))
        .build();
        
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    
    ObjectMapper mapper = new ObjectMapper();
    String orderId = mapper.readTree(response.body()).get("id").asText();
    
    return ResponseEntity.ok(Map.of("id", orderId));
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/api/orders') {
    $accessToken = generateAccessToken();
    
    $orderData = [
        'intent' => 'CAPTURE',
        'purchase_units' => [[
            'amount' => [
                'currency_code' => 'USD',
                'value' => '10.00'
            ]
        ]]
    ];
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $_ENV['PAYPAL_API'] . '/v2/checkout/orders');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($orderData));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $data = json_decode($response, true);
    echo json_encode(['id' => $data['id']]);
}
?>
```

{% endtab %}

{% tab title=".NET" %}

```csharp
[HttpPost("api/orders")]
public async Task<IActionResult> CreateOrder()
{
    var accessToken = await PayPalAuth.GenerateAccessToken();
    
    var orderData = new
    {
        intent = "CAPTURE",
        purchase_units = new[]
        {
            new
            {
                amount = new
                {
                    currency_code = "USD",
                    value = "10.00"
                }
            }
        }
    };
    
    var json = JsonConvert.SerializeObject(orderData);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var request = new HttpRequestMessage(HttpMethod.Post, $"{PayPalApi}/v2/checkout/orders");
    request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    request.Content = content;
    
    var response = await client.SendAsync(request);
    var responseContent = await response.Content.ReadAsStringAsync();
    
    var responseJson = JObject.Parse(responseContent);
    return Ok(new { id = responseJson["id"].ToString() });
}
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
post '/api/orders' do
  content_type :json
  
  access_token = generate_access_token
  
  order_data = {
    intent: 'CAPTURE',
    purchase_units: [{
      amount: {
        currency_code: 'USD',
        value: '10.00'
      }
    }]
  }
  
  uri = URI("#{ENV['PAYPAL_API']}/v2/checkout/orders")
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Post.new(uri)
  request['Authorization'] = "Bearer #{access_token}"
  request['Content-Type'] = 'application/json'
  request.body = order_data.to_json
  
  response = http.request(request)
  data = JSON.parse(response.body)
  
  { id: data['id'] }.to_json
end
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Capture Order

Capture payment for an approved order

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://api-m.sandbox.paypal.com/v2/checkout/orders/ORDER_ID/capture \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

{% endtab %}

{% tab title="Node.js" %}

```js
app.post("/api/orders/:orderID/capture", async (req, res) => {
  const accessToken = await generateAccessToken();
  const { orderID } = req.params;

  const response = await axios.post(
    `${process.env.PAYPAL_API}/v2/checkout/orders/${orderID}/capture`,
    {},
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      }
    }
  );

  res.json(response.data);
});
```

{% endtab %}

{% tab title="Python" %}

```python
@app.route('/api/orders/<order_id>/capture', methods=['POST'])
def capture_order(order_id):
    access_token = generate_access_token()
    
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    }
    
    response = requests.post(
        f"{os.getenv('PAYPAL_API')}/v2/checkout/orders/{order_id}/capture",
        headers=headers
    )
    
    return jsonify(response.json())
```

{% endtab %}

{% tab title="Java" %}

```java
@PostMapping("/api/orders/{orderID}/capture")
public ResponseEntity<String> captureOrder(@PathVariable String orderID) throws Exception {
    String accessToken = PayPalAuth.generateAccessToken();
    
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(PAYPAL_API + "/v2/checkout/orders/" + orderID + "/capture"))
        .header("Authorization", "Bearer " + accessToken)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString("{}"))
        .build();
        
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    
    return ResponseEntity.ok(response.body());
}
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
if (preg_match('/^\/api\/orders\/([^\/]+)\/capture$/', $_SERVER['REQUEST_URI'], $matches)) {
    $orderID = $matches[1];
    $accessToken = generateAccessToken();
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $_ENV['PAYPAL_API'] . '/v2/checkout/orders/' . $orderID . '/capture');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, '{}');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    echo $response;
}
?>
```

{% endtab %}

{% tab title=".NET" %}

```csharp
[HttpPost("api/orders/{orderID}/capture")]
public async Task<IActionResult> CaptureOrder(string orderID)
{
    var accessToken = await PayPalAuth.GenerateAccessToken();
    
    var request = new HttpRequestMessage(HttpMethod.Post, $"{PayPalApi}/v2/checkout/orders/{orderID}/capture");
    request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
    
    var response = await client.SendAsync(request);
    var responseContent = await response.Content.ReadAsStringAsync();
    
    return Ok(responseContent);
}
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
post '/api/orders/:order_id/capture' do
  content_type :json
  
  order_id = params[:order_id]
  access_token = generate_access_token
  
  uri = URI("#{ENV['PAYPAL_API']}/v2/checkout/orders/#{order_id}/capture")
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Post.new(uri)
  request['Authorization'] = "Bearer #{access_token}"
  request['Content-Type'] = 'application/json'
  request.body = '{}'
  
  response = http.request(request)
  response.body
end
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

<details>

<summary>Complete Implementation Example (Node.js/Express)</summary>

{% code title="" %}

```js
const express = require('express');
const axios = require('axios');
require('dotenv').config();

const app = express();
app.use(express.json());

async function generateAccessToken() {
  const auth = Buffer.from(`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`).toString("base64");
  const response = await axios.post(
    `${process.env.PAYPAL_API}/v1/oauth2/token`,
    "grant_type=client_credentials",
    {
      headers: {
        Authorization: `Basic ${auth}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
    }
  );
  return response.data.access_token;
}

app.post("/api/orders", async (req, res) => {
  const accessToken = await generateAccessToken();
  const response = await axios.post(
    `${process.env.PAYPAL_API}/v2/checkout/orders`,
    {
      intent: "CAPTURE",
      purchase_units: [{ amount: { currency_code: "USD", value: "10.00" } }]
    },
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      }
    }
  );
  res.json({ id: response.data.id });
});

app.post("/api/orders/:orderID/capture", async (req, res) => {
  const accessToken = await generateAccessToken();
  const { orderID } = req.params;
  const response = await axios.post(
    `${process.env.PAYPAL_API}/v2/checkout/orders/${orderID}/capture`,
    {},
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      }
    }
  );
  res.json(response.data);
});

app.listen(3000, () => console.log('Server running on port 3000'));
```

{% endcode %}

</details>

## Next Steps

* Validate payment status after capture &#x20;
* Store payment details in your database &#x20;
* Add webhook listeners to handle asynchronous events &#x20;
* Implement error handling and logging
* Add order validation and security checks

## Helpful Links

* [Orders v2 API](/online-payments/checkout/api-reference/orders.md)
* [OAuth 2.0 Guide](https://developer.paypal.com/api/rest/authentication/)
* [Backend Integration Pattern](/online-payments/checkout/integration-guide.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://paypal.gitbook.com/online-payments/checkout/integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
