Integration Guide
This guide shows how to implement PayPal Checkout server-side using Node.js, Python, PHP, Java, .NET, or Ruby.
You'll learn how to:
Set up environment credentials
Authenticate your server with PayPal
Create and capture orders securely from your backend
Overview
PayPal Checkout uses a two-part integration:
Client-side — Renders Smart Payment Buttons
Server-side — Handles secure API calls (create and capture orders)
This guide focuses on the server-side logic using the Orders v2 API.
Prerequisites
A PayPal developer account: https://developer.paypal.com/
A sandbox app created in the Dashboard
Your Client ID and Secret
All examples use sandbox
endpoints. Swap in api.paypal.com
for production.
Implement PayPal Checkout
Environment Setup
Log in to use your own API keys in these code examples.
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
Authentication
Get an access token from PayPal
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"
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;
}
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']
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();
}
}
<?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'];
}
?>
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();
}
}
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
Create Order
Create a new PayPal order
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"
}
}]
}'
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 });
});
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']})
@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));
}
<?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']]);
}
?>
[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() });
}
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
Capture Order
Capture payment for an approved order
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"
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.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())
@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());
}
<?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;
}
?>
[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);
}
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
Complete Implementation Example (Node.js/Express)
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'));
Next Steps
Validate payment status after capture
Store payment details in your database
Add webhook listeners to handle asynchronous events
Implement error handling and logging
Add order validation and security checks
Helpful Links
Last updated
Was this helpful?