Full Integration Example
Two ready-to-run snippets, pick the one that matches your backend.
- Basic: the order ID (
invoiceId) is known at page-load time. Pass it toinit()and you are done. - CMS pattern: the order does not exist yet at page-load (typical for WooCommerce, Magento, OpenCart, custom platforms). When the customer clicks the PayPal button, your backend creates the order and returns the resulting
invoiceIdthroughonBeforeProcessPayment.
Both snippets pass paymentData (with amount + currency) and token to init(). The component fetches the terminal configuration, loads the PayPal JS SDK with the merchant's clientId, and renders the official PayPal Smart Payment Button.
If the cart amount can change after the button is rendered (quantity edits, coupons), you have two options:
- Fetch values on every click. Skip
paymentDataandtokenatinit()entirely; return fresh values fromonBeforeProcessPaymentbased on the current cart state. This is the simplest path for highly dynamic carts: no re-init needed. - Re-initialize on change. If you do pass
paymentDataandtokenatinit(), callinit()again with fresh values whenever the cart changes. The access token is minted for the values you passed and the backend rejects mismatches.
- Basic
- CMS pattern
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src='https://test-pay.dnapayments.com/components/paypal/paypal-component.js'></script>
<script>
async function renderPayPal() {
const token = 'YOUR_ACCESS_TOKEN';
const paymentData = {
amount: 24,
currency: 'GBP',
invoiceId: 'YOUR_INVOICE_ID',
description: 'Shoes',
paymentSettings: {
terminalId: 'YOUR_TERMINAL_ID',
callbackUrl: 'https://example.com/callback',
failureCallbackUrl: 'https://example.com/failure-callback'
},
customerDetails: {
accountDetails: { accountId: 'uuid000001' },
billingAddress: {
firstName: 'John', lastName: 'Doe',
addressLine1: 'Fulham Rd',
postalCode: 'SW6 1HS', city: 'London', country: 'GB'
},
email: 'example@email.com',
mobilePhone: '+441234567890'
},
orderLines: [{
name: 'Running shoe',
quantity: 1,
unitPrice: 24,
taxRate: 20,
totalAmount: 24,
totalTaxAmount: 4,
imageUrl: 'https://www.example.com/logo.png',
productUrl: 'https://www.example.com/AD6654412.html'
}]
};
const events = {
onClick: () => {
console.log('PayPal button clicked');
},
onError: (error) => {
console.error('PayPal error:', error);
},
onCancel: () => {
console.log('Payment cancelled');
},
onPaymentSuccess: (result) => {
console.log('Payment processed', result);
window.location.href = '/success';
}
};
window.DNAPayments.PayPalComponent.init({
containerElement: document.getElementById('paypal-btn-container'),
terminalId: 'YOUR_TERMINAL_ID',
paymentData,
events,
token
});
}
window.addEventListener('load', renderPayPal);
</script>
</head>
<body>
<div id='paypal-btn-container' style='width: 100%;'></div>
</body>
</html>
Use this shape when the order is created server-side at the moment the customer clicks the PayPal button. onBeforeProcessPayment fires right after the click, calls your backend to create the order, gets a freshly minted access token bound to the new invoiceId, and returns both to the component before the order is sent to DNA Payments and PayPal opens its checkout window.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src='https://test-pay.dnapayments.com/components/paypal/paypal-component.js'></script>
<script>
async function renderPayPal() {
// paymentData passed at init() does NOT contain invoiceId yet.
// amount and currency are required so the access token can be bound
// to the correct total.
const paymentData = {
amount: 24,
currency: 'GBP',
description: 'Shoes',
paymentSettings: {
terminalId: 'YOUR_TERMINAL_ID',
callbackUrl: 'https://example.com/callback',
failureCallbackUrl: 'https://example.com/failure-callback'
},
customerDetails: {
accountDetails: { accountId: 'uuid000001' },
billingAddress: { country: 'GB' }
},
orderLines: [{
name: 'Running shoe',
quantity: 1,
unitPrice: 24,
taxRate: 20,
totalAmount: 24,
totalTaxAmount: 4
}]
};
const events = {
onClick: () => {
console.log('PayPal button clicked');
},
// Fires right after the click, before the order is created on the
// DNA Payments backend and before PayPal opens its checkout window.
// The server creates the order and issues a token bound to it, then
// returns the refreshed paymentData and token. The locked fields
// (amount, currency, delivery address) must match init().
onBeforeProcessPayment: async () => {
const { invoiceId, token } = await fetch('/api/create-order-and-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: paymentData.amount, currency: paymentData.currency })
}).then(r => r.json());
return {
paymentData: { ...paymentData, invoiceId },
token
};
},
onPaymentSuccess: (result) => {
console.log('Payment processed', result);
window.location.href = '/success';
},
onCancel: () => {
console.log('Payment cancelled');
},
onError: (error) => {
console.error('PayPal error:', error);
}
};
window.DNAPayments.PayPalComponent.init({
containerElement: document.getElementById('paypal-btn-container'),
terminalId: 'YOUR_TERMINAL_ID',
paymentData,
events,
token: 'BOOTSTRAP_ACCESS_TOKEN' // replaced by the one returned from onBeforeProcessPayment
});
}
window.addEventListener('load', renderPayPal);
</script>
</head>
<body>
<div id='paypal-btn-container' style='width: 100%;'></div>
</body>
</html>
Replace the script source with the production CDN before going live:
<script src='https://pay.dnapayments.com/components/paypal/paypal-component.js'></script>
See Deployment for the full go-live checklist.