Skip to main content

Your First Payment

Complete walkthrough for processing your first payment from start to finish.

This tutorial walks you through a complete payment integration from start to finish.

Prerequisites​

Before starting, ensure you have:

  • Merchant project credentials (client ID, client secret, project ID)
  • Redirect URLs configured in your merchant dashboard
  • A web server capable of receiving webhooks (or ngrok for local testing)

Overview​

A typical payment flow consists of these steps:

Step 1: Authenticate​

async function authenticate(clientId, clientSecret) {
const response = await fetch(
'https://api.paysera.com/auth/realms/Paysera/protocol/openid-connect/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
}),
}
);

if (!response.ok) {
throw new Error(`Authentication failed: ${await response.text()}`);
}

const data = await response.json();
console.log('Authenticated successfully!');
return data.access_token;
}

const accessToken = await authenticate(
process.env.PAYSERA_CLIENT_ID,
process.env.PAYSERA_CLIENT_SECRET
);

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600,
"token_type": "Bearer"
}

Step 2: Create a Payment Order​

The payment order contains the transaction details.

Amount Format

All amounts use minor currency units: "2500" in request = €25.00

const orderResponse = await fetch(
'https://api.paysera.com/merchant-order/integration/v1/orders',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
project_id: process.env.PAYSERA_PROJECT_ID,
redirect_urls: {
success_url: 'https://your-site.com/checkout/success?order_id=12345',
failure_url: 'https://your-site.com/checkout/failure?order_id=12345',
callback_url: 'https://your-site.com/webhooks/paysera',
},
metadata: {
platform: 'custom',
platform_version: '1.0.0',
},
purchase: {
reference: 'ORDER-12345',
amount: '2500', // €25.00 in cents
currency: 'EUR',
},
}),
}
);

const order = await orderResponse.json();
console.log('Order ID:', order.id);

Response:

{
"id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"projectId": "YOUR_PROJECT_ID",
"status": "pending_payment",
"amount": 2500,
"currency": "EUR",
"reference": "ORDER-12345",
"createdAt": 1736433000,
"updatedAt": 1736433000
}

The payment link generates a URL where customers complete their payment.

const linkResponse = await fetch(
'https://api.paysera.com/checkout-payment-link/integration/v1/payment-links',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
order_id: order.id,
name: 'Order #12345',
lifetime: 3600,
experience: {
language: 'en',
},
purchase: {
amount: 2500,
},
payer_information: {
name: 'John Doe',
email: 'john.doe@example.com',
},
}),
}
);

const paymentLink = await linkResponse.json();
console.log('Redirect customer to:', paymentLink.link.url);

Response:

{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"link": {
"url": "https://checkout.paysera.com/pay/c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f"
},
"status": "active",
"amount": 2500,
"currency": "EUR",
"expiredAt": 1736436600,
"createdAt": 1736433000
}

Step 4: Redirect the Customer​

Send the customer to the payment URL:

<?php

// In your checkout controller
header('Location: ' . $response->getPaymentUrl());
exit;

Or in JavaScript:

window.location.href = paymentUrl;

Step 5: Handle the Webhook Callback​

When the payment completes, Paysera sends a POST request to your callback URL.

const crypto = require('crypto');

// Express.js webhook handler
app.post('/webhooks/paysera', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString();
const signature = req.headers['x-paysera-signature'];
const secret = process.env.PAYSERA_CLIENT_SECRET;

// Verify HMAC-SHA256 signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).send('Invalid signature');
}

const data = JSON.parse(payload);
const status = data.order.status;
const reference = data.order.reference;

if (status === 'paid') {
updateOrderStatus(reference, 'paid');
sendConfirmationEmail(reference);
}

res.status(200).send('OK');
});

Step 6: Handle Success/Failure Redirects​

After completing (or failing) the payment, customers are redirected to your success or failure URLs.

<?php

// checkout/success.php
$orderId = $_GET['order_id'] ?? null;

if ($orderId) {
// Show success message
// The order status should be verified via webhook, not URL parameters
echo "Thank you for your order!";
}
<?php

// checkout/failure.php
$orderId = $_GET['order_id'] ?? null;

if ($orderId) {
// Show failure message with retry option
echo "Payment failed. Please try again.";
}

Complete Code Example​

Here's a complete working example using direct REST API calls:

// checkout.js - Initiates payment (Node.js / Express handler)

const AUTH_URL = 'https://api.paysera.com/auth/realms/Paysera/protocol/openid-connect/token';
const ORDERS_URL = 'https://api.paysera.com/merchant-order/integration/v1/orders';
const LINKS_URL = 'https://api.paysera.com/checkout-payment-link/integration/v1/payment-links';

async function initiatePayment(order) {
// 1. Authenticate
const tokenResp = await fetch(AUTH_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.PAYSERA_CLIENT_ID,
client_secret: process.env.PAYSERA_CLIENT_SECRET,
}),
});
if (!tokenResp.ok) throw new Error(`Auth failed: ${await tokenResp.text()}`);
const { access_token: accessToken } = await tokenResp.json();

const authHeaders = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};

// 2. Create payment order
const orderResp = await fetch(ORDERS_URL, {
method: 'POST',
headers: authHeaders,
body: JSON.stringify({
project_id: process.env.PAYSERA_PROJECT_ID,
redirect_urls: {
success_url: `https://your-site.com/success?order=${order.id}`,
failure_url: `https://your-site.com/failure?order=${order.id}`,
callback_url: 'https://your-site.com/webhook',
},
purchase: {
reference: order.id,
amount: String(order.amount), // cents
currency: order.currency,
},
}),
});
if (!orderResp.ok) throw new Error(`Order failed: ${await orderResp.text()}`);
const createdOrder = await orderResp.json();

// 3. Create payment link
const linkResp = await fetch(LINKS_URL, {
method: 'POST',
headers: authHeaders,
body: JSON.stringify({
order_id: createdOrder.id,
name: `Order ${order.id}`,
lifetime: 3600,
experience: { language: 'en' },
purchase: { amount: order.amount },
payer_information: { email: order.customerEmail },
}),
});
if (!linkResp.ok) throw new Error(`Link failed: ${await linkResp.text()}`);
const { link } = await linkResp.json();

return link.url;
}

// Express handler
app.post('/checkout', async (req, res) => {
try {
const paymentUrl = await initiatePayment({
id: `ORDER-${Date.now()}`,
amount: 2500, // 25.00 EUR in cents
currency: 'EUR',
customerEmail: 'customer@example.com',
});
res.redirect(paymentUrl);
} catch (err) {
console.error('Payment initialization failed:', err);
res.redirect('/checkout/error');
}
});

Next Steps​