Build an Advanced Kayana Integration with Stripe in PHP
Last updated Mar 17, 2026
Build an advanced Kayana integration to securely collect card payments using Kayana's Payment API powered by Stripe for your e-commerce store.
1. Overview
The Kayana Payment API enables you to accept payments without building your own PCI-compliant checkout.
This guide walks you through integrating the Stripe Embedded Checkout flow using PHP in sandbox mode.
2. What You'll Build
In this guide, you will be able to:
- Create a payment instance on your PHP server
- Retrieve the Stripe publishable key
- Initialise Stripe on the frontend
- Render Stripe's secure Embedded Checkout capsule
- Complete the payment securely
3. Before You Begin
You'll need:
- A Kayana property ID
- Access to the sandbox environment
- Basic knowledge of PHP
- A PHP backend server (PHP 7.4+ recommended)
- A frontend (HTML/JavaScript or any framework)
4. Sandbox vs Production
Sandbox
Safe test environment:
- Uses Stripe test keys
- No real transactions
- Ideal for development and testing
Production
- Requires an authorisation token
- Uses live Stripe keys
- Processes real payments
5. How It Works
The Stripe payment flow consists of:
| Component | Description |
|---|---|
| Server-side (PHP) | Create a payment instance using Kayana's API |
| Server receives | client_secret and publishable_key |
| Client-side | Initialise Stripe and render Embedded Checkout |
| Stripe | Handles payment confirmation securely |
6. Server Setup (PHP)
Base URL
| Environment | URL |
|---|---|
| Sandbox | https://integration.dev.kayana.co.uk |
| Production | https://integration.kayana.co.uk |
Project Structure
your-project/ ├── config/ │ └── config.php ├── api/ │ └── create-payment.php ├── public/ │ └── index.html └── .htaccess
7. Step 1 — Create a Payment (PHP Backend)
Create a PHP API endpoint that calls Kayana's payment API.
Configuration File
config/config.php
<?php
return [ 'kayana' => [ 'api_key' => 'YOUR_API_KEY', 'base_url' => 'https://integration.dev.kayana.co.uk', 'property_id' => 'Prop_abc123example', ], 'stripe' => [ 'psp_code' => 'STRIPE', ], ];
⚠️ Important: Never expose your API key in the frontend or commit it to version control.
PHP Server Implementation
api/create-payment.php
<?php
header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type');
// Handle preflight requests if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit(); }
// Only allow POST requests if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode([ 'status' => false, 'message' => 'Method not allowed' ]); exit(); }
// Load configuration $config = require_once __DIR__ . '/../config/config.php';
// Get request body $input = json_decode(file_get_contents('php://input'), true);
// Set default values or use provided values $amount = $input['amount'] ?? 12.00; $currency = $input['currency_code'] ?? 'GBP'; $customerEmail = $input['customer_email'] ?? 'customer@example.com';
// Prepare request payload $payload = [ 'property_id' => $config['kayana']['property_id'], 'psp_code' => $config['stripe']['psp_code'], 'currency_code' => $currency, 'amount' => $amount, 'customer_email' => $customerEmail, ];
// Initialize cURL $ch = curl_init();
curl_setopt_array($ch, [ CURLOPT_URL => $config['kayana']['base_url'] . '/business/payment', CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-api-key: ' . $config['kayana']['api_key'], 'type: business', ], CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, ]);
$response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch);
curl_close($ch);
// Handle cURL errors if ($curlError) { http_response_code(500); echo json_encode([ 'status' => false, 'message' => 'Connection error: ' . $curlError ]); exit(); }
// Handle HTTP errors if ($httpCode !== 200) { http_response_code($httpCode); echo json_encode([ 'status' => false, 'message' => 'Payment creation failed', 'http_code' => $httpCode ]); exit(); }
// Return the response echo $response;
Alternative: Using Guzzle HTTP Client
If you prefer using Composer and Guzzle:
Install Guzzle:
composer require guzzlehttp/guzzle
api/create-payment-guzzle.php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException;
header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit(); }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode([ 'status' => false, 'message' => 'Method not allowed' ]); exit(); }
$config = require_once __DIR__ . '/../config/config.php'; $input = json_decode(file_get_contents('php://input'), true);
$client = new Client([ 'base_uri' => $config['kayana']['base_url'], 'timeout' => 30, ]);
try { $response = $client->post('/business/payment', [ 'headers' => [ 'Content-Type' => 'application/json', 'x-api-key' => $config['kayana']['api_key'], 'type' => 'business', ], 'json' => [ 'property_id' => $config['kayana']['property_id'], 'psp_code' => $config['stripe']['psp_code'], 'currency_code' => $input['currency_code'] ?? 'GBP', 'amount' => $input['amount'] ?? 12.00, 'customer_email' => $input['customer_email'] ?? 'customer@example.com', ], ]);
echo $response->getBody()->getContents();
} catch (RequestException $e) { http_response_code(500); echo json_encode([ 'status' => false, 'message' => 'Payment creation failed: ' . $e->getMessage() ]); }
Request Parameters
| Parameter | Type | Description |
|---|---|---|
| property_id | string | Format Prop_* |
| psp_code | string | STRIPE |
| currency_code | string | GBP, EUR, USD |
| amount | number | Decimal amount (example: 12.0) |
| customer_email | string | Customer email |
Example API Response
{ "status": true, "data": { "instance_id": "Payment_xyz789example", "client_secret": "cs_test_abc123_secret_xyz456", "publishable_key": "pk_test_abc123example", "next_step": { "action": "FORM", "proceed": true }, "transaction_id": "Tx_txn456example" } }
Response Parameters
| Parameter | Description |
|---|---|
| instance_id | Unique payment instance ID |
| client_secret | Stripe Checkout session secret |
| publishable_key | Stripe public key |
| transaction_id | Internal transaction reference |
⚠️ Important: client_secret must only be used on the frontend to initialise Stripe.
8. Step 2 — Frontend Integration (Stripe Embedded Checkout)
HTML/JavaScript Implementation
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Kayana Stripe Payment</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { min-height: 100vh; background: linear-gradient(135deg, #f3e8ff, #e9d5ff); } .checkout-card { max-width: 600px; border-radius: 16px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); } .pay-btn { background: linear-gradient(90deg, #ef4444, #f97316); border: none; height: 52px; font-size: 16px; font-weight: 600; border-radius: 10px; } .btn-spinner { width: 20px; height: 20px; border: 2px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; display: inline-block; vertical-align: middle; } @keyframes spin { to { transform: rotate(360deg); } } #checkout-container { min-height: 400px; } </style> </head> <body> <div> <div> <div> <div> <h5>Stripe Payment</h5> <p>Complete your payment securely</p> </div>
<div id="payment-form-container"> <div> <label>Email Address</label> <input type="email" id="email" placeholder="customer@example.com" required> </div>
<div> <label>Amount</label> <input type="number" id="amount" value="12.00" step="0.01" min="0.50" required> </div>
<div> <label>Currency</label> <select id="currency"> <option value="GBP">GBP - British Pound</option> <option value="EUR">EUR - Euro</option> <option value="USD">USD - US Dollar</option> </select> </div>
<button id="pay-btn"> <span>Open Payment</span> </button>
<div id="error-message"></div> </div>
<div id="checkout-container"></div> </div> </div> </div>
<script src="https://js.stripe.com/clover/stripe.js"></script> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <script> let stripe = null;
function showLoader() { $('#pay-btn') .prop('disabled', true) .html('<span></span> Processing...'); }
function hideLoader() { $('#pay-btn') .prop('disabled', false) .html('<span>Open Payment</span>'); }
function showError(message) { $('#error-message').text(message).removeClass('d-none'); }
function hideError() { $('#error-message').addClass('d-none'); }
$('#pay-btn').on('click', async function() { hideError(); showLoader();
const email = $('#email').val(); const amount = parseFloat($('#amount').val()); const currency = $('#currency').val();
if (!email || !amount) { showError('Please fill in all required fields.'); hideLoader(); return; }
try { const response = await fetch('/api/create-payment.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ customer_email: email, amount: amount, currency_code: currency, }), });
const data = await response.json();
if (data.status && data.data) { stripe = Stripe(data.data.publishable_key); $('#payment-form-container').addClass('d-none');
const checkout = await stripe.initEmbeddedCheckout({ clientSecret: data.data.client_secret, });
checkout.mount('#checkout-container'); } else { showError(data.message || 'Failed to create payment session.'); hideLoader(); } } catch (error) { console.error('Error:', error); showError('An unexpected error occurred. Please try again.'); hideLoader(); } }); </script> </body> </html>
9. Payment Flow
User clicks "Pay" ↓ Frontend calls the PHP API ↓ PHP calls Kayana API ↓ Kayana creates Stripe Checkout session ↓ Returns: client_secret publishable_key ↓ Frontend loads Stripe ↓ Stripe Embedded Checkout appears ↓ User enters card details ↓ Stripe confirms payment
10. Testing in Sandbox
Use Stripe test cards:
| Card Number | Brand | Scenario |
|---|---|---|
| 4242 4242 4242 4242 | Visa | Successful |
| 4000 0025 0000 3155 | Visa | Requires 3D Secure |
| 4000 0000 0000 9995 | Visa | Declined |
Additional Details:
- Expiry → Any future date
- CVC → Any 3 digits
- ZIP → Any valid format
⚠️ Warning: Never use test cards in production.
11. Error Handling
All responses include:
{ "status": false, "message": "Description of error" }
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Invalid API key | Incorrect x-api-key | Verify with Kayana |
| Invalid property ID | Wrong format | Must start with Prop_* |
| Unsupported currency | Not enabled | Contact Kayana |
| Stripe configuration missing | PSP not enabled | Contact Kayana |
12. Webhook Handler (Optional)
api/webhook.php
<?php
$config = require_once __DIR__ . '/../config/config.php';
$payload = file_get_contents('php://input'); $sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? ''; $endpointSecret = $config['stripe']['webhook_secret'] ?? '';
try { $event = json_decode($payload, true);
switch ($event['type']) { case 'checkout.session.completed': $session = $event['data']['object']; // Handle successful payment // Update order status in database error_log('Payment completed: ' . $session['id']); break;
case 'payment_intent.succeeded': $paymentIntent = $event['data']['object']; error_log('PaymentIntent succeeded: ' . $paymentIntent['id']); break;
case 'payment_intent.payment_failed': $paymentIntent = $event['data']['object']; error_log('PaymentIntent failed: ' . $paymentIntent['id']); break;
default: error_log('Unhandled event type: ' . $event['type']); }
http_response_code(200); echo json_encode(['received' => true]);
} catch (Exception $e) { http_response_code(400); echo json_encode(['error' => $e->getMessage()]); }
13. Going Live Checklist
Before production:
Obtain production x-api-key
Switch to production base URL
Use live Stripe publishable key
Test real card transactions
Implement Stripe webhooks
Set up proper error logging
Enable HTTPS on your server
Never:
- Use sandbox keys in production
- Expose secret keys in the frontend
- Log sensitive payment data
14. Security Best Practices
- Store API keys securely - Use environment variables or secure config files
- Validate all input - Sanitise and validate user input on the server
- Use HTTPS - Always serve your payment pages over HTTPS
- Implement CSRF protection - Add CSRF tokens to your forms
- Rate limiting - Implement rate limiting on your API endpoints
15. Support
- Technical Support: Kayana Technical Team
- Production credentials: Contact Kayana's Account Manager
- Documentation: Kayana Help Centre
Next Steps
If you prefer to use RYFT as your Payment Service Provider (PSP), Kayana also supports a full RYFT integration flow. Refer to our guide on Building an advanced Kayana integration in PHP.



