Developer Documentation

Musqet Payment Integration - Developer Documentation#

Overview#

This documentation covers how to integrate Musqet payments directly (without a CMS plugin) using standalone PHP examples. Two gateway backends are supported, each with its own example directory:

GatewayExamples DirectoryPayment Methods
Cardstreamcardstream-gateway-examples/Card (Hosted Fields), Google Pay, Apple Pay
Encodedencoded-gateway-examples/Card (HPF), Google Pay
BitcoinBoth directoriesBitcoin (gateway-agnostic)

The two gateways differ significantly in their architecture:

CardstreamEncoded
AuthenticationSHA-512 signature over fieldsOAuth 2.0 (JWT tokens)
Request formatForm-encodedJSON
Card JS libraryhostedfields.js (jQuery plugin)hpf-3.1.2.min.js (custom events on document)
Success checkresponseCode === '0'status in ['succeeded','paid','processed']
Country codesISO 3166-1 numeric (826, 840)ISO 3166-1 alpha-3 (GBR, USA)
Currency codesISO 4217 numeric (826, 840)ISO 4217 alpha (GBP, USD)
Google PayYes (via native Google Pay JS API)Yes (via APM library)
Apple PayYes (via native ApplePaySession API)No

Cardstream Gateway#

This section covers integration with the Cardstream gateway Cardstream uses SHA-512 signature authentication (no OAuth), form-encoded requests, and the hostedfields.js jQuery plugin for embedded card payments.

Two checkout modes are supported:

  1. Redirect Mode - Customer is redirected to Cardstream's hosted payment page
  2. Embedded Mode - Cardstream's hosted fields are embedded directly in your checkout page (requires jQuery)
  3. Google Pay - Native Google Pay button using Google's Pay API, processed via Cardstream's /direct/ endpoint
  4. Apple Pay - Native Apple Pay button using Safari's ApplePaySession API, processed via Cardstream's /direct/ endpoint

Table of Contents (Cardstream)#

  1. Gateway URL
  2. Authentication (Signature)
  3. Redirect Mode
  4. Embedded Mode (Hosted Fields)
  5. Google Pay
  6. Apple Pay
  7. 3D Secure
  8. Webhooks / Callbacks
  9. Numeric Code Formats
  10. Integration Testing
  11. Running the Cardstream Examples

Cardstream Gateway URL#

The Cardstream gateway URL is specific to the Payment Services Provider. For Musqet, the gateway is always https://payments.musqet.tech:

PathUsage
https://payments.musqet.tech/direct/Server-to-server transactions
https://payments.musqet.tech/hosted/Redirect / Hosted Payment Page
https://payments.musqet.tech/sdk/web/v1/js/hostedfields.min.jsHosted Fields JavaScript library

Musqet gateway URL: https://payments.musqet.tech


Cardstream Authentication (Signature)#

Cardstream does not use OAuth or bearer tokens. Instead, every request is authenticated with a SHA-512 signature computed over the sorted, URL-encoded fields plus the shared secret.

Signature Algorithm#

function generateSignature($fields, $sharedSecret) {
    // 1. Remove any existing signature
    unset($fields['signature']);

    // 2. Sort alphabetically by key
    ksort($fields);

    // 3. Build URL-encoded query string
    $query = http_build_query($fields, '', '&');

    // 4. Append the shared secret (no separator)
    // 5. Hash with SHA-512
    return hash('sha512', $query . $sharedSecret);
}

Verifying a Response Signature#

function verifyResponseSignature($response, $sharedSecret) {
    $receivedSig = $response['signature'] ?? '';
    unset($response['signature']);
    $expectedSig = generateSignature($response, $sharedSecret);
    return hash_equals($expectedSig, $receivedSig);
}

Cardstream Redirect Mode#

The simplest integration. Build signed form fields and POST them to the gateway's hosted payment page.

Flow#

  1. Build transaction fields with your merchant ID, order details, and redirect URL
  2. Sign the fields with SHA-512
  3. Render an auto-submit HTML form POSTing to https://payments.musqet.tech/hosted/
  4. Customer completes payment on the gateway's hosted page
  5. Gateway redirects back to your redirectURL with a signed POST containing the result

Building Redirect Fields (Backend)#

$api = new CardstreamApi($merchantId, $sharedSecret, $gatewayUrl);

$fields = [
    'merchantID'        => $merchantId,
    'action'            => 'SALE',
    'type'              => '1',      // 1 = ecommerce, 2 = MOTO
    'amount'            => '9999',   // Minor units (pence/cents)
    'currencyCode'      => '826',    // ISO 4217 numeric (826 = GBP)
    'countryCode'       => '826',    // ISO 3166-1 numeric (826 = GB)
    'orderRef'          => 'ORDER_123',
    'transactionUnique' => uniqid('ORDER_123-'),
    'redirectURL'       => 'https://yoursite.com/api/webhook.php',
    'customerName'      => 'John Doe',
    'customerEmail'     => 'john@example.com',
    'customerAddress'   => '123 Test Street',
    'customerCity'      => 'London',
    'customerPostCode'  => 'SW1A 1AA',
    'customerCountryCode' => '826',
];

$fields['signature'] = $api->generateSignature($fields);

Auto-Submit Form (Frontend)#

<form id="redirect-form" method="POST" action="https://payments.musqet.tech/hosted/">
    <input type="hidden" name="merchantID" value="...">
    <input type="hidden" name="action" value="SALE">
    <!-- ... all signed fields ... -->
    <input type="hidden" name="signature" value="...">
</form>
<script>document.getElementById('redirect-form').submit();</script>

Handling the Redirect Callback#

// The gateway POSTs form-encoded data back to your redirectURL
$response = $_POST;

// Always verify the signature first
if (!$api->verifyResponseSignature($response)) {
    die('Invalid signature');
}

if ($response['responseCode'] === '0') {
    // Payment successful
    $xref = $response['xref']; // Save for refunds
} else {
    // Payment failed
    echo 'Error: ' . $response['responseMessage'];
}

Cardstream Embedded Mode (Hosted Fields)#

Hosted fields embed secure, PCI-compliant card input fields in your checkout page using the hostedfields.js jQuery plugin.

Flow#

  1. Frontend: Load jQuery + hostedfields.js from the gateway
  2. Frontend: Initialize the jQuery hostedForm plugin on your form
  3. Frontend: User enters card details into hosted field iframes
  4. Frontend: Call getPaymentDetails() to get a paymentToken
  5. Backend: Send paymentToken + order details to https://payments.musqet.tech/direct/
  6. Backend: Handle response (success, 3DS, or error)

Prerequisites#

HTML Structure#

<!-- jQuery is required by hostedfields.js -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<form id="payment-form">
    <div class="form-group">
        <label>Card Number</label>
        <input type="hostedfield:cardNumber" placeholder="4444 3333 2222 1111">
    </div>
    <div class="form-group">
        <label>Expiry Date</label>
        <input type="hostedfield:cardExpiryDate" placeholder="MM/YY">
    </div>
    <div class="form-group">
        <label>CVV</label>
        <input type="hostedfield:cardCVV" placeholder="123">
    </div>
    <button type="submit" id="pay-button">Pay Now</button>
</form>

Note the special type attributes: hostedfield:cardNumber, hostedfield:cardExpiryDate, hostedfield:cardCVV. These tell the library which fields to replace with secure iframes.

JavaScript Initialization#

// Initialize the jQuery hostedForm plugin
$('#payment-form').hostedForm({
    autoSetup: true,
    autoSubmit: false,  // We handle submission ourselves
    fields: {
        any: {
            nativeEvents: true
        }
    }
});

var hostedFormInstance = $('#payment-form').hostedForm('instance');

// Listen for events
$('#payment-form').on('hostedfield:ready', function() {
    console.log('Hosted fields ready');
});

$('#payment-form').on('hostedform:valid', function() {
    document.getElementById('pay-button').disabled = false;
});

$('#payment-form').on('hostedform:invalid', function() {
    // Form has validation errors
});

$('#payment-form').on('hostedform:error', function(e, data) {
    console.error('Error:', data);
});

Getting the Payment Token#

// On form submit, get the payment token
hostedFormInstance.getPaymentDetails({}, true).then(function(details) {
    if (details.success) {
        // Send details.paymentToken to your backend
        submitToBackend(details.paymentToken);
    } else {
        alert(details.message || 'Card validation failed.');
    }
}).catch(function(err) {
    alert(err.message || 'Payment failed.');
});

Processing the Token (Backend)#

$api = new CardstreamApi($merchantId, $sharedSecret, $gatewayUrl);

$fields = [
    'merchantID'         => $merchantId,
    'action'             => 'SALE',
    'type'               => '1',
    'amount'             => (string) $api->convertAmountToMinor($amount),
    'currencyCode'       => (string) $api->getCurrencyCode($currency),
    'countryCode'        => (string) $api->getCountryCodeNumeric($billingCountry),
    'orderRef'           => $orderId,
    'transactionUnique'  => uniqid($orderId . '-'),
    'redirectURL'        => $callbackUrl,
    'paymentToken'       => $paymentToken,      // From hostedfields.js
    'deviceIpAddress'    => $_SERVER['REMOTE_ADDR'],
    'threeDSRedirectURL' => $threeDSCallbackUrl, // For 3DS redirects
    'customerName'       => 'John Doe',
    'customerEmail'      => 'john@example.com',
    // ... other customer fields
];

$response = $api->sendDirectTransaction($fields);

if ($api->isSuccess($response)) {
    // responseCode === '0' — payment successful
    $xref = $response['xref']; // Save for refunds
} elseif ($api->requires3ds($response)) {
    // responseCode === '65802' — 3DS required
    // See 3D Secure section below
} else {
    // Payment failed
    $error = $response['responseMessage'];
}

Cardstream Google Pay#

This documentation describes Google Pay support for embedded mode. In redirect mode, Google Pay is supported by the gateway directly via the hosted payment page. The embedded integration uses Google's native Pay API (pay.google.com/gp/p/js/pay.js) on the frontend, with the token sent to Cardstream's /direct/ endpoint using the paymentMethod=googlepay field.

Flow#

  1. Frontend loads Google's Pay API script
  2. Create a PaymentsClient and call isReadyToPay() to check device/browser support
  3. Render the Google Pay button
  4. Customer clicks button → Google Pay sheet opens → customer authorizes
  5. Frontend receives PaymentData with token at paymentMethodData.tokenizationData.token
  6. Frontend sends the raw token string to your backend
  7. Backend POSTs to Cardstream /direct/ with paymentMethod=googlepay + paymentToken=<token> alongside standard transaction fields
  8. Cardstream returns standard response (responseCode=0 for success, 65802 for 3DS)

Frontend: Initialize Google Pay#

<script src="https://pay.google.com/gp/p/js/pay.js"></script>

<script>
// Tokenization specification — gateway must be 'cardstream'
function getTokenizationSpecification() {
    return {
        type: 'PAYMENT_GATEWAY',
        parameters: {
            gateway: 'cardstream',
            gatewayMerchantId: 'YOUR_CARDSTREAM_MERCHANT_ID'
        }
    };
}

// Allowed payment methods
function getBaseCardPaymentMethod() {
    return {
        type: 'CARD',
        parameters: {
            allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
            allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX']
        }
    };
}

// Create client — use 'TEST' for sandbox, 'PRODUCTION' for live
var paymentsClient = new google.payments.api.PaymentsClient({
    environment: 'TEST'
});

// Check if Google Pay is available
paymentsClient.isReadyToPay({
    apiVersion: 2,
    apiVersionMinor: 0,
    allowedPaymentMethods: [getBaseCardPaymentMethod()]
}).then(function(response) {
    if (response.result) {
        // Mount the Google Pay button
        var button = paymentsClient.createButton({
            onClick: onGooglePayClicked,
            buttonColor: 'black',
            buttonType: 'pay',
            buttonSizeMode: 'fill'
        });
        document.getElementById('googlepay-container').appendChild(button);
    }
});
</script>

Frontend: Handle Google Pay Button Click#

function onGooglePayClicked() {
    var paymentDataRequest = {
        apiVersion: 2,
        apiVersionMinor: 0,
        allowedPaymentMethods: [{
            ...getBaseCardPaymentMethod(),
            tokenizationSpecification: getTokenizationSpecification()
        }],
        transactionInfo: {
            totalPriceStatus: 'FINAL',
            totalPrice: '99.99',
            currencyCode: 'GBP',
            countryCode: 'GB'
        },
        merchantInfo: {
            merchantId: 'YOUR_GOOGLE_PAY_MERCHANT_ID', // 'TEST' for sandbox
            merchantName: 'Your Store Name'
        }
    };

    paymentsClient.loadPaymentData(paymentDataRequest)
        .then(function(paymentData) {
            // Extract the token and send to backend
            var token = paymentData.paymentMethodData.tokenizationData.token;
            submitGooglePayToken(token);
        })
        .catch(function(err) {
            if (err.statusCode === 'CANCELED') return; // User cancelled
            console.error('Google Pay error:', err);
        });
}

function submitGooglePayToken(token) {
    fetch('/api/process-googlepay.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            google_pay_token: token,
            order_id: 'ORDER_123',
            amount: 99.99,
            currency: 'GBP',
            billing: {
                firstName: 'John',
                lastName: 'Doe',
                email: 'john@example.com',
                address1: '123 Test Street',
                city: 'London',
                countryCode: 'GB',
                postcode: 'SW1A 1AA'
            }
        })
    })
    .then(r => r.json())
    .then(function(result) {
        if (result.success) {
            window.location.href = result.redirect_url;
        } else if (result.requires_3ds) {
            handle3DS(result); // Same 3DS flow as card payments
        } else {
            alert(result.error || 'Payment failed');
        }
    });
}

Backend: Process Google Pay Payment#

// process-googlepay.php
$input = json_decode(file_get_contents('php://input'), true);
$googlePayToken = $input['google_pay_token'];

$api = new CardstreamApi($merchantId, $sharedSecret, $gatewayUrl);

$fields = [
    'merchantID'         => $merchantId,
    'action'             => 'SALE',
    'type'               => '1',
    'amount'             => (string) $api->convertAmountToMinor($amount),
    'currencyCode'       => (string) $api->getCurrencyCode($currency),
    'countryCode'        => (string) $api->getCountryCodeNumeric($billingCountry),
    'orderRef'           => $orderId,
    'transactionUnique'  => uniqid($orderId . '-'),
    'redirectURL'        => $callbackUrl,
    'paymentMethod'      => 'googlepay',              // Required: identifies Google Pay
    'paymentToken'       => $googlePayToken,           // Required: raw token from Google Pay
    'deviceIpAddress'    => $_SERVER['REMOTE_ADDR'],
    'threeDSRedirectURL' => $threeDSCallbackUrl,
    'customerName'       => 'John Doe',
    'customerEmail'      => 'john@example.com',
    // ... other customer fields
];

$response = $api->sendDirectTransaction($fields);

if ($api->isSuccess($response)) {
    // responseCode === '0' — payment successful
    $xref = $response['xref'];
} elseif ($api->requires3ds($response)) {
    // responseCode === '65802' — 3DS required (same flow as card)
} else {
    $error = $response['responseMessage'];
}

Key Differences from Card Payments#

Card (Hosted Fields)Google Pay
Frontend libraryhostedfields.js (jQuery)Google Pay API (pay.js)
Token sourcegetPaymentDetails() callbackloadPaymentData() response
Backend fieldpaymentToken (from hosted fields)paymentMethod=googlepay + paymentToken (from Google)
3DS handlingSameSame
Redirect modeSupportedNot needed (Google Pay only in embedded)

Configuration#

SettingDescription
googlepay_merchant_idGoogle Pay merchant ID from Google Business Console. Use TEST in sandbox.
googlepay_gateway_merchant_idGateway merchant ID for tokenization. Defaults to your Cardstream merchant ID.

Cardstream Apple Pay#

This documentation describes Apple Pay support for embedded mode. In redirect mode, Apple Pay is supported by the gateway directly via the hosted payment page. The embedded integration uses Safari's native ApplePaySession API on the frontend, with server-side merchant validation via Apple's servers, and the payment token sent to Cardstream's /direct/ endpoint using the paymentMethod=applepay field.

Flow#

  1. Frontend checks ApplePaySession.canMakePayments() — hides option if unavailable
  2. User clicks Apple Pay button → new ApplePaySession(3, paymentRequest) with amount/currency
  3. onvalidatemerchant: frontend sends validationURL to backend → backend POSTs to Apple with merchant cert → returns merchant session → completeMerchantValidation()
  4. onpaymentauthorized: extract event.payment.token → stringify → store as payment token
  5. Backend receives token → sends to Cardstream /direct/ with paymentMethod=applepay, paymentToken=<token>
  6. Response: responseCode=0 success, 65802 3DS required, else error

Prerequisites#

Frontend: Apple Pay Button and Session#

// Check if Apple Pay is available
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
    document.getElementById('applepay-container').style.display = 'block';
}

function onApplePayClicked() {
    var session = new ApplePaySession(3, {
        countryCode: 'GB',
        currencyCode: 'GBP',
        total: { label: 'Store', amount: '10.00' },
        supportedNetworks: ['visa', 'masterCard', 'amex'],
        merchantCapabilities: ['supports3DS']
    });

    session.onvalidatemerchant = function(event) {
        // Send validation URL to backend for merchant validation
        fetch('/api/applepay-validate-merchant.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ validationURL: event.validationURL })
        })
        .then(function(r) { return r.json(); })
        .then(function(merchantSession) {
            session.completeMerchantValidation(merchantSession);
        })
        .catch(function(err) {
            console.error('Merchant validation failed:', err);
            session.abort();
        });
    };

    session.onpaymentauthorized = function(event) {
        var token = JSON.stringify(event.payment.token);
        // Send token to backend
        fetch('/api/process-applepay.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                apple_pay_token: token,
                order_id: 'ORDER_123',
                amount: 10.00,
                currency: 'GBP',
                billing: {
                    firstName: 'John',
                    lastName: 'Doe',
                    email: 'john@example.com',
                    address1: '123 Test Street',
                    city: 'London',
                    countryCode: 'GB',
                    postcode: 'SW1A 1AA'
                }
            })
        })
        .then(function(r) { return r.json(); })
        .then(function(result) {
            if (result.success) {
                session.completePayment(ApplePaySession.STATUS_SUCCESS);
                window.location.href = result.redirect_url;
            } else {
                session.completePayment(ApplePaySession.STATUS_FAILURE);
                alert(result.error || 'Payment failed');
            }
        });
    };

    session.begin();
}

Backend: Merchant Validation#

// applepay-validate-merchant.php
$input = json_decode(file_get_contents('php://input'), true);
$validationUrl = $input['validationURL'];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $validationUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'merchantIdentifier' => $merchantId,
    'displayName' => 'Store Name',
    'initiative' => 'web',
    'initiativeContext' => $domain,
]));
curl_setopt($ch, CURLOPT_SSLCERT, $certPath);
curl_setopt($ch, CURLOPT_SSLKEY, $keyPath);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$merchantSession = json_decode(curl_exec($ch), true);

header('Content-Type: application/json');
echo json_encode($merchantSession);

Backend: Process Apple Pay Payment#

// process-applepay.php
$input = json_decode(file_get_contents('php://input'), true);
$applePayToken = $input['apple_pay_token'];

$api = new CardstreamApi($merchantId, $sharedSecret, $gatewayUrl);

$fields = [
    'merchantID'         => $merchantId,
    'action'             => 'SALE',
    'type'               => '1',
    'amount'             => (string) $api->convertAmountToMinor($amount),
    'currencyCode'       => (string) $api->getCurrencyCode($currency),
    'countryCode'        => (string) $api->getCountryCodeNumeric($billingCountry),
    'orderRef'           => $orderId,
    'transactionUnique'  => uniqid($orderId . '-'),
    'redirectURL'        => $callbackUrl,
    'paymentMethod'      => 'applepay',               // Required: identifies Apple Pay
    'paymentToken'       => $applePayToken,            // Required: stringified token from Apple Pay
    'deviceIpAddress'    => $_SERVER['REMOTE_ADDR'],
    'threeDSRedirectURL' => $threeDSCallbackUrl,
    'customerName'       => 'John Doe',
    'customerEmail'      => 'john@example.com',
    // ... other customer fields
];

$response = $api->sendDirectTransaction($fields);

if ($api->isSuccess($response)) {
    // responseCode === '0' — payment successful
    $xref = $response['xref'];
} elseif ($api->requires3ds($response)) {
    // responseCode === '65802' — 3DS required (same flow as card)
} else {
    $error = $response['responseMessage'];
}

Key Differences from Google Pay#

Google PayApple Pay
Frontend APIGoogle Pay JS API (pay.js)ApplePaySession (native Safari)
Availability checkisReadyToPay()ApplePaySession.canMakePayments()
Merchant validationNot requiredRequired (server-side with merchant cert)
Token sourcepaymentMethodData.tokenizationData.tokenevent.payment.token (stringified)
Backend fieldpaymentMethod=googlepaypaymentMethod=applepay
Browser supportChrome, Safari, Firefox, EdgeSafari (macOS/iOS), Chrome on iOS 16+
Certificate requiredNoYes (Apple Merchant Identity Certificate)

Configuration#

SettingDescription
applepay_merchant_idApple Pay merchant identifier from Apple Developer account
applepay_merchant_certPath or PEM content of the Merchant Identity Certificate
applepay_merchant_keyPath or PEM content of the Merchant Identity Private Key

Cardstream 3D Secure#

When a direct transaction returns responseCode=65802, 3D Secure authentication is required.

Flow#

  1. Initial transaction returns responseCode=65802 with ACS URL and threeDSRef
  2. Redirect customer (or use iframe) to the ACS URL for authentication
  3. ACS posts back to your threeDSRedirectURL with cres/threeDSRef (3DS2) or PaRes/MD (3DS1)
  4. Send a continuation transaction to /direct/ with the ACS response

Handling 3DS Response (Frontend)#

// If backend returns requires_3ds, show ACS in iframe
if (result.requires_3ds) {
    // Create iframe overlay
    var iframe = document.createElement('iframe');
    iframe.name = 'threeds_acs';
    document.body.appendChild(iframe);

    // Build auto-submit form targeting the iframe
    var form = document.createElement('form');
    form.method = 'POST';
    form.target = 'threeds_acs';
    form.action = result.threeds_url;

    // Add threeDSRequest fields
    var request = result.threeds_request || {};
    for (var key in request) {
        var input = document.createElement('input');
        input.type = 'hidden';
        input.name = key;
        input.value = request[key];
        form.appendChild(input);
    }

    document.body.appendChild(form);
    form.submit();
    form.remove();
}

3DS Continuation (Backend)#

// threeds-callback.php — receives POST from ACS
$threeDSResponse = $_POST['cres'] ?? $_POST['PaRes'] ?? '';
$threeDSRef = $_POST['threeDSRef'] ?? $_POST['MD'] ?? '';

$response = $api->send3dsContinuation($threeDSRef, $threeDSResponse);

if ($api->isSuccess($response)) {
    // Payment successful after 3DS
    header('Location: ' . $successUrl);
} else {
    // Payment failed
    echo 'Error: ' . $api->extractErrorMessage($response);
}

Cardstream Webhooks / Callbacks#

Cardstream sends form-encoded POST data (not JSON) to your redirectURL. Always verify the SHA-512 signature before processing.

// webhook.php
$response = $_POST;  // Form-encoded, not JSON

$api = new CardstreamApi($merchantId, $sharedSecret, $gatewayUrl);

if (!$api->verifyResponseSignature($response)) {
    http_response_code(400);
    die('Invalid signature');
}

$orderRef = $response['orderRef'] ?? null;
$responseCode = $response['responseCode'] ?? null;
$xref = $response['xref'] ?? null;

if ($responseCode === '0') {
    // Payment successful
    // updateOrderStatus($orderRef, 'paid', $xref);
} else {
    // Payment failed
    // updateOrderStatus($orderRef, 'failed');
}

http_response_code(200);
echo 'OK';

Webhook Summary#

FieldDetails
FormatForm-encoded POST ($_POST)
VerificationSHA-512 signature verification required
Success checkresponseCode === '0'
Transaction refxref (cross-reference string)

Cardstream Numeric Code Formats#

Cardstream uses numeric ISO codes throughout. Amounts are in minor units (pence/cents).

Currency Codes (ISO 4217 Numeric)#

CurrencyAlphaNumeric
British PoundGBP826
US DollarUSD840
EuroEUR978
Australian DollarAUD36
Canadian DollarCAD124

Country Codes (ISO 3166-1 Numeric)#

CountryAlpha-2Numeric
United KingdomGB826
United StatesUS840
FranceFR250
GermanyDE276
ItalyIT380

Amount Format#

Amounts are in minor currency units (pence/cents). Multiply the decimal amount by 100:

$amountMinor = (int) round((float) $amount * 100);
// 99.99 -> 9999

Cardstream Response Codes#

CodeMeaning
0Success
658023D Secure authentication required
OtherFailure — see responseMessage for details

Cardstream Transaction Types#

FieldValueMeaning
actionSALEAuthorise + capture
actionREFUNDRefund (requires xref)
type1Ecommerce
type2MOTO (Mail Order / Telephone Order)

Cardstream Integration Testing#

For test cards and specific amounts that trigger different response codes, see the Cardstream integration testing guide:

Integration Testing Reference

This guide covers test card numbers, test amounts for simulating approvals, declines, and specific error responses, and other testing scenarios.


Running the Cardstream Examples#

cd Documentation/cardstream-gateway-examples
cp config.php config.local.php   # then fill in credentials
php -S localhost:8080
URLDescription
http://localhost:8080/frontend/checkout-card.htmlCard payment (hosted fields)
http://localhost:8080/frontend/checkout-googlepay.htmlGoogle Pay payment
http://localhost:8080/frontend/checkout-applepay.htmlApple Pay payment (Safari/iOS only)
http://localhost:8080/frontend/checkout-bitcoin.htmlBitcoin payment (iframe)
http://localhost:8080/redirect-mode.phpRedirect mode (auto-submit form)

Cardstream Example Files#

cardstream-gateway-examples/
├── config.php                     - Config template
├── config.local.php               - Your credentials (gitignored)
├── CardstreamApi.php              - Core API class (signatures, transactions, converters)
├── redirect-mode.php              - Redirect/HPP demo
├── api/
│   ├── session.php                - Returns gateway config for hostedfields.js
│   ├── process-payment.php        - Receives paymentToken, POSTs to /direct/
│   ├── process-googlepay.php      - Google Pay token processing via /direct/
│   ├── process-applepay.php       - Apple Pay token processing via /direct/
│   ├── applepay-validate-merchant.php - Apple Pay merchant validation proxy
│   ├── threeds-callback.php       - 3DS continuation handler
│   ├── bitcoin-session.php        - Bitcoin iframe URL generator
│   └── webhook.php                - Callback handler with signature verification
└── frontend/
    ├── checkout-card.html         - Card checkout (hostedfields.js + jQuery)
    ├── checkout-googlepay.html    - Google Pay checkout (native Google Pay API)
    ├── checkout-applepay.html     - Apple Pay checkout (ApplePaySession API)
    ├── checkout-bitcoin.html      - Bitcoin checkout (iframe)
    ├── success.html               - Payment success page
    └── cancel.html                - Payment cancellation page


Encoded Gateway#

This section covers integration with the Encoded payment gateway. Card payments are processed through Encoded's Hosted Payment Fields (HPF) and Hosted Payment Page (HPP). Google Pay uses Encoded's Alternative Payment Methods (APM) library.

Two checkout modes are supported:

  1. Redirect Mode - Customer is redirected to Encoded's hosted payment page
  2. Embedded Mode - Encoded's payment fields are embedded directly in your checkout page

Table of Contents (Encoded)#

  1. Environments & URLs
  2. Authentication
  3. Redirect Mode (Hosted Payment Page)
  4. Embedded Mode
  1. Transaction API
  2. Webhooks / Callbacks
  3. Test Cards
  4. Running the Encoded Examples

Environments & URLs#

Encoded Gateway URLs#

EnvironmentURL
Productionhttps://pay.musqet.tech
Sandboxhttps://pay-test.musqet.tech

Encoded API URLs (For HPF & Redirect)#

EnvironmentAPI URLAssets URL
Productionhttps://pay.musqet.tech/api/v1/https://pay.musqet.tech/assets/
Sandbox (SIT)https://pay-test.musqet.tech/api/v1/https://pay-test.musqet.tech/assets/

Encoded APM URLs (For Google Pay / Apple Pay)#

EnvironmentURL
Productionhttps://gateway.encoded.services
Sandboxhttps://sit.encoded.services

Encoded Authentication URL#

EnvironmentAuth URL
Productionhttps://pay.musqet.tech/auth/oauth/token
Sandboxhttps://pay-test.musqet.tech/auth/oauth/token

Authentication#

The Encoded gateway uses OAuth 2.0 Client Credentials Grant for authentication. You will need:

Obtaining an Access Token#

function createAccessToken($merchantId, $secretKey, $isSandbox = true) {
    $authUrl = $isSandbox
        ? 'https://pay-test.musqet.tech/auth/oauth/token'
        : 'https://pay.musqet.tech/auth/oauth/token';

    $ch = curl_init($authUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
    curl_setopt($ch, CURLOPT_USERPWD, "$merchantId:$secretKey");
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    return $data['access_token'];
}

Token Scopes#

Different operations require different token scopes:

ScopeUsage
(default)Full API access for server-side operations
session_limited_readLimited token for Hosted Payment Fields (safe for frontend)
apmToken for Alternative Payment Methods (Google Pay, Apple Pay)

Redirect Mode (Encoded Hosted Payment Page)#

The simplest integration method. Redirect customers to Encoded's hosted payment page.

Flow#

  1. Create an order via API
  2. Redirect customer to the payment URL
  3. Customer completes payment
  4. Customer is redirected back to your success/cancel URL
  5. You receive a callback with payment status

Creating an Order (Backend)#

function createOrder($orderData, $merchantId, $secretKey, $isSandbox = true) {
    $accessToken = createAccessToken($merchantId, $secretKey, $isSandbox);

    $gatewayUrl = $isSandbox
        ? 'https://pay-test.musqet.tech/api/v1/orders'
        : 'https://pay.musqet.tech/api/v1/orders';

    $items = [];
    foreach ($orderData['items'] as $i => $item) {
        $items[$i] = [
            'object' => 'order.item',
            'ref' => $item['sku'],
            'description' => $item['name'],
            'quantity' => (int)$item['quantity'],
            'unitAmount' => (float)$item['price'],
            'taxUnitAmount' => 0.00,
            'taxRate' => 0.00,
            'totalAmount' => (float)($item['price'] * $item['quantity']),
            'taxAmount' => 0.00
        ];
    }

    $payload = [
        'object' => 'order',
        'ref' => $orderData['orderId'],
        'description' => 'Order #' . $orderData['orderId'],
        'currency' => $orderData['currency'] ?? 'GBP',
        'totalAmount' => (float)$orderData['amount'],
        'items' => $items,
        'billingCustomer' => [
            'object' => 'customer',
            'forename' => $orderData['billing']['firstName'],
            'surname' => $orderData['billing']['lastName'],
            'contact' => [
                'object' => 'contact',
                'email' => $orderData['billing']['email'],
                'phone' => [
                    'object' => 'phone',
                    'mobile' => $orderData['billing']['phone'] ?? ''
                ],
                'address' => [
                    'object' => 'address',
                    'forename' => $orderData['billing']['firstName'],
                    'surname' => $orderData['billing']['lastName'],
                    'line1' => $orderData['billing']['address1'],
                    'line2' => $orderData['billing']['address2'] ?? '',
                    'line3' => $orderData['billing']['city'],
                    'country' => $orderData['billing']['countryCode'], // ISO3: "GBR", "FRA", etc.
                    'postcode' => $orderData['billing']['postcode']
                ]
            ]
        ],
        'hpp' => [
            'view' => [
                'order' => ['type' => 'expanded']
            ],
            'returnUrl' => $orderData['successUrl']
        ]
    ];

    $ch = curl_init($gatewayUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    // Important: The API expects an array of orders
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([$payload]));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Accept: application/json',
        'Authorization: Bearer ' . $accessToken,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);

    // Response is an array, get first order's HPP URL
    return $data[0]['links']['hpp']['v1'];
}

Redirect Customer#

// Frontend
function redirectToPayment(checkoutUrl) {
    window.location.href = checkoutUrl;
}

Handle Callback#

// callback.php - Endpoint receiving POST from Musqet gateway
$requestBody = file_get_contents('php://input');
$data = json_decode($requestBody, true);

$orderId = $data['orderId'] ?? null;
$status = $data['status'] ?? null;
$transactionId = $data['transactionId'] ?? null;

if ($status === 'succeeded' || $status === 'paid') {
    // Update order as paid
    updateOrderStatus($orderId, 'paid', $transactionId);
} else {
    // Handle failed payment
    updateOrderStatus($orderId, 'failed');
}

http_response_code(200);
echo 'OK';

Embedded Mode#

Encoded Hosted Payment Fields (Card Payments)#

Encoded's Hosted Payment Fields (HPF) provide secure, PCI-compliant card input fields embedded as iframes in your checkout page.

Integration Flow#

  1. Backend: Create a payment session
  2. Backend: Generate a session-limited JWT token
  3. Frontend: Initialize HPF JavaScript library
  4. Frontend: User enters card details
  5. Frontend: Sync session (save card data to session)
  6. Backend: Submit transaction using session ID

Step 1: Create Payment Session (Backend)#

function createSession($accessToken, $isSandbox = true) {
    $apiUrl = $isSandbox
        ? 'https://pay-test.musqet.tech/api/v1/sessions'
        : 'https://pay.musqet.tech/api/v1/sessions';

    $payload = [
        'object' => 'session',
        'fields' => ['pan', 'expiryDate', 'securityCode']
    ];

    $ch = curl_init($apiUrl);
    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 ' . $accessToken,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true); // Returns session with 'id' field
}

Step 2: Generate Session-Limited JWT (Backend)#

function createHpfToken($merchantId, $secretKey, $sessionId, $isSandbox = true) {
    $authUrl = $isSandbox
        ? 'https://pay-test.musqet.tech/auth/oauth/token'
        : 'https://pay.musqet.tech/auth/oauth/token';

    $postData = http_build_query([
        'grant_type' => 'client_credentials',
        'scope' => 'session_limited_read',
        'payment_session_id' => $sessionId
    ]);

    $ch = curl_init($authUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_USERPWD, "$merchantId:$secretKey");
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    return $data['access_token']; // Session-limited JWT for frontend
}

Step 3: Frontend HTML Structure#

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Checkout</title>
    <!-- HPF JavaScript Library -->
    <script src="https://pay-test.musqet.tech/assets/js/hpf/hpf-3.1.2.min.js"></script>
    <style>
        .hpf-field {
            height: 40px;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 8px;
            margin-bottom: 10px;
        }
        .hpf-field.invalid {
            border-color: red;
        }
        .hpf-field.valid {
            border-color: green;
        }
    </style>
</head>
<body>
    <form id="payment-form">
        <div class="form-group">
            <label for="pan">Card Number</label>
            <div id="pan" class="hpf-field"></div>
        </div>

        <div class="form-group">
            <label for="expiry">Expiry Date</label>
            <div id="expiry" class="hpf-field"></div>
        </div>

        <div class="form-group">
            <label for="securityCode">CVV</label>
            <div id="securityCode" class="hpf-field"></div>
        </div>

        <button type="submit" id="pay-button">Pay Now</button>
    </form>

    <script src="checkout.js"></script>
</body>
</html>

Step 4: Frontend JavaScript (checkout.js)#

// Configuration received from your backend
const config = {
    sessionId: 'SESSION_ID_FROM_BACKEND',
    hpfToken: 'HPF_JWT_TOKEN_FROM_BACKEND'
};

// Track field validity
const fieldStatus = {
    pan: false,
    expiry: false,
    securityCode: false
};

// Initialize Hosted Payment Fields
function initializeHpf() {
    // Register event listeners BEFORE initializing HPF
    // HPF dispatches events to the document, not via callback
    document.addEventListener('initialisationComplete.encodedHpf', function(event) {
        console.log('HPF initialized successfully');
    });

    document.addEventListener('fieldStatusChange.encodedHpf', function(event) {
        console.log('Field status change:', event.detail);

        const field = event.detail.field;
        // HPF uses 'state' with values: 'valid', 'invalid', 'unset'
        const isValid = event.detail.state === 'valid';

        // Map field names (HPF uses 'expiryDate', container is 'expiry')
        const fieldMapping = {
            'pan': 'pan',
            'expiryDate': 'expiry',
            'expiry': 'expiry',
            'securityCode': 'securityCode'
        };
        const mappedField = fieldMapping[field] || field;

        fieldStatus[mappedField] = isValid;

        // Update UI
        const element = document.getElementById(mappedField);
        if (element) {
            element.classList.toggle('valid', isValid);
            element.classList.toggle('invalid', event.detail.state === 'invalid');
        }

        updatePayButton();
    });

    document.addEventListener('paymentSessionSyncComplete.encodedHpf', function(event) {
        console.log('Session sync complete');
        submitPayment();
    });

    document.addEventListener('paymentSessionSyncFailed.encodedHpf', function(event) {
        console.error('Session sync failed:', event.detail);
        alert('Payment failed. Please try again.');
    });

    // Now initialize HPF
    HostedPaymentFields.initialise(config.hpfToken, {
        sessionId: config.sessionId,
        form: {
            id: 'payment-form',
            fields: {
                pan: {
                    id: 'pan',
                    placeholder: '4444 3333 2222 1111'
                },
                expiry: {
                    id: 'expiry',
                    placeholder: 'MM/YY'
                },
                securityCode: {
                    id: 'securityCode',
                    placeholder: '123'
                }
            }
        }
    });
}

// Check if all fields are valid
function updatePayButton() {
    const allValid = fieldStatus.pan && fieldStatus.expiry && fieldStatus.securityCode;
    document.getElementById('pay-button').disabled = !allValid;
}

// Handle form submission
document.getElementById('payment-form').addEventListener('submit', function(e) {
    e.preventDefault();

    // Check all fields are valid
    if (!fieldStatus.pan || !fieldStatus.expiry || !fieldStatus.securityCode) {
        alert('Please fill in all card details correctly.');
        return;
    }

    // Trigger session sync
    const syncEvent = new CustomEvent('paymentSessionSyncRequest.encodedHpf');
    document.getElementById('payment-form').dispatchEvent(syncEvent);
});

// Submit payment to your backend
async function submitPayment() {
    const response = await fetch('/api/payment/process', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            sessionId: config.sessionId,
            orderId: 'YOUR_ORDER_ID'
        })
    });

    const result = await response.json();

    if (result.success) {
        window.location.href = '/order/success';
    } else {
        alert('Payment failed: ' + result.message);
    }
}

// Initialize on page load
document.addEventListener('DOMContentLoaded', initializeHpf);

Step 5: Process Transaction (Backend)#

// Backend endpoint: /api/payment/process
function processPayment($sessionId, $orderId, $amount, $currency = 'GBP') {
    $accessToken = createAccessToken($merchantId, $secretKey);

    $apiUrl = 'https://pay-test.musqet.tech/api/v1/transactions';

    $payload = [
        'object' => 'transaction.request',
        'ref' => 'order_' . $orderId . '_' . time(),
        'action' => 'pay', // or 'authorise' for auth-only
        'currency' => $currency,
        'amount' => (float)$amount,
        'platformType' => 'ecom',
        'source' => [
            'object' => 'source',
            'session' => [
                'object' => 'session',
                'id' => $sessionId
            ]
        ],
        'customer' => [
            'object' => 'customer',
            'ref' => 'customer_' . $orderId,
            'forename' => $billing['firstName'],
            'surname' => $billing['lastName'],
            'contact' => [
                'object' => 'contact',
                'address' => [
                    'object' => 'address',
                    'line1' => $billing['address1'],
                    'country' => $billing['countryCode'],
                    'postcode' => $billing['postcode']
                ]
            ]
        ]
    ];

    $ch = curl_init($apiUrl);
    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 ' . $accessToken,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($response, true);

    // Check transaction status
    if (isset($result['id']) && in_array($result['status'], ['succeeded', 'paid', 'processed'])) {
        return ['success' => true, 'transactionId' => $result['id']];
    }

    return ['success' => false, 'message' => $result['message'] ?? 'Payment failed'];
}

Google Pay Integration (Encoded APM)#

Google Pay integration uses Encoded's Alternative Payment Methods (APM) JavaScript library.

Warning: This section is currently being tested and is not fully functional.

Prerequisites#

  1. Register with Google Pay and obtain a Google Pay Merchant ID (from Google Pay Console)
  2. Verify your domain with Google
  3. You will need two IDs:

Step 1: Get APM Token (Backend)#

function createApmToken($merchantId, $secretKey, $isSandbox = true) {
    $authUrl = $isSandbox
        ? 'https://pay-test.musqet.tech/auth/oauth/token'
        : 'https://pay.musqet.tech/auth/oauth/token';

    $postData = 'grant_type=client_credentials&scope=apm';

    $ch = curl_init($authUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_USERPWD, "$merchantId:$secretKey");
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    return $data['access_token'];
}

Step 2: Frontend Integration#

<!DOCTYPE html>
<html>
<head>
    <title>Checkout with Google Pay</title>
    <!-- APM JavaScript Library -->
    <script src="https://pay-test.musqet.tech/assets/js/apm/apm-1.0.0.min.js"></script>
</head>
<body>
    <div id="google-pay-button"></div>

    <script>
    // Configuration from backend
    const apmConfig = {
        apmToken: 'APM_JWT_FROM_BACKEND',
        amount: '99.99',
        currency: 'GBP',
        merchantId: 'YOUR_GATEWAY_MERCHANT_ID',
        googlePayMerchantId: 'YOUR_GOOGLE_MERCHANT_ID',
        displayName: 'Your Store Name'
    };

    function initializeGooglePay() {
        // Build merchant config
        const merchantConfig = {
            merchantId: apmConfig.merchantId,
            merchantName: apmConfig.displayName
        };

        // Add Google Pay Merchant ID if available (from Google Pay Console)
        if (apmConfig.googlePayMerchantId) {
            merchantConfig.googlePayMerchantId = apmConfig.googlePayMerchantId;
        }

        AlternativePaymentMethods.initialise({
            jwt: apmConfig.apmToken,
            enablement: {
                googlePay: true,
                applePay: false
            },
            merchantConfig: merchantConfig,
            payment: {
                countryCode: 'GB',
                currencyCode: apmConfig.currency,
                totalPriceLabel: 'Total',
                totalPrice: apmConfig.amount
            },
            buttonStyling: {
                googlePay: {
                    buttonColor: 'black',
                    buttonType: 'pay',
                    buttonSizeMode: 'fill'
                }
            },
            domContainers: {
                googlePay: 'google-pay-button'
            },
            googlePayListeners: {
                onPaymentAuthorized: handleGooglePayAuthorized,
                onPaymentDataChanged: handlePaymentDataChanged,
                onError: handleGooglePayError
            }
        });
    }

    function handleGooglePayAuthorized(paymentData) {
        // Extract the token
        const token = paymentData.paymentMethodData.tokenizationData.token;

        // Base64 encode the token as required by the API
        const encodedToken = btoa(token);

        // Send to backend
        submitGooglePayPayment(encodedToken);

        return { transactionState: 'SUCCESS' };
    }

    function handlePaymentDataChanged(data) {
        return {};
    }

    function handleGooglePayError(error) {
        console.error('Google Pay error:', error);
    }

    async function submitGooglePayPayment(token) {
        const response = await fetch('/api/payment/googlepay', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                google_pay_token: token,
                orderId: 'YOUR_ORDER_ID'
            })
        });

        const result = await response.json();
        if (result.success) {
            window.location.href = result.redirect_url;
        }
    }

    document.addEventListener('DOMContentLoaded', initializeGooglePay);
    </script>
</body>
</html>

Step 3: Process Google Pay Transaction (Backend)#

function processGooglePayPayment($googlePayToken, $orderId, $amount, $currency = 'GBP') {
    $accessToken = createAccessToken($merchantId, $secretKey);

    $apiUrl = 'https://pay-test.musqet.tech/api/v1/transactions';

    $payload = [
        'object' => 'transaction.request',
        'ref' => 'gpay_' . $orderId . '_' . time(),
        'action' => 'pay',
        'currency' => $currency,
        'amount' => (float)$amount,
        'platformType' => 'ecom',
        'source' => [
            'object' => 'source',
            'google_pay' => [
                'object' => 'google_pay',
                'token' => $googlePayToken // Base64 encoded token from frontend
            ]
        ],
        'customer' => [
            'object' => 'customer',
            'ref' => 'customer_' . $orderId,
            // ... customer details
        ]
    ];

    $ch = curl_init($apiUrl);
    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 ' . $accessToken,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    return json_decode($response, true);
}

Transaction API#

Transaction Actions#

ActionDescription
payAuthorise and capture in a single request
authoriseAuthorise only (capture later)
captureCapture a previous authorisation
refundRefund a captured transaction
voidCancel an authorisation

Transaction Request Structure#

{
    "object": "transaction.request",
    "ref": "unique_reference",
    "action": "pay",
    "currency": "GBP",
    "amount": 99.99,
    "platformType": "ecom",
    "source": {
        "object": "source",
        "session": {
            "object": "session",
            "id": "session-uuid"
        }
    },
    "customer": {
        "object": "customer",
        "ref": "customer_id",
        "forename": "John",
        "surname": "Doe",
        "email": "john@example.com",
        "contact": {
            "object": "contact",
            "address": {
                "object": "address",
                "line1": "123 Main St",
                "line2": "",
                "line3": "London",
                "country": "GBR",
                "postcode": "SW1A 1AA"
            }
        }
    }
}

Transaction Response#

{
    "object": "transaction",
    "id": "b015dd96-a8e5-46e1-b390-af39d0b93960",
    "creationDate": "2024-01-15T10:14:20Z",
    "status": "processed",
    "request": {
        "object": "transaction.request",
        "id": "700ff349-4038-4063-ad45-715ce9d8a48c",
        "action": "pay",
        "amount": 99.99,
        "currency": "GBP"
    },
    "response": {
        "object": "transaction.response",
        "authorisationCode": "123456",
        "responseCode": "00",
        "responseMessage": "Approved"
    }
}

Webhooks / Callbacks#

Callback Payload Example#

{
    "object": "callback",
    "event": "transaction.completed",
    "data": {
        "transactionId": "b015dd96-a8e5-46e1-b390-af39d0b93960",
        "orderId": "ORDER_12345",
        "status": "succeeded",
        "amount": 99.99,
        "currency": "GBP",
        "timestamp": "2024-01-15T10:14:25Z"
    }
}

Handling Callbacks (Backend)#

// webhook.php - Endpoint receiving webhooks from Musqet gateway
$requestBody = file_get_contents('php://input');
$payload = json_decode($requestBody, true);

$event = $payload['event'] ?? null;
$data = $payload['data'] ?? [];

// Log for debugging
error_log('Payment callback received: ' . $event . ' - ' . json_encode($data));

switch ($event) {
    case 'transaction.completed':
        if ($data['status'] === 'succeeded') {
            // Mark order as paid
            updateOrderStatus($data['orderId'], 'paid', $data['transactionId']);
        } else {
            updateOrderStatus($data['orderId'], 'failed');
        }
        break;

    case 'transaction.refunded':
        updateOrderStatus($data['orderId'], 'refunded');
        break;
}

// Always respond with 200 to acknowledge receipt
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['received' => true]);

Test Cards#

Use these cards in sandbox/test mode:

Card NumberExpiryCVVResult
4444 3333 2222 1111Any future dateAny 3 digitsApproved
4000 0000 0000 0002Any future dateAny 3 digitsDeclined
4000 0000 0000 3220Any future dateAny 3 digits3D Secure Required

Country Codes#

The Encoded API uses ISO 3166-1 alpha-3 country codes:

CountryISO2ISO3
United KingdomGBGBR
FranceFRFRA
GermanyDEDEU
United StatesUSUSA
SpainESESP
ItalyITITA

Error Handling#

Common Error Responses#

{
    "object": "error",
    "code": "invalid_request",
    "message": "The request was invalid",
    "details": [
        {
            "field": "amount",
            "message": "Amount must be greater than 0"
        }
    ]
}

Error Codes#

CodeDescription
invalid_requestRequest validation failed
authentication_failedInvalid credentials
session_expiredPayment session has expired
declinedTransaction was declined
insufficient_fundsCard has insufficient funds
3ds_required3D Secure authentication required

Running the Encoded Examples#

cd Documentation/encoded-gateway-examples
cp config.php config.local.php   # then fill in credentials
php -S localhost:8080

Open http://localhost:8080/frontend/checkout-card.html in a browser.


Bitcoin Payments#

Bitcoin payments use a gateway-agnostic iframe component hosted at bitcoin-payment-component.musqet.tech. The Bitcoin payment flow is completely independent of the card processing gateway (Cardstream or Encoded) — it uses its own iframe widget and does not require any gateway credentials or signature authentication.

Both the Cardstream and Encoded example directories include Bitcoin support via bitcoin-session.php and checkout-bitcoin.html.


Bitcoin Environments#

EnvironmentURL
Productionhttps://bitcoin-payment-component.musqet.tech
Sandboxhttps://staging.bitcoin-payment-component.musqet.tech

Generating the Bitcoin URL (Backend)#

The Bitcoin iframe URL is constructed from your Business ID and order details:

function getBitcoinUrl($businessId, $orderId, $amount, $currency, $successUrl, $cancelUrl, $isSandbox = true) {
    $baseUrl = $isSandbox
        ? 'https://staging.bitcoin-payment-component.musqet.tech'
        : 'https://bitcoin-payment-component.musqet.tech';

    $amountInCents = (int)($amount * 100);

    return sprintf(
        '%s/b/%s/c?amount=%d&successUrl=%s&cancelUrl=%s&currency=%s&orderId=%s',
        $baseUrl,
        $businessId,
        $amountInCents,
        urlencode($successUrl),
        urlencode($cancelUrl),
        $currency,
        urlencode($orderId)
    );
}

URL Parameters#

ParameterDescription
businessIdYour Musqet Bitcoin Business ID
amountAmount in minor units (pence/cents)
currencyAlpha currency code (e.g. GBP, USD)
successUrlURL the iframe redirects to on successful payment
cancelUrlURL the iframe redirects to on cancellation
orderIdYour order reference

Frontend Integration#

Load the Bitcoin URL in an iframe and listen for postMessage events from the payment widget:

<div id="bitcoin-container" style="max-width: 500px; height: 600px;">
    <iframe id="bitcoin-iframe" style="width: 100%; height: 100%; border: none;"></iframe>
</div>

<script>
// Set iframe source to the URL from your backend
const iframe = document.getElementById('bitcoin-iframe');
iframe.src = 'BITCOIN_URL_FROM_BACKEND';

// Listen for messages from the Bitcoin iframe
window.addEventListener('message', function(event) {
    // Verify origin
    if (!event.origin.includes('bitcoin-payment-component.musqet.tech')) {
        return;
    }

    const data = event.data;

    switch (data.type) {
        case 'musqet-bitcoin-payment-complete':
            // Payment successful — notify your backend and redirect
            handleBitcoinSuccess(data);
            break;

        case 'musqet-bitcoin-payment-cancelled':
            // Customer cancelled — redirect to cancellation page
            window.location.href = '/order/cancelled';
            break;

        case 'musqet-bitcoin-payment-error':
            // Payment error
            console.error('Bitcoin payment error:', data.error);
            break;
    }
});

function handleBitcoinSuccess(data) {
    fetch('/api/payment/bitcoin/confirm', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            orderId: 'YOUR_ORDER_ID',
            transactionId: data.transactionId,
            status: 'success'
        })
    }).then(function() {
        window.location.href = '/order/success';
    });
}
</script>

postMessage Events#

The Bitcoin iframe communicates with the parent window via postMessage. Always verify the origin includes bitcoin-payment-component.musqet.tech before processing events.

Event TypeDescriptionData
musqet-bitcoin-payment-completePayment confirmed on-chaintransactionId
musqet-bitcoin-payment-cancelledCustomer cancelled the payment
musqet-bitcoin-payment-errorPayment failederror (string)

Bitcoin Example Files#

Both gateway example directories include Bitcoin support:

FilePurpose
api/bitcoin-session.phpReturns the Bitcoin iframe URL as JSON
frontend/checkout-bitcoin.htmlFrontend checkout page with iframe and postMessage handling
frontend/success.htmlPayment success page
frontend/cancel.htmlPayment cancellation page

Changelog#