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:
| Gateway | Examples Directory | Payment Methods |
|---|---|---|
| Cardstream | cardstream-gateway-examples/ | Card (Hosted Fields), Google Pay, Apple Pay |
| Encoded | encoded-gateway-examples/ | Card (HPF), Google Pay |
| Bitcoin | Both directories | Bitcoin (gateway-agnostic) |
The two gateways differ significantly in their architecture:
| Cardstream | Encoded | |
|---|---|---|
| Authentication | SHA-512 signature over fields | OAuth 2.0 (JWT tokens) |
| Request format | Form-encoded | JSON |
| Card JS library | hostedfields.js (jQuery plugin) | hpf-3.1.2.min.js (custom events on document) |
| Success check | responseCode === '0' | status in ['succeeded','paid','processed'] |
| Country codes | ISO 3166-1 numeric (826, 840) | ISO 3166-1 alpha-3 (GBR, USA) |
| Currency codes | ISO 4217 numeric (826, 840) | ISO 4217 alpha (GBP, USD) |
| Google Pay | Yes (via native Google Pay JS API) | Yes (via APM library) |
| Apple Pay | Yes (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:
- Redirect Mode - Customer is redirected to Cardstream's hosted payment page
- Embedded Mode - Cardstream's hosted fields are embedded directly in your checkout page (requires jQuery)
- Google Pay - Native Google Pay button using Google's Pay API, processed via Cardstream's
/direct/endpoint - Apple Pay - Native Apple Pay button using Safari's ApplePaySession API, processed via Cardstream's
/direct/endpoint
Table of Contents (Cardstream)#
- Gateway URL
- Authentication (Signature)
- Redirect Mode
- Embedded Mode (Hosted Fields)
- Google Pay
- Apple Pay
- 3D Secure
- Webhooks / Callbacks
- Numeric Code Formats
- Integration Testing
- 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:
| Path | Usage |
|---|---|
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.js | Hosted 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#
- Build transaction fields with your merchant ID, order details, and redirect URL
- Sign the fields with SHA-512
- Render an auto-submit HTML form POSTing to
https://payments.musqet.tech/hosted/ - Customer completes payment on the gateway's hosted page
- Gateway redirects back to your
redirectURLwith 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#
- Frontend: Load jQuery +
hostedfields.jsfrom the gateway - Frontend: Initialize the jQuery
hostedFormplugin on your form - Frontend: User enters card details into hosted field iframes
- Frontend: Call
getPaymentDetails()to get apaymentToken - Backend: Send
paymentToken+ order details tohttps://payments.musqet.tech/direct/ - Backend: Handle response (success, 3DS, or error)
Prerequisites#
- jQuery (any recent version)
- No server-side session creation needed (no OAuth required)
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#
- Frontend loads Google's Pay API script
- Create a
PaymentsClientand callisReadyToPay()to check device/browser support - Render the Google Pay button
- Customer clicks button → Google Pay sheet opens → customer authorizes
- Frontend receives
PaymentDatawith token atpaymentMethodData.tokenizationData.token - Frontend sends the raw token string to your backend
- Backend POSTs to Cardstream
/direct/withpaymentMethod=googlepay+paymentToken=<token>alongside standard transaction fields - Cardstream returns standard response (
responseCode=0for success,65802for 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 library | hostedfields.js (jQuery) | Google Pay API (pay.js) |
| Token source | getPaymentDetails() callback | loadPaymentData() response |
| Backend field | paymentToken (from hosted fields) | paymentMethod=googlepay + paymentToken (from Google) |
| 3DS handling | Same | Same |
| Redirect mode | Supported | Not needed (Google Pay only in embedded) |
Configuration#
| Setting | Description |
|---|---|
googlepay_merchant_id | Google Pay merchant ID from Google Business Console. Use TEST in sandbox. |
googlepay_gateway_merchant_id | Gateway 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#
- Frontend checks
ApplePaySession.canMakePayments()— hides option if unavailable - User clicks Apple Pay button →
new ApplePaySession(3, paymentRequest)with amount/currency onvalidatemerchant: frontend sendsvalidationURLto backend → backend POSTs to Apple with merchant cert → returns merchant session →completeMerchantValidation()onpaymentauthorized: extractevent.payment.token→ stringify → store as payment token- Backend receives token → sends to Cardstream
/direct/withpaymentMethod=applepay,paymentToken=<token> - Response:
responseCode=0success,658023DS required, else error
Prerequisites#
- Apple Developer account with Apple Pay merchant registration
- Merchant Identity Certificate (.pem) from Apple
- Domain verification (host
/.well-known/apple-developer-merchantid-domain-association) - HTTPS required (Apple Pay only works on secure origins)
- Safari, or Chrome on iOS 16+
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 Pay | Apple Pay | |
|---|---|---|
| Frontend API | Google Pay JS API (pay.js) | ApplePaySession (native Safari) |
| Availability check | isReadyToPay() | ApplePaySession.canMakePayments() |
| Merchant validation | Not required | Required (server-side with merchant cert) |
| Token source | paymentMethodData.tokenizationData.token | event.payment.token (stringified) |
| Backend field | paymentMethod=googlepay | paymentMethod=applepay |
| Browser support | Chrome, Safari, Firefox, Edge | Safari (macOS/iOS), Chrome on iOS 16+ |
| Certificate required | No | Yes (Apple Merchant Identity Certificate) |
Configuration#
| Setting | Description |
|---|---|
applepay_merchant_id | Apple Pay merchant identifier from Apple Developer account |
applepay_merchant_cert | Path or PEM content of the Merchant Identity Certificate |
applepay_merchant_key | Path 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#
- Initial transaction returns
responseCode=65802with ACS URL andthreeDSRef - Redirect customer (or use iframe) to the ACS URL for authentication
- ACS posts back to your
threeDSRedirectURLwithcres/threeDSRef(3DS2) orPaRes/MD(3DS1) - 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#
| Field | Details |
|---|---|
| Format | Form-encoded POST ($_POST) |
| Verification | SHA-512 signature verification required |
| Success check | responseCode === '0' |
| Transaction ref | xref (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)#
| Currency | Alpha | Numeric |
|---|---|---|
| British Pound | GBP | 826 |
| US Dollar | USD | 840 |
| Euro | EUR | 978 |
| Australian Dollar | AUD | 36 |
| Canadian Dollar | CAD | 124 |
Country Codes (ISO 3166-1 Numeric)#
| Country | Alpha-2 | Numeric |
|---|---|---|
| United Kingdom | GB | 826 |
| United States | US | 840 |
| France | FR | 250 |
| Germany | DE | 276 |
| Italy | IT | 380 |
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#
| Code | Meaning |
|---|---|
0 | Success |
65802 | 3D Secure authentication required |
| Other | Failure — see responseMessage for details |
Cardstream Transaction Types#
| Field | Value | Meaning |
|---|---|---|
action | SALE | Authorise + capture |
action | REFUND | Refund (requires xref) |
type | 1 | Ecommerce |
type | 2 | MOTO (Mail Order / Telephone Order) |
Cardstream Integration Testing#
For test cards and specific amounts that trigger different response codes, see the Cardstream integration testing guide:
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
| URL | Description |
|---|---|
http://localhost:8080/frontend/checkout-card.html | Card payment (hosted fields) |
http://localhost:8080/frontend/checkout-googlepay.html | Google Pay payment |
http://localhost:8080/frontend/checkout-applepay.html | Apple Pay payment (Safari/iOS only) |
http://localhost:8080/frontend/checkout-bitcoin.html | Bitcoin payment (iframe) |
http://localhost:8080/redirect-mode.php | Redirect 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:
- Redirect Mode - Customer is redirected to Encoded's hosted payment page
- Embedded Mode - Encoded's payment fields are embedded directly in your checkout page
Table of Contents (Encoded)#
Environments & URLs#
Encoded Gateway URLs#
| Environment | URL |
|---|---|
| Production | https://pay.musqet.tech |
| Sandbox | https://pay-test.musqet.tech |
Encoded API URLs (For HPF & Redirect)#
| Environment | API URL | Assets URL |
|---|---|---|
| Production | https://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)#
| Environment | URL |
|---|---|
| Production | https://gateway.encoded.services |
| Sandbox | https://sit.encoded.services |
Encoded Authentication URL#
| Environment | Auth URL |
|---|---|
| Production | https://pay.musqet.tech/auth/oauth/token |
| Sandbox | https://pay-test.musqet.tech/auth/oauth/token |
Authentication#
The Encoded gateway uses OAuth 2.0 Client Credentials Grant for authentication. You will need:
- Merchant ID (Client ID)
- Secret Key (Client Secret)
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:
| Scope | Usage |
|---|---|
| (default) | Full API access for server-side operations |
session_limited_read | Limited token for Hosted Payment Fields (safe for frontend) |
apm | Token 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#
- Create an order via API
- Redirect customer to the payment URL
- Customer completes payment
- Customer is redirected back to your success/cancel URL
- 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#
- Backend: Create a payment session
- Backend: Generate a session-limited JWT token
- Frontend: Initialize HPF JavaScript library
- Frontend: User enters card details
- Frontend: Sync session (save card data to session)
- 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#
- Register with Google Pay and obtain a Google Pay Merchant ID (from Google Pay Console)
- Verify your domain with Google
- You will need two IDs:
- Merchant ID - Your Musqet/Encoded gateway merchant ID
- Google Pay Merchant ID - Your ID from Google Pay Console (optional in test mode)
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#
| Action | Description |
|---|---|
pay | Authorise and capture in a single request |
authorise | Authorise only (capture later) |
capture | Capture a previous authorisation |
refund | Refund a captured transaction |
void | Cancel 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 Number | Expiry | CVV | Result |
|---|---|---|---|
| 4444 3333 2222 1111 | Any future date | Any 3 digits | Approved |
| 4000 0000 0000 0002 | Any future date | Any 3 digits | Declined |
| 4000 0000 0000 3220 | Any future date | Any 3 digits | 3D Secure Required |
Country Codes#
The Encoded API uses ISO 3166-1 alpha-3 country codes:
| Country | ISO2 | ISO3 |
|---|---|---|
| United Kingdom | GB | GBR |
| France | FR | FRA |
| Germany | DE | DEU |
| United States | US | USA |
| Spain | ES | ESP |
| Italy | IT | ITA |
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#
| Code | Description |
|---|---|
invalid_request | Request validation failed |
authentication_failed | Invalid credentials |
session_expired | Payment session has expired |
declined | Transaction was declined |
insufficient_funds | Card has insufficient funds |
3ds_required | 3D 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#
| Environment | URL |
|---|---|
| Production | https://bitcoin-payment-component.musqet.tech |
| Sandbox | https://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¤cy=%s&orderId=%s',
$baseUrl,
$businessId,
$amountInCents,
urlencode($successUrl),
urlencode($cancelUrl),
$currency,
urlencode($orderId)
);
}
URL Parameters#
| Parameter | Description |
|---|---|
businessId | Your Musqet Bitcoin Business ID |
amount | Amount in minor units (pence/cents) |
currency | Alpha currency code (e.g. GBP, USD) |
successUrl | URL the iframe redirects to on successful payment |
cancelUrl | URL the iframe redirects to on cancellation |
orderId | Your 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 Type | Description | Data |
|---|---|---|
musqet-bitcoin-payment-complete | Payment confirmed on-chain | transactionId |
musqet-bitcoin-payment-cancelled | Customer cancelled the payment | — |
musqet-bitcoin-payment-error | Payment failed | error (string) |
Bitcoin Example Files#
Both gateway example directories include Bitcoin support:
| File | Purpose |
|---|---|
api/bitcoin-session.php | Returns the Bitcoin iframe URL as JSON |
frontend/checkout-bitcoin.html | Frontend checkout page with iframe and postMessage handling |
frontend/success.html | Payment success page |
frontend/cancel.html | Payment cancellation page |
Changelog#
- v1.4 - Added Apple Pay support for Cardstream gateway (native ApplePaySession API with merchant validation)
- v1.3 - Added Google Pay support for Cardstream gateway (native Google Pay JS API)
- v1.2 - Separated Bitcoin into standalone section; removed Part 1/Part 2 naming
- v1.1 - Added Cardstream gateway examples and documentation
- v1.0 - Initial release with Encoded HPF, Google Pay, and Bitcoin support