Creating a Payment Intent
Initiate a checkout session by callingPOST /payments to retrieve a checkout_url. Redirect your customers to this URL to complete payment.
<?php
// Initialize integration parameters
$baseUrl = 'https://pay.yourbrand.com/api/v1';
$apiKey = 'op_live_xxxxxxxxxxxxxxxxxxxxxx';
$payload = [
'amount' => '500.00',
'currency' => 'BDT',
'callback_url' => 'https://my-store.com/webhooks/ownpay',
'redirect_url' => 'https://my-store.com/checkout/success',
'cancel_url' => 'https://my-store.com/checkout/cancel',
'customer_email' => '[email protected]',
'customer_name' => 'John Doe',
'reference' => 'ORDER-10024'
];
// Execute POST request via cURL
$ch = curl_init("$baseUrl/payments");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
'Accept: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 201 && ($result['success'] ?? false)) {
$checkoutUrl = $result['data']['checkout_url'];
// Redirect the customer to the white-label checkout flow
header("Location: $checkoutUrl");
exit;
} else {
echo "Error creating payment: " . ($result['error'] ?? 'Unknown API error');
}
import axios from 'axios';
const baseUrl = 'https://pay.yourbrand.com/api/v1';
const apiKey = 'op_live_xxxxxxxxxxxxxxxxxxxxxx';
async function createPayment() {
try {
const response = await axios.post(`${baseUrl}/payments`, {
amount: '500.00',
currency: 'BDT',
callback_url: 'https://my-store.com/webhooks/ownpay',
redirect_url: 'https://my-store.com/checkout/success',
cancel_url: 'https://my-store.com/checkout/cancel',
customer_email: '[email protected]',
customer_name: 'John Doe',
reference: 'ORDER-10024'
}, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
if (response.data.success) {
console.log('Checkout URL:', response.data.data.checkout_url);
console.log('Payment ID:', response.data.data.payment_id);
}
} catch (error) {
console.error('Error:', error.response?.data?.error || error.message);
}
}
createPayment();
import requests
base_url = "https://pay.yourbrand.com/api/v1"
api_key = "op_live_xxxxxxxxxxxxxxxxxxxxxx"
payload = {
"amount": "500.00",
"currency": "BDT",
"callback_url": "https://my-store.com/webhooks/ownpay",
"redirect_url": "https://my-store.com/checkout/success",
"cancel_url": "https://my-store.com/checkout/cancel",
"customer_email": "[email protected]",
"customer_name": "John Doe",
"reference": "ORDER-10024"
}
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
response = requests.post(f"{base_url}/payments", json=payload, headers=headers)
result = response.json()
if response.status_code == 201 and result.get("success"):
checkout_url = result["data"]["checkout_url"]
print(f"Redirecting user to: {checkout_url}")
else:
print(f"Error: {result.get('error', 'Unknown API error')}")
curl -X POST https://pay.yourbrand.com/api/v1/payments \
-H "Authorization: Bearer op_live_xxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"amount": "500.00",
"currency": "BDT",
"callback_url": "https://my-store.com/webhooks/ownpay",
"redirect_url": "https://my-store.com/checkout/success",
"cancel_url": "https://my-store.com/checkout/cancel",
"customer_email": "[email protected]",
"customer_name": "John Doe",
"reference": "ORDER-10024"
}'
Webhook Signature Verification
To ensure webhook events originate from your trusted OwnPay instance, always verify theX-OwnPay-Signature header against your Webhook Secret before acting on them.
<?php
// Retrieve secret from configuration
$webhookSecret = getenv('OWNPAY_WEBHOOK_SECRET');
// Fetch the raw HTTP POST request body
$rawPayload = file_get_contents('php://input');
// Retrieve custom headers
$signatureHeader = $_SERVER['HTTP_X_OWNPAY_SIGNATURE'] ?? '';
$timestampHeader = $_SERVER['HTTP_X_OWNPAY_TIMESTAMP'] ?? '';
// Calculate the expected HMAC-SHA256 signature
$expectedSignature = hash_hmac('sha256', $timestampHeader . '.' . $rawPayload, $webhookSecret);
// Safe timing-attack-resistant comparison
if (!hash_equals('sha256=' . $expectedSignature, $signatureHeader)) {
http_response_code(401);
exit('Invalid signature');
}
// Signature is valid, proceed with event parsing
$payload = json_decode($rawPayload, true);
if ($payload['event'] === 'payment.paid') {
$transaction = $payload['data'];
$orderRef = $transaction['metadata']['order_id'];
$amount = $transaction['amount'];
// Process order fulfillment asynchronously
// ...
}
http_response_code(200);
echo 'OK';
import express from 'express';
import crypto from 'crypto';
const app = express();
const webhookSecret = process.env.OWNPAY_WEBHOOK_SECRET;
// Expose raw parser for signature checks
app.post('/webhooks/ownpay', express.raw({ type: 'application/json' }), (req, res) => {
const signatureHeader = req.headers['x-ownpay-signature'];
const timestampHeader = req.headers['x-ownpay-timestamp'];
if (!signatureHeader || !timestampHeader) {
return res.status(400).send('Missing webhook headers');
}
// Compute timing-safe hash
const hmac = crypto.createHmac('sha256', webhookSecret);
hmac.update(`${timestampHeader}.${req.body.toString()}`);
const computedSignature = `sha256=${hmac.digest('hex')}`;
const isValid = crypto.timingSafeEqual(
Buffer.from(computedSignature, 'utf8'),
Buffer.from(signatureHeader, 'utf8')
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Parse payload content after verification
const payload = JSON.parse(req.body.toString());
if (payload.event === 'payment.paid') {
const transaction = payload.data;
console.log(`Fulfilling order ${transaction.metadata.order_id} for BDT ${transaction.amount}`);
// Process order fulfillment
}
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Listening for webhooks on port 3000'));
Querying Transaction Status
To avoid relying strictly on webhooks, query the status endpoint directly when a customer lands back on your redirection page.PHP
<?php
$baseUrl = 'https://pay.yourbrand.com/api/v1';
$apiKey = 'op_live_xxxxxxxxxxxxxxxxxxxxxx';
$paymentId = 'pi_01j0x8kz7m3wd4b9qfhec5rtg6'; // Retrieved UUID
$ch = curl_init("$baseUrl/payments/$paymentId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Accept: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 200 && ($result['success'] ?? false)) {
$payment = $result['data'];
switch ($payment['status']) {
case 'paid':
echo "Transaction successful! Reference: " . $payment['id'];
break;
case 'processing':
echo "Payment is processing, awaiting settlement confirmation.";
break;
case 'failed':
echo "Payment failed.";
break;
default:
echo "Payment status: " . $payment['status'];
break;
}
}
Extension Hooks (Plugin Development)
OwnPay plugins tap into the core lifecycle event loop using action filters and listeners.Custom Payment Fee Filter
You can modify transaction amounts dynamically based on payment parameters.PHP
use OwnPay\Event\EventManager;
// Add a 2% convenience fee if payment is processed on Stripe credit card gateway
EventManager::addFilter('payment.amount', function(string $amount, array $payment): string {
if (($payment['gateway'] ?? '') === 'stripe-card') {
// ALWAYS use bcmath for high-precision decimal operations!
return bcmul($amount, '1.02', 2);
}
return $amount;
}, 10, 2);
Event Action Listeners
Execute background actions (such as email receipt triggers) when payments succeed.PHP
use OwnPay\Event\EventManager;
EventManager::addAction('payment.completed', function(array $transaction): void {
$email = $transaction['customer']['email'] ?? null;
$amount = $transaction['amount'];
$currency = $transaction['currency'];
if ($email) {
// Trigger background receipt delivery
mail($email, "Receipt for payment", "Thank you for your payment of $amount $currency.");
}
}, 10, 1);