Skip to content
Kayana

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:

  1. Create a payment instance on your PHP server
  2. Retrieve the Stripe publishable key
  3. Initialise Stripe on the frontend
  4. Render Stripe's secure Embedded Checkout capsule
  5. 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:

ComponentDescription
Server-side (PHP)Create a payment instance using Kayana's API
Server receivesclient_secret and publishable_key
Client-sideInitialise Stripe and render Embedded Checkout
StripeHandles payment confirmation securely

6. Server Setup (PHP)

Base URL

EnvironmentURL
Sandboxhttps://integration.dev.kayana.co.uk
Productionhttps://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

ParameterTypeDescription
property_idstringFormat Prop_*
psp_codestringSTRIPE
currency_codestringGBP, EUR, USD
amountnumberDecimal amount (example: 12.0)
customer_emailstringCustomer 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

ParameterDescription
instance_idUnique payment instance ID
client_secretStripe Checkout session secret
publishable_keyStripe public key
transaction_idInternal 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 NumberBrandScenario
4242 4242 4242 4242VisaSuccessful
4000 0025 0000 3155VisaRequires 3D Secure
4000 0000 0000 9995VisaDeclined

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

ErrorCauseSolution
Invalid API keyIncorrect x-api-keyVerify with Kayana
Invalid property IDWrong formatMust start with Prop_*
Unsupported currencyNot enabledContact Kayana
Stripe configuration missingPSP not enabledContact 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

  1. Store API keys securely - Use environment variables or secure config files
  2. Validate all input - Sanitise and validate user input on the server
  3. Use HTTPS - Always serve your payment pages over HTTPS
  4. Implement CSRF protection - Add CSRF tokens to your forms
  5. 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.

Still need a hand?Contact support← All help