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​
- JavaScript
- Python
- cURL
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
);
import os
import requests
def authenticate(client_id: str, client_secret: str) -> str:
response = requests.post(
'https://api.paysera.com/auth/realms/Paysera/protocol/openid-connect/token',
data={
'grant_type': 'client_credentials',
'client_id': client_id,
'client_secret': client_secret,
},
headers={'Content-Type': 'application/x-www-form-urlencoded'},
)
response.raise_for_status()
print('Authenticated successfully!')
return response.json()['access_token']
access_token = authenticate(
os.environ['PAYSERA_CLIENT_ID'],
os.environ['PAYSERA_CLIENT_SECRET']
)
curl -X POST https://api.paysera.com/auth/realms/Paysera/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_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.
All amounts use minor currency units: "2500" in request = €25.00
- JavaScript
- Python
- cURL
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);
order_response = requests.post(
'https://api.paysera.com/merchant-order/integration/v1/orders',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
},
json={
'project_id': os.environ['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',
},
},
)
order = order_response.json()
print(f"Order ID: {order['id']}")
curl -X POST https://api.paysera.com/merchant-order/integration/v1/orders \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "YOUR_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": {
"referrer": "https://your-site.com",
"platform": "custom",
"platform_version": "1.0.0"
},
"purchase": {
"reference": "ORDER-12345",
"amount": "2500",
"currency": "EUR"
}
}'
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
}
Step 3: Create a Payment Link​
The payment link generates a URL where customers complete their payment.
- JavaScript
- Python
- cURL
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);
link_response = requests.post(
'https://api.paysera.com/checkout-payment-link/integration/v1/payment-links',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
},
json={
'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',
},
},
)
payment_link = link_response.json()
print(f"Redirect customer to: {payment_link['link']['url']}")
curl -X POST https://api.paysera.com/checkout-payment-link/integration/v1/payment-links \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"name": "Order #12345",
"lifetime": 3600,
"experience": {
"language": "en"
},
"purchase": {
"amount": 2500
},
"payer_information": {
"name": "John Doe",
"email": "john.doe@example.com"
}
}'
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.
- JavaScript
- Python
- Manual Verification (PHP)
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');
});
import hmac
import hashlib
import os
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks/paysera', methods=['POST'])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get('X-Paysera-Signature', '')
secret = os.environ['PAYSERA_CLIENT_SECRET']
# Verify HMAC-SHA256 signature
expected_signature = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_signature, signature):
return 'Invalid signature', 401
data = request.get_json()
status = data['order']['status']
reference = data['order']['reference']
if status == 'paid':
update_order_status(reference, 'paid')
send_confirmation_email(reference)
return 'OK', 200
<?php
// Get the signature from headers
$signature = $_SERVER['HTTP_X_PAYSERA_SIGNATURE'] ?? '';
$payload = file_get_contents('php://input');
$secret = getenv('PAYSERA_CLIENT_SECRET');
// Verify HMAC-SHA256 signature
$expectedSignature = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expectedSignature, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
// Parse the callback data
$data = json_decode($payload, true);
$status = $data['order']['status'];
$reference = $data['order']['reference'];
// Process based on status
if ($status === 'paid') {
updateOrderStatus($reference, 'paid');
}
http_response_code(200);
echo '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​
- Learn more about API Integration for advanced features
- Understand Payment Statuses in detail