PayPal is one of the most popular payment gateways, and it allows website owners to accept payments online. PayPal Standard Checkout with the JavaScript SDK is one of the simplest and most secure ways to accept online payments in PHP applications. Unlike older PayPal standards, the JS-SDK instantly renders smart buttons, handles client-side approval, and completes the order through server-side API calls — making it PCI-friendly and developer-efficient.
PayPal Standard Checkout uses JavaScript SDK to integrate payment button in the webpage. The buyer can use this button to make payment with their PayPal account or debit/credit card. Integrate PayPal Standard Checkout system to allow buyers to purchase online and make the payment from the website. In this tutorial, we will show you how to integrate PayPal Standard Checkout in PHP. Here we will provide a step-by-step guide for PayPal Standard Checkout Integration with PHP Using JS-SDK.
In this step-by-step guide, we will integrate:
✔ PayPal JS-SDK (Smart Buttons)
✔ Server-side Order Creation (PHP)
✔ Server-side Capture API (PHP)
✔ Saving Transactions to Database (MySQL)
✔ Displaying Payment Success Page
✔ Using PayPal Sandbox & Production Credentials
Below is how PayPal Standard Checkout works with PHP:
User Clicks PayPal Button
↓
JS-SDK requests `create_order.php`
↓
Server contacts PayPal → Creates Order
↓
User approves payment on PayPal popup
↓
JS-SDK sends OrderID to `capture_order.php`
↓
Server captures the payment & stores it in DB
↓
Returns success → Redirects user to success.php
This PayPal Checkout tutorial references the following PHP structure:
paypal_checkout_with_php/ ├── index.php ├── config.php ├── paypal_api.php ├── create_order.php ├── capture_order.php ├── success.php └── css/ └── style.css
Before you begin, ensure you have:
To get started, you need to create a PayPal Developer account and set up a sandbox environment (PayPal REST API app) for testing purposes.
Sandbox test account:
Navigate to the Testing Tools → Sandbox Accounts section in the PayPal Developer Dashboard and create a personal sandbox account to simulate buyer transactions during testing.
Create a MySQL database and a table to store transaction details. Use the following SQL query to create the transactions table:
CREATE TABLE IF NOT EXISTS `transactions` (
`id` int NOT NULL AUTO_INCREMENT,
`order_id` varchar(255) NOT NULL,
`transaction_id` varchar(255) DEFAULT NULL,
`payer_id` varchar(255) DEFAULT NULL,
`payer_email` varchar(255) DEFAULT NULL,
`payer_name` varchar(255) DEFAULT NULL,
`amount` decimal(12,2) DEFAULT NULL,
`currency` varchar(10) DEFAULT NULL,
`status` varchar(50) DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`raw_response` longtext,
PRIMARY KEY (`id`),
UNIQUE KEY `order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Create a config.php file to store your PayPal API credentials, DB connection settings and product details. Also, create a utility function to establish a MySQLi connection.
<?php
// Product details (example)
define('PRODUCT_NAME', 'Example Product');
define('PRODUCT_PRICE', '9.99');
define('PRODUCT_CURRENCY', 'USD');
// Database settings
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'paypal_checkout_db');
// PayPal REST API credentials
// For testing use sandbox credentials and set sandbox true
define('PAYPAL_CLIENT_ID', 'YOUR_PAYPAL_CLIENT_ID');
define('PAYPAL_SECRET', 'YOUR_PAYPAL_SECRET');
define('PAYPAL_SANDBOX', TRUE); //TRUE=Sandbox | FALSE=Production
// Utility: create mysqli connection
function get_db_connection() {
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($mysqli->connect_errno) {
http_response_code(500);
echo json_encode(['error' => 'Database connection failed: ' . $mysqli->connect_error]);
exit;
}
$mysqli->set_charset('utf8mb4');
return $mysqli;
}
?>
Create a paypal_api.php file to handle PayPal API interactions. This file will include functions to get an access token, create an order, and capture payment.
<?php
// Include config for constants
require_once __DIR__ . '/config.php';
// Define PayPal API base URL
if (PAYPAL_SANDBOX) {
define('PAYPAL_BASE', 'https://api-m.sandbox.paypal.com');
} else {
define('PAYPAL_BASE', 'https://api-m.paypal.com');
}
// Get PayPal access token
function paypal_get_access_token() {
$url = PAYPAL_BASE . '/v1/oauth2/token';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, PAYPAL_CLIENT_ID . ':' . PAYPAL_SECRET);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Accept-Language: en_US',
]);
$response = curl_exec($ch);
if ($response === false) {
curl_close($ch);
return null;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
$data = json_decode($response, true);
return $data['access_token'] ?? null;
}
return null;
}
// Create PayPal order
function paypal_create_order($amount, $currency = 'USD') {
$token = paypal_get_access_token();
if (!$token) return null;
$url = PAYPAL_BASE . '/v2/checkout/orders';
$body = [
'intent' => 'CAPTURE',
'purchase_units' => [[
'amount' => [
'currency_code' => $currency,
'value' => $amount,
'breakdown' => [
'item_total' => [
'currency_code' => $currency,
'value' => $amount,
],
],
],
'items' => [[
'name' => PRODUCT_NAME,
'unit_amount' => [
'currency_code' => $currency,
'value' => $amount,
],
'quantity' => '1',
]],
]],
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
]);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$response = curl_exec($ch);
if ($response === false) {
curl_close($ch);
return null;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
return json_decode($response, true);
}
return null;
}
// Capture PayPal order
function paypal_capture_order($orderId) {
$token = paypal_get_access_token();
if (!$token) return null;
$url = PAYPAL_BASE . '/v2/checkout/orders/' . urlencode($orderId) . '/capture';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $token,
]);
curl_setopt($ch, CURLOPT_POST, true);
$response = curl_exec($ch);
if ($response === false) {
curl_close($ch);
return null;
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
return json_decode($response, true);
}
return null;
}
?>
In this step, we create a simple client-side checkout page using PHP. This page will include a button that, when clicked, will initiate the payment process by calling the server-side script.
Include the configuration file (config.php) at the beginning of your PHP script to access the product details defined earlier.
<?php
require_once __DIR__ . '/config.php';
?>
Next, create the HTML structure for the checkout page. Use PHP to dynamically display the product name and price using the constants defined in config.php.
<div class="panel">
<div class="panel-heading">
<h1><?php echo htmlspecialchars(PRODUCT_NAME); ?></h1>
<p>Price: <strong><?php echo htmlspecialchars(PRODUCT_CURRENCY . ' ' . PRODUCT_PRICE); ?></strong></p>
</div>
<div class="panel-body">
<!-- Display status message -->
<div id="status-message"></div>
<!-- Set up a container element for the PayPal button -->
<div id="paypal-button-container"></div>
</div>
</div>
Next, include the PayPal JavaScript SDK by adding the following script tag. Make sure to replace the placeholders with your actual PayPal client ID and currency code using PHP.
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo urlencode(PAYPAL_CLIENT_ID); ?>¤cy=<?php echo urlencode(PRODUCT_CURRENCY); ?>"></script>
Now, add the JavaScript code to handle the PayPal button rendering and payment process. This code will create and capture the order by communicating with the server-side scripts we created earlier.
<script>
// Create order on the server
function createOrderOnServer() {
hideStatus();
return fetch('create_order.php', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: '<?php echo PRODUCT_PRICE; ?>',
currency: '<?php echo PRODUCT_CURRENCY; ?>'
})
}).then(function(res){
return res.json();
}).catch(function(err){
showStatus('Could not create order. Please try again.', 'error');
console.error(err);
return Promise.reject(err);
});
}
// Capture order on the server
function captureOrderOnServer(orderID) {
hideStatus();
return fetch('capture_order.php', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderID: orderID })
}).then(function(res){
return res.json();
}).catch(function(err){
showStatus('Payment processing failed. Please try again or contact support.', 'error');
console.error(err);
return Promise.reject(err);
});
}
paypal.Buttons({
createOrder: function(data, actions) {
return createOrderOnServer().then(function(data) {
if (data && data.id) return data.id;
return actions.reject();
});
},
onApprove: function(data, actions) {
return captureOrderOnServer(data.orderID).then(function(res){
if (res && res.success && res.db_id) {
// redirect to success page with DB id
showStatus('Payment successful — redirecting...', 'success');
// small delay to show message then redirect
setTimeout(function(){
window.location.href = 'success.php?id=' + encodeURIComponent(res.db_id);
}, 700);
} else {
showStatus('Capture failed. See console for details.', 'error');
console.log(res);
}
}).catch(function(err){
// errors already handled in captureOrderOnServer; ensure visible
showStatus('Capture failed. See console for details.', 'error');
console.error(err);
});
},
onError: function(err) {
console.error(err);
showStatus('An error occurred during payment. Please try again.', 'error');
}
}).render('#paypal-button-container');
// Helper UI functions: show and hide status messages
function showStatus(message, type) {
var el = document.getElementById('status-message');
if (!el) return;
el.textContent = message;
el.style.display = 'block';
// basic styling for types
if (type === 'error') {
el.style.background = '#ffe6e6';
el.style.color = '#a00';
el.style.border = '1px solid #f5c2c2';
} else if (type === 'success') {
el.style.background = '#e6ffea';
el.style.color = '#047a12';
el.style.border = '1px solid #bfe6c8';
} else {
el.style.background = '#eef3ff';
el.style.color = '#0b3d91';
el.style.border = '1px solid #c9d9ff';
}
}
function hideStatus() {
var el = document.getElementById('status-message');
if (!el) return;
el.style.display = 'none';
}
</script>
In this step, we create a server-side PHP script to handle the creation of PayPal orders. This script will receive the order details from the client-side, call the PayPal API to create the order, and return the order ID to the client.
<?php
// Include PayPal API functions
require_once __DIR__ . '/paypal_api.php';
// Include configuration file
require_once __DIR__ . '/config.php';
// Set response content type to JSON
header('Content-Type: application/json');
// Get amount and currency from request, or use defaults
$data = json_decode(file_get_contents('php://input'), true);
$amount = isset($data['amount']) ? $data['amount'] : PRODUCT_PRICE;
$currency = isset($data['currency']) ? $data['currency'] : PRODUCT_CURRENCY;
// Create PayPal order
$order = paypal_create_order($amount, $currency);
if (!$order) {
http_response_code(500);
echo json_encode(['error' => 'Could not create order']);
exit;
}
echo json_encode($order);
?>
Finally, we create another server-side PHP script to handle the capture of PayPal orders. This script will receive the order ID from the client-side, call the PayPal API to capture the payment, store the transaction details in the database, and return a success response to the client.
<?php
// Include PayPal API functions
require_once __DIR__ . '/paypal_api.php';
// Include configuration file
require_once __DIR__ . '/config.php';
// Set response content type to JSON
header('Content-Type: application/json');
// Get orderID from request
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['orderID'])) {
http_response_code(400);
echo json_encode(['error' => 'orderID required']);
exit;
}
$orderID = $input['orderID'];
// Capture PayPal order
$capture = paypal_capture_order($orderID);
if (!$capture) {
http_response_code(500);
echo json_encode(['error' => 'Capture failed']);
exit;
}
// Extract payer and transaction info
$status = $capture['status'] ?? null;
$payer = $capture['payer'] ?? [];
$payer_id = $payer['payer_id'] ?? ($payer['payer_id'] ?? null);
$payer_email = $payer['email_address'] ?? null;
$payer_name = null;
if (isset($payer['name'])) {
$payer_name = trim(($payer['name']['given_name'] ?? '') . ' ' . ($payer['name']['surname'] ?? ''));
}
$purchase_unit = $capture['purchase_units'][0] ?? null;
$payments = $purchase_unit['payments'] ?? null;
$captures = null;
if ($payments && isset($payments['captures'][0])) {
$captures = $payments['captures'][0];
}
$transaction_id = $captures['id'] ?? null;
$amount = $captures['amount']['value'] ?? ($purchase_unit['amount']['value'] ?? null);
$currency = $captures['amount']['currency_code'] ?? ($purchase_unit['amount']['currency_code'] ?? null);
// Store to database using MySQLi prepared statements
$mysqli = get_db_connection();
$stmt = $mysqli->prepare("INSERT INTO transactions
(order_id, transaction_id, payer_id, payer_email, payer_name, amount, currency, status, raw_response)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
if (!$stmt) {
http_response_code(500);
echo json_encode(['error' => 'DB prepare failed: ' . $mysqli->error]);
exit;
}
$raw_response = json_encode($capture);
$stmt->bind_param('sssssssss', $orderID, $transaction_id, $payer_id, $payer_email, $payer_name, $amount, $currency, $status, $raw_response);
$exec = $stmt->execute();
if (!$exec) {
http_response_code(500);
echo json_encode(['error' => 'DB insert failed: ' . $stmt->error]);
$stmt->close();
exit;
}
$db_id = $stmt->insert_id;
$stmt->close();
$mysqli->close();
// Return success response with encoded DB id
echo json_encode(['success' => true, 'db_id' => base64_encode($db_id), 'capture' => $capture]);
?>
After capturing the order and storing the transaction details, we can create a simple PHP page to display the transaction information to the user. This page will retrieve the transaction ID from the query parameters, fetch the details from the database, and display them in a readable format.
<?php
// Include configuration file
require_once __DIR__ . '/config.php';
// Get transaction ID from query parameter
$id = isset($_GET['id']) ? intval(base64_decode($_GET['id'])) : 0;
if ($id <= 0) {
echo 'Invalid ID';
exit;
}
// Fetch transaction details from database
$mysqli = get_db_connection();
$stmt = $mysqli->prepare('SELECT id, order_id, transaction_id, payer_id, payer_email, payer_name, amount, currency, status, created_at, raw_response FROM transactions WHERE id = ?');
$stmt->bind_param('i', $id);
$stmt->execute();
$res = $stmt->get_result();
$row = $res->fetch_assoc();
$stmt->close();
$mysqli->close();
if (!$row) {
echo 'Transaction not found';
exit;
}
?>
<h1>Payment Success</h1>
<p><strong>Order ID:</strong> <?php echo htmlspecialchars($row['order_id']); ?></p>
<p><strong>Transaction ID:</strong> <?php echo htmlspecialchars($row['transaction_id']); ?></p>
<p><strong>Payer ID:</strong> <?php echo htmlspecialchars($row['payer_id']); ?></p>
<p><strong>Payer:</strong> <?php echo htmlspecialchars($row['payer_name'] . ' (' . $row['payer_email'] . ')'); ?></p>
<p><strong>Amount:</strong> <?php echo htmlspecialchars($row['currency'] . ' ' . $row['amount']); ?></p>
<p><strong>Status:</strong> <?php echo htmlspecialchars($row['status']); ?></p>
<p><strong>Created:</strong> <?php echo htmlspecialchars($row['created_at']); ?></p>
Once the integration is completed and the payment process works as expected in the sandbox environment, you can proceed to make the PayPal Standard Checkout payment gateway live for real transactions.
To make the PayPal Standard Checkout payment gateway live, you need to switch from the PayPal sandbox environment to the live environment. This involves updating your API credentials in your configuration file.
define('PAYPAL_CLIENT_ID', 'YOUR_PAYPAL_LIVE_CLIENT_ID');
define('PAYPAL_SECRET', 'YOUR_PAYPAL_LIVE_SECRET');
define('PAYPAL_SANDBOX', FALSE);
If you are using PayPal checkout for the DIGITAL_GOODS and want to disable the shipping address requirement for debit/credit card payments through PayPal Checkout, you can do so by modifying the order creation request to include the appropriate parameters.
checkout/orders), you can set the application_context to NO_SHIPPING indicate that no shipping address is needed.$body = [
...
'shipping_preference' => 'NO_SHIPPING'
];
With the PayPal JS-SDK and PHP REST API integration, you can seamlessly accept payments on any website with minimal setup. This approach ensures:
Whether you’re building an eCommerce system, selling digital goods, or implementing subscription billing, this PayPal Standard Checkout workflow is reliable, scalable, and production-ready. Happy coding! 🚀
If you want to explore more advanced features like subscriptions, vaulting, or PayPal advanced card payments, check out the other articles in this series.
Looking for expert assistance to implement or extend this script’s functionality? Submit a Service Request
💰 Budget-friendly • 🌍 Global clients • 🚀 Production-ready solutions
I’m seeing no buttons, where do they get set up in ?
Very nice and well explained, exactly what I was looking for. I notice that only successfull payments are recordered on the database. Thanks anyway the basis are there to start and develope a complete solution. My regards
GretJob, thank you so much!
Hi, thanks for the code. Nice work!
Problem:
Being in sandbox mode there are 5 choices of payment options: Paypal, Sepa, GiroPay, Sofort, Debit/Credit Card
Being in production mode there a only 3 choices of payment options: Paypal, Sepa, Debit/Credit Card
Why are in production mode less payment options?
Do I have to configurate my Paypal Account to offer all option in production mode?
Any help is appreciated.
Ulrich
Yes, you must enable all the payment options in the PayPal Live account for production.
Thanks for the code.
Am having an issue where
https://developer.paypal.com/docs/api/orders/v2/#error-PERMISSION_DENIED
I did successfully get the access token from PayPal but not able to order details.
This is awesome, it does with Credit Cards. Thanks so much brothers and sisters!
This looks fantastic, just what I was looking for, a guide for real people rather than a corporation. May I ask, does this PayPal solution allow customers to make purchases with their credit card, even if they don’t have a PayPal account? I remember the old PayPal Express Checkout did not support credit cards.
How do i use the script multiple times on a website for different products?