On this page
No Headings
Last updated: June 30, 2026
Accept PayPal payments on your website with the minimum amount of code required. You might use this ultra light integration as a proof of concept, basic e-commerce checkout, or simple donation form.
This integration creates an order and captures a payment. It uses PayPal's JavaScript SDK on the client side and server SDK on the server side.
The JavaScript SDK renders the PayPal button and handles the UI. The server SDK creates and captures orders with your app credentials.
The code examples on this page use USD and US-based scenarios. PayPal supports multiple currencies and countries. See currency codes and country codes for the full list, and payment methods for country-specific options.
This integration works everywhere PayPal is available.
Choose your preferred language and set up the project structure.
# Create project structure
mkdir paypal-minimal && cd paypal-minimal
npm init -y
npm install express @paypal/checkout-server-sdk dotenv
mkdir public
# Create .env file with development defaults
cat > .env << 'EOF'
# Development/Sandbox Configuration (Default)
# ==========================================
PAYPAL_CLIENT_ID=your_sandbox_client_id
PAYPAL_CLIENT_SECRET=your_sandbox_secret
NODE_ENV=development
PORT=3000
# Negative Testing (Sandbox Only)
# --------------------------------
# Set to true to enable error simulation in sandbox
ENABLE_NEGATIVE_TESTING=false
NEGATIVE_TEST_TYPE=INSUFFICIENT_FUNDS
# Options: INSUFFICIENT_FUNDS, INSTRUMENT_DECLINED, TRANSACTION_REFUSED,
# INTERNAL_SERVER_ERROR, DUPLICATE_INVOICE_ID
# Production Configuration (Uncomment to use)
# ==========================================
# PAYPAL_CLIENT_ID=your_production_client_id
# PAYPAL_CLIENT_SECRET=your_production_secret
# NODE_ENV=production
# PORT=3000
EOF
# Create empty files
touch server.js
touch public/index.html
echo "✅ Project structure created! Now copy the code below into the files."# Create project structure
mkdir paypal-minimal && cd paypal-minimal
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install flask paypalrestsdk python-dotenv
mkdir templates static
# Create .env file with development defaults
cat > .env << 'EOF'
# Development/Sandbox Configuration (Default)
# ==========================================
PAYPAL_CLIENT_ID=your_sandbox_client_id
PAYPAL_CLIENT_SECRET=your_sandbox_secret
FLASK_ENV=development
PORT=3000
# Negative Testing (Sandbox Only)
# --------------------------------
# Set to true to enable error simulation in sandbox
ENABLE_NEGATIVE_TESTING=false
NEGATIVE_TEST_TYPE=INSUFFICIENT_FUNDS
# Options: INSUFFICIENT_FUNDS, INSTRUMENT_DECLINED, TRANSACTION_REFUSED,
# INTERNAL_SERVER_ERROR, DUPLICATE_INVOICE_ID
# Production Configuration (Uncomment to use)
# ==========================================
# PAYPAL_CLIENT_ID=your_production_client_id
# PAYPAL_CLIENT_SECRET=your_production_secret
# FLASK_ENV=production
# PORT=3000
EOF
# Create requirements.txt
cat > requirements.txt << 'EOF'
flask
paypalrestsdk
python-dotenv
EOF
# Create empty files
touch app.py
touch templates/index.html
echo "✅ Project structure created! Now copy the code below into the files."# Create Maven project structure
mkdir paypal-minimal && cd paypal-minimal
mkdir -p src/main/java/com/paypal/demo
mkdir -p src/main/resources
mkdir -p src/main/resources/static
# Create pom.xml with dependencies
cat > pom.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.paypal</groupId>
<artifactId>paypal-minimal</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>checkout-sdk</artifactId>
<version>1.0.5</version>
</dependency>
</dependencies>
</project>
EOF
# Create application.properties
cat > src/main/resources/application.properties << 'EOF'
# Development/Sandbox Configuration (Default)
paypal.client.id=your_sandbox_client_id
paypal.client.secret=your_sandbox_secret
paypal.mode=sandbox
server.port=3000
# Negative Testing (Sandbox Only)
# Set to true to enable error simulation in sandbox
enable.negative.testing=false
negative.test.type=INSUFFICIENT_FUNDS
# Options: INSUFFICIENT_FUNDS, INSTRUMENT_DECLINED, TRANSACTION_REFUSED,
# INTERNAL_SERVER_ERROR, DUPLICATE_INVOICE_ID
# Production Configuration (comment above, uncomment below)
# paypal.client.id=your_production_client_id
# paypal.client.secret=your_production_secret
# paypal.mode=live
# server.port=3000
EOF
# Create empty Java files
touch src/main/java/com/paypal/demo/PayPalController.java
touch src/main/java/com/paypal/demo/Application.java
touch src/main/resources/static/index.html
echo "✅ Project structure created! Now copy the code below into the files."# Create project structure
mkdir paypal-minimal && cd paypal-minimal
composer init -n
composer require paypal/paypal-checkout-sdk vlucas/phpdotenv
mkdir public
# Create .env file with development defaults
cat > .env << 'EOF'
# Development/Sandbox Configuration (Default)
# ==========================================
PAYPAL_CLIENT_ID=your_sandbox_client_id
PAYPAL_CLIENT_SECRET=your_sandbox_secret
APP_ENV=development
PORT=3000
# Negative Testing (Sandbox Only)
# --------------------------------
# Set to true to enable error simulation in sandbox
ENABLE_NEGATIVE_TESTING=false
NEGATIVE_TEST_TYPE=INSUFFICIENT_FUNDS
# Options: INSUFFICIENT_FUNDS, INSTRUMENT_DECLINED, TRANSACTION_REFUSED,
# INTERNAL_SERVER_ERROR, DUPLICATE_INVOICE_ID
# Production Configuration (Uncomment to use)
# ==========================================
# PAYPAL_CLIENT_ID=your_production_client_id
# PAYPAL_CLIENT_SECRET=your_production_secret
# APP_ENV=production
# PORT=3000
EOF
# Create empty files
touch index.php
touch public/index.html
echo "✅ Project structure created! Now copy the code below into the files."# Create project structure
mkdir paypal-minimal && cd paypal-minimal
bundle init
# Add gems to Gemfile
cat >> Gemfile << 'EOF'
gem 'sinatra'
gem 'paypal-checkout-sdk'
gem 'dotenv'
EOF
bundle install
mkdir public
# Create .env file with development defaults
cat > .env << 'EOF'
# Development/Sandbox Configuration (Default)
# ==========================================
PAYPAL_CLIENT_ID=your_sandbox_client_id
PAYPAL_CLIENT_SECRET=your_sandbox_secret
RACK_ENV=development
PORT=3000
# Negative Testing (Sandbox Only)
# --------------------------------
# Set to true to enable error simulation in sandbox
ENABLE_NEGATIVE_TESTING=false
NEGATIVE_TEST_TYPE=INSUFFICIENT_FUNDS
# Options: INSUFFICIENT_FUNDS, INSTRUMENT_DECLINED, TRANSACTION_REFUSED,
# INTERNAL_SERVER_ERROR, DUPLICATE_INVOICE_ID
# Production Configuration (Uncomment to use)
# ==========================================
# PAYPAL_CLIENT_ID=your_production_client_id
# PAYPAL_CLIENT_SECRET=your_production_secret
# RACK_ENV=production
# PORT=3000
EOF
# Create empty files
touch app.rb
touch public/index.html
echo "✅ Project structure created! Now copy the code below into the files."Choose your preferred language and copy the code into your server file:
# Create 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"
}
}]
}'
# Capture 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"require("dotenv").config();
const express = require("express");
const paypal = require("@paypal/checkout-server-sdk");
const app = express();
app.use(express.json());
app.use(express.static("public"));
// PayPal configuration
const environment =
process.env.NODE_ENV === "production"
? new paypal.core.LiveEnvironment(
process.env.PAYPAL_CLIENT_ID,
process.env.PAYPAL_CLIENT_SECRET,
)
: new paypal.core.SandboxEnvironment(
process.env.PAYPAL_CLIENT_ID,
process.env.PAYPAL_CLIENT_SECRET,
);
const client = new paypal.core.PayPalHttpClient(environment);
// Helper for negative testing
const addNegativeTesting = (request) => {
if (
process.env.NODE_ENV !== "production" &&
process.env.ENABLE_NEGATIVE_TESTING === "true" &&
process.env.NEGATIVE_TEST_TYPE
) {
request.headers["PayPal-Mock-Response"] = JSON.stringify({
mock_application_codes: process.env.NEGATIVE_TEST_TYPE,
});
}
};
// Create order
app.post("/api/orders", async (req, res) => {
const request = new paypal.orders.OrdersCreateRequest();
addNegativeTesting(request);
request.requestBody({
intent: "CAPTURE",
purchase_units: [
{ amount: { currency_code: "USD", value: req.body.amount || "10.00" } },
],
});
try {
const order = await client.execute(request);
res.json({ id: order.result.id });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Capture order
app.post("/api/orders/:orderID/capture", async (req, res) => {
const request = new paypal.orders.OrdersCaptureRequest(req.params.orderID);
addNegativeTesting(request);
try {
const capture = await client.execute(request);
res.json({ id: capture.result.id, status: capture.result.status });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(process.env.PORT || 3000, () => {
console.log(
`Server running on port ${process.env.PORT || 3000} in ${process.env.NODE_ENV === "production" ? "production" : "sandbox"} mode`,
);
});from flask import Flask, request, jsonify
from paypalrestsdk import Api, Order
import os
app = Flask(__name__)
# Configure PayPal SDK
api = Api({
'mode': 'sandbox' if os.environ.get('FLASK_ENV') != 'production' else 'live',
'client_id': os.environ['PAYPAL_CLIENT_ID'],
'client_secret': os.environ['PAYPAL_CLIENT_SECRET']
})
# Create order
@app.route('/api/orders', methods=['POST'])
def create_order():
try:
order = Order({
"intent": "CAPTURE",
"purchase_units": [{
"amount": {
"currency_code": "USD",
"value": request.json.get('amount', '10.00')
}
}]
})
if order.create():
return jsonify({"id": order.id})
else:
return jsonify({"error": order.error}), 500
except Exception as e:
return jsonify({"error": str(e)}), 500
# Capture order
@app.route('/api/orders/<order_id>/capture', methods=['POST'])
def capture_order(order_id):
try:
order = Order.find(order_id)
if order.capture():
return jsonify({
"id": order.id,
"status": order.status
})
else:
return jsonify({"error": order.error}), 500
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(port=int(os.environ.get('PORT', 3000)))import com.paypal.core.PayPalEnvironment;
import com.paypal.core.PayPalHttpClient;
import com.paypal.orders.OrdersCreateRequest;
import com.paypal.orders.OrdersCaptureRequest;
import com.paypal.orders.Order;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/api")
public class PayPalController {
private PayPalHttpClient httpClient;
public PayPalController() {
String env = System.getenv("NODE_ENV");
PayPalEnvironment environment = "production".equals(env)
? new LiveEnvironment(
System.getenv("PAYPAL_CLIENT_ID"),
System.getenv("PAYPAL_CLIENT_SECRET")
)
: new SandboxEnvironment(
System.getenv("PAYPAL_CLIENT_ID"),
System.getenv("PAYPAL_CLIENT_SECRET")
);
this.httpClient = new PayPalHttpClient(environment);
}
// Create order
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody OrderRequest orderRequest) {
try {
OrdersCreateRequest request = new OrdersCreateRequest();
request.requestBody(new OrderRequestBody()
.intent("CAPTURE")
.purchaseUnits(List.of(
new PurchaseUnitRequest()
.amount(new AmountWithBreakdown()
.currencyCode("USD")
.value(orderRequest.getAmount() != null ? orderRequest.getAmount() : "10.00")
)
))
);
HttpResponse<Order> response = httpClient.execute(request);
Order order = response.result();
return ResponseEntity.ok(Map.of("id", order.id()));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
}
}
// Capture order
@PostMapping("/orders/{orderId}/capture")
public ResponseEntity<?> captureOrder(@PathVariable String orderId) {
try {
OrdersCaptureRequest request = new OrdersCaptureRequest(orderId);
HttpResponse<Order> response = httpClient.execute(request);
Order order = response.result();
return ResponseEntity.ok(Map.of(
"id", order.id(),
"status", order.status()
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
}
}
}<?php
require __DIR__ . '/vendor/autoload.php';
use PayPalCheckoutSdk\Core\PayPalHttpClient;
use PayPalCheckoutSdk\Core\SandboxEnvironment;
use PayPalCheckoutSdk\Core\ProductionEnvironment;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
// PayPal configuration
$env = getenv('APP_ENV');
$environment = ($env === 'production')
? new ProductionEnvironment(getenv('PAYPAL_CLIENT_ID'), getenv('PAYPAL_CLIENT_SECRET'))
: new SandboxEnvironment(getenv('PAYPAL_CLIENT_ID'), getenv('PAYPAL_CLIENT_SECRET'));
$client = new PayPalHttpClient($environment);
// Create order
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['REQUEST_URI'] === '/api/orders') {
$input = json_decode(file_get_contents('php://input'), true);
$request = new OrdersCreateRequest();
$request->body = [
'intent' => 'CAPTURE',
'purchase_units' => [[
'amount' => [
'currency_code' => 'USD',
'value' => $input['amount'] ?? '10.00'
]
]]
];
try {
$response = $client->execute($request);
echo json_encode(['id' => $response->result->id]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
// Capture order
if ($_SERVER['REQUEST_METHOD'] === 'POST' && preg_match('/\/api\/orders\/(.+)\/capture/', $_SERVER['REQUEST_URI'], $matches)) {
$orderId = $matches[1];
$request = new OrdersCaptureRequest($orderId);
try {
$response = $client->execute($request);
echo json_encode([
'id' => $response->result->id,
'status' => $response->result->status
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
?>require 'sinatra'
require 'paypal-checkout-sdk'
require 'json'
# PayPal configuration
environment = ENV['RACK_ENV'] == 'production' ?
PayPal::LiveEnvironment.new(ENV['PAYPAL_CLIENT_ID'], ENV['PAYPAL_CLIENT_SECRET']) :
PayPal::SandboxEnvironment.new(ENV['PAYPAL_CLIENT_ID'], ENV['PAYPAL_CLIENT_SECRET'])
client = PayPal::PayPalHttpClient.new(environment)
# Create order
post '/api/orders' do
content_type :json
request_body = JSON.parse(request.body.read)
order_request = PayPalCheckoutSdk::Orders::OrdersCreateRequest.new
order_request.request_body({
intent: 'CAPTURE',
purchase_units: [{
amount: {
currency_code: 'USD',
value: request_body['amount'] || '10.00'
}
}]
})
begin
response = client.execute(order_request)
{ id: response.result.id }.to_json
rescue StandardError => e
status 500
{ error: e.message }.to_json
end
end
# Capture order
post '/api/orders/:order_id/capture' do
content_type :json
capture_request = PayPalCheckoutSdk::Orders::OrdersCaptureRequest.new(params[:order_id])
begin
response = client.execute(capture_request)
{
id: response.result.id,
status: response.result.status
}.to_json
rescue StandardError => e
status 500
{ error: e.message }.to_json
end
endOpen the public/index.html file in a code editor and copy and paste the following code. Replace YOUR_CLIENT_ID with the client ID created in the prerequisites.
If your app uses PayPal buttons across multiple pages or a modular JavaScript architecture, see Multipage and modular app issues.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>PayPal Checkout</title>
</head>
<body>
<div id="paypal-button-container"></div>
<div id="result-message"></div>
<script>
// Pass the createOrder, onApprove, onCancel, and onError callbacks to the SDK
async function createOrder(data) {
const response = await fetch("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// We don't recommend passing order amounts from the client
// Your API should exchange a SKU for amount values
body: JSON.stringify({ sku: "BDUR24586" }),
});
const { id } = await response.json();
return { orderId: id };
}
async function onApprove(data) {
const { orderId } = data;
const response = await fetch(`/api/orders/${orderId}/capture`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const orderData = await response.json();
return orderData;
}
function onCancel(data) {
document.getElementById("result-message").innerHTML =
`<p style="color: orange;">Payment cancelled</p>`;
}
function onError(err) {
console.error("PayPal Error:", err);
let errorMessage = "An error occurred. Please try again.";
// Handle specific error codes
if (err.message && err.message.includes("INSUFFICIENT_FUNDS")) {
errorMessage = "Payment declined due to insufficient funds.";
} else if (err.message && err.message.includes("INSTRUMENT_DECLINED")) {
errorMessage =
"Your payment method was declined. Please try another.";
} else if (err.message && err.message.includes("TRANSACTION_REFUSED")) {
errorMessage = "Transaction was refused. Please contact support.";
} else if (err.message && err.message.includes("DUPLICATE_INVOICE")) {
errorMessage =
"This order has already been processed. Please refresh and try again.";
}
document.getElementById("result-message").innerHTML =
`<p style="color: red;">${errorMessage}</p>`;
}
// After the browser is done downloading the SDK, it will invoke onLoad()
// and begin the SDK setup
async function onLoad() {
try {
const sdkInstance = await window.paypal.createInstance({
clientId: "YOUR_CLIENT_ID", // clientId is safe to put in plaintext on the client
components: ["paypal-payments"],
});
const eligibility = await sdkInstance.findEligibleMethods();
const isPayPalEligible = eligibility.isEligible("paypal");
if (isPayPalEligible) {
const paypalButton = document.createElement("paypal-button");
document
.querySelector("#paypal-button-container")
.append(paypalButton);
const paypalCheckoutSession =
await sdkInstance.createPayPalOneTimePaymentSession({
onApprove,
onCancel,
onError,
});
paypalButton.addEventListener("click", async () => {
try {
// Get the promise reference by invoking createOrder()
// Do not await this async function since it can cause transient activation issues
const createOrderPromise = createOrder();
await paypalCheckoutSession.start(
{ presentationMode: "auto" },
createOrderPromise,
);
} catch (error) {
console.error(error);
}
});
}
} catch (error) {
console.error(error);
}
}
</script>
<script
async
src="https://www.sandbox.paypal.com/web-sdk/v6/core"
onload="onLoad()"
></script>
</body>
</html>Open the .env file in a code editor and replace your_sandbox_client_id and your_sandbox_secret with the client ID and secret you created for your app in the prerequisites.
Start the server:
node server.jsOpen the following URL in your browser. You should see a PayPal button.
http://localhost:3000Use the following best practices to ensure secure, reliable, and maintainable integrations when building your checkout experience.
Before testing, make sure you have sandbox account credentials for both buyer and seller roles. Get sandbox account credentials.
Use the following standard test scenarios to verify your checkout handles key outcomes.
| Test scenario | Setup | Expected result |
|---|---|---|
| Successful payment | Default settings | Green success message and transaction ID shown. |
| Cancelled payment | Default settings | Orange cancel message shown. |
| Server error | Stop server | Red error message shown. |
Negative testing works only in sandbox mode. It is automatically disabled when NODE_ENV=production.
For negative testing:
.env file, set ENABLE_NEGATIVE_TESTING=true and set NEGATIVE_TEST_TYPE to one of the error codes in the table..env file: node server.js.| Test scenario | Error code | Expected result |
|---|---|---|
| Insufficient funds | INSUFFICIENT_FUNDS | Error: buyer cannot pay at capture. |
| Instrument declined | INSTRUMENT_DECLINED | Error: payment method was declined at capture. |
| Internal server error | INTERNAL_SERVER_ERROR | Error: 500 error occurred at create or capture. |
| Transaction refused | TRANSACTION_REFUSED | Error: transaction refused at capture. |
| Duplicate invoice ID | DUPLICATE_INVOICE_ID | Error: invoice ID must be unique at create. |
.env file:
These values are suggested monitoring thresholds for your integration, not performance guarantees from PayPal.
| Metric | Target | Action if below target |
|---|---|---|
| Payment success rate | 95% | Check API errors in logs |
| Cancel rate | <20% | Review checkout UX and pricing display |
| Error rate | <2% | Check error logs and API responses |
| Order creation response time | <2 seconds | Optimize server performance and check PayPal status |
| Capture response time | <3 seconds | Review server performance |
The following use cases are common next steps after completing this quick start.