View Categories

TrustistEcommerce Server-to-Server Card Payments Integration Guide

Which approach should I use? #

Use this API‑only guide when you need full control and cannot (or prefer not to) load our JS SDK. For most web integrations, the JS SDK guide is the recommended, faster path.

– Prefer the JS SDK (recommended for most merchants):
  – Simplest integration: the SDK collects browser data for you and opens our hosted bank authentication page.
  – Built‑in UI: lightbox, iframe, or redirect with safe defaults and automatic return handling.
  – Fewer moving parts on your side: one `createPayment` callback, then `paymentComplete`.
  – Same server‑side guarantees: payments are finalised server‑to‑server; webhooks remain the source of truth.

– Use this API‑only approach when:
  – You cannot include third‑party scripts for policy/CSP reasons.
  – You want to orchestrate the UI entirely yourself (kiosks, embedded apps, custom layouts).
  – You have a non‑browser client, or a server‑triggered flow that still needs card input you collect.
  – You need deep control over when/where the hosted authentication page is shown.

Overview #

Server-to-server card payments allow you to collect card details on your own system and securely process payments through the TrustistEcommerce API. This integration is suitable for merchants who:

– Have their own card collection forms
– Store customer card details in their own systems
– Need more control over the payment user experience
– Are PCI compliant (PCI SAQ-D)

Important: This integration requires that your systems are PCI compliant. If you are not PCI compliant, please use our standard payment link integration instead.

How It Works #

The server-to-server payment flow consists of these steps:

1. Frontend: Collect card details (your own form)
2. Backend: Call the TrustistEcommerce API once with card + browser data
3. Frontend: If needed, open the bank authentication URL we return (iframe or popup)
4. Backend: Receive webhook confirmation of payment completion (source of truth)

Flow Diagram #

TrustistTransfer server to server flow

The diagram shows two scenarios:

Scenario A: Frictionless payment – card is approved immediately without 3D Secure
Scenario B: 3D Secure challenge required – customer authenticates with their bank via PayerServices

Prerequisites #

Before you begin, ensure you have:

1. API Credentials: Your TrustistEcommerce API Key and Client ID
   – See Getting Your API Keys

2. Authentication Setup: Understanding of HMAC authentication
   – See Authentication Guide

3. Webhook Endpoint: A secure endpoint to receive payment notifications
   – See Merchant Webhooks

4. PCI Compliance: Your system must be PCI SAQ-D compliant to handle raw card data

Step 1: Create and Process a Payment (Single Call) #

From your backend, make a single API call that both creates and processes the payment. This request includes the card details and browser data required for authentication.

API Endpoint #

POST https://api-sandbox.trustistecommerce.com/v1/payments/server-to-server

(For production, use: `https://api.trustistecommerce.com/v1/payments/server-to-server`)

Authentication #

Use HMAC authentication as described in our [Authentication Guide](https://trustisttransfer.com/docs/authentication/).

Request Body #

{
  "amount": 99.99,
  "reference": "ORDER-12345",
  "description": "Order for customer John Smith",
  "customerDetails": "John Smith, john@example.com",
  "payerEmail": "john@example.com",
  "cardDetails": {
    "number": "4444333322221111",
    "expiryMonth": "12",
    "expiryYear": "2025",
    "cvc": "123"
  },
  "billingAddress": {
    "firstName": "John",
    "lastName": "Smith",
    "lineOne": "123 High Street",
    "lineTwo": "Apartment 4B",
    "city": "London",
    "region": "Greater London",
    "country": "GB",
    "postalCode": "SW1A 1AA"
  },
  "browserData": {
    "browserJavaEnabled": false,
    "browserJavascriptEnabled": true,
    "browserLanguage": "en-GB",
    "browserColorDepth": 24,
    "browserScreenHeight": 1080,
    "browserScreenWidth": 1920,
    "browserTZ": -60,
    "browserUserAgent": "Mozilla/5.0...",
    "browserAcceptHeader": "text/html,application/xhtml+xml...",
    "browserIP": "203.0.113.42",
    "challengeWindowSize": "FULLSCREEN",
    "deviceChannel": "Browser"
  }
}

Field Descriptions #

FieldTypeRequiredDescription
`amount`decimalYesPayment amount in GBP (e.g., 99.99)
`reference`stringYesYour unique order/transaction reference
`description`stringNoPayment description
`customerDetails`stringNoCustomer information for your records
`payerEmail`stringYesCustomer’s email address
`cardDetails`objectYesCard information (see below)
`billingAddress`objectNoBilling address (recommended for 3DS)
`browserData`objectYesBrowser information for 3D Secure

Card Details Object #

FieldTypeRequiredDescription
`number`stringYesCard number (13-19 digits, no spaces)
`expiryMonth`stringYesExpiry month (MM format, e.g., “12”)
`expiryYear`stringYesExpiry year (YYYY format, e.g., “2025”)
`cvc`stringYesCard security code (3-4 digits)

Browser Data Object #

These fields are required for 3D Secure authentication and can be collected using JavaScript:

FieldTypeJavaScript Code
`browserJavaEnabled`boolean`navigator.javaEnabled()`
`browserJavascriptEnabled`booleanAlways `true` (if JS is running)
`browserLanguage`string`navigator.language`
`browserColorDepth`number`screen.colorDepth`
`browserScreenHeight`number`screen.height`
`browserScreenWidth`number`screen.width`
`browserTZ`number`new Date().getTimezoneOffset()`
`browserUserAgent`string`navigator.userAgent`
`browserAcceptHeader`stringFrom server-side request headers
`browserIP`stringCustomer’s IP address (server-side)
`challengeWindowSize`string“FULLSCREEN”, “500×600”, etc.
`deviceChannel`string“Browser” or “Application”

JavaScript Example: Collecting Browser Data #

function collectBrowserData() {
    return {
        browserJavaEnabled: navigator.javaEnabled(),
        browserJavascriptEnabled: true,
        browserLanguage: navigator.language,
        browserColorDepth: screen.colorDepth,
        browserScreenHeight: screen.height,
        browserScreenWidth: screen.width,
        browserTZ: new Date().getTimezoneOffset(),
        browserUserAgent: navigator.userAgent,
        challengeWindowSize: "FULLSCREEN",
        deviceChannel: "Browser"
    };
}

Response #

The API will respond with one of two scenarios:

Scenario 1: Payment Approved (Frictionless) #

If the payment is approved without requiring 3D Secure:

{
  "id": "3PAYMENT123456789ABCD",
  "status": "COMPLETE",
  "requiredAction": null
}

Payment is complete! Proceed to show your success page.

Scenario 2: 3D Secure Required #

If 3D Secure authentication is required:

{
  "id": "3PAYMENT123456789ABCD",
  "status": "PENDING_ACTION",
  "requiredAction": {
    "type": "Challenge",
    "url": "https://payerservices-sandbox.trustisttransfer.com/ServerToServer/Challenge?paymentId=3PAYMENT123456789ABCD&merchantOrigin=https://yoursite.com"
  }
}

⚠️ 3D Secure required! Proceed to Step 2.

Error Responses #

If validation fails, you’ll receive a 400 Bad Request with a descriptive error:

{
  "error": "Invalid card number length or format"
}
{
  "error": "Card expiry date is in the past"
}
{
  "error": "American Express cards are not accepted by this merchant"
}

Step 2: Handle Bank Authentication (If Required) #

If `requiredAction` is present in the response, open the provided authentication URL in an iframe or popup. We host this page and coordinate the bank challenge; no additional SDKs are required.

Option A: Using an iframe (Recommended) #

Open the `requiredAction.url` in an iframe or popup:

async function handle3DSecure(requiredAction) {
    if (!requiredAction || requiredAction.type !== "Challenge") {
        return; // No challenge required
    }

    // Open the challenge URL in an iframe
    const iframe = document.createElement('iframe');
    iframe.src = requiredAction.url;
    iframe.style.width = '100%';
    iframe.style.height = '600px';
    iframe.style.border = 'none';
    document.getElementById('payment-container').appendChild(iframe);

    // Listen for completion message
    window.addEventListener('message', function(event) {
        if (event.data && event.data.type === 'PAYMENT_COMPLETE') {

            // Payment completed after 3DS
            const paymentId = event.data.paymentId;
            const status = event.data.status;

            if (status === 'COMPLETE') {
                // Success! Redirect to your success page
                window.location.href = '/order-confirmation?payment=' + paymentId;
            } else {
                // Payment failed
                showError('Payment was not successful. Please try again.');
            }
        }
    });
}

Option B: Using a Popup Window #

function handle3DSecurePopup(requiredAction) {
    if (!requiredAction || requiredAction.type !== "Challenge") {
        return;
    }

    // Open challenge URL in popup
    const popup = window.open(
        requiredAction.url,
        '3DSecureChallenge',
        'width=500,height=600,scrollbars=yes'
    );

    // Listen for completion
    window.addEventListener('message', function(event) {
        if (event.data && event.data.type === 'PAYMENT_COMPLETE') {
            popup.close();

            if (event.data.status === 'COMPLETE') {
                window.location.href = '/order-confirmation?payment=' + event.data.paymentId;
            } else {
                showError('Payment failed after authentication.');
            }
        }
    });
}

What Happens During Authentication? #

1. The customer is presented with their bank’s authentication challenge (e.g., enter a code sent via SMS)

2. After completing the challenge, we automatically process and capture the payment if approved

3. We post a message back to your page with the final payment status

4. You can then redirect the customer to your success or failure page

Step 3: Handle Webhook Notifications #

While the frontend receives immediate feedback, you should always rely on webhook notifications for the authoritative payment status.

Why Webhooks? #

– Customer may close browser before frontend completes
– Network issues may prevent frontend from receiving status
– Webhooks provide server-to-server confirmation

Webhook Events #

You’ll receive one of these events:

EventDescription
`payment.completed`Payment successfully captured
`payment.failed`Payment failed or declined

Webhook Payload Example #

{
  "eventType": "payment.completed",
  "paymentId": "3PAYMENT123456789ABCD",
  "merchantId": "your_merchant_id",
  "amount": 99.99,
  "reference": "ORDER-12345",
  "status": "COMPLETE",
  "timestamp": "2025-10-01T14:30:00Z"
}

Handling Webhooks #

See our Merchant Webhooks Guide for:

– Webhook authentication
– Idempotency handling
– Retry logic
– Testing webhooks

Complete Integration Example #

Here’s a complete example showing all steps together:

Backend Code (Language-Agnostic Pseudocode) #

// 1. Create and process payment with card details (single call)

function processPayment(orderData, cardDetails, browserData):

    // Build payment request
    paymentRequest = {
        amount: orderData.total,
        reference: orderData.orderId,
        description: orderData.description,
        payerEmail: orderData.customerEmail,
        cardDetails: cardDetails,
        billingAddress: orderData.billingAddress,
        browserData: browserData
    }

    // Call TrustistEcommerce API with HMAC authentication (single call)
    response = httpPost(
        url: "https://api.trustistecommerce.com/v1/payments/server-to-server",
        body: paymentRequest,
        headers: {
            "Authorization": generateHmacAuth(paymentRequest),
            "Content-Type": "application/json"
        }
    )

    if response.status == 400:

        // Validation error
        return {
            success: false,
            error: response.body.error
        }

    if response.body.status == "COMPLETE":

        // Frictionless success
        return {
            success: true,
            paymentId: response.body.id,
            completed: true
        }

    else if response.body.requiredAction:

        // 3DS required
        return {
            success: true,
            paymentId: response.body.id,
            completed: false,
            requiredAction: response.body.requiredAction
        }

    return {
        success: false,
        error: "Unexpected response"
    }

// 2. Webhook handler

function handleWebhook(webhookPayload, webhookSignature):

    // Verify webhook signature (see Webhook docs)
    if not verifyWebhookSignature(webhookPayload, webhookSignature):

        return 401 // Unauthorized

    // Process webhook
    if webhookPayload.eventType == "payment.completed":

        // Mark order as paid in your database

        updateOrderStatus(webhookPayload.reference, "PAID")
        sendConfirmationEmail(webhookPayload.paymentId)

    else if webhookPayload.eventType == "payment.failed":

        // Mark order as failed
        updateOrderStatus(webhookPayload.reference, "FAILED")
        sendFailureNotification(webhookPayload.paymentId)

    return 200 // OK

Frontend Code (JavaScript) #

<!DOCTYPE html>
<html>
<head>
    <title>Checkout</title>
    <!-- No SDK required for bank authentication -->
</head>
<body>
    <form id="payment-form">
        <label>
            Card Number:
            <input type="text" id="card-number" maxlength="19" />
        </label>

        <label>
            Expiry (MM/YY):
            <input type="text" id="expiry" placeholder="12/25" />
        </label>

        <label>
            CVC:
            <input type="text" id="cvc" maxlength="4" />
        </label>

        <button type="submit">Pay £99.99</button>
    </form>

    <div id="payment-container"></div>

    <div id="error-message" style="display:none; color:red;"></div>

    <script>

        // Collect browser data
        function collectBrowserData() {
            return {
                browserJavaEnabled: navigator.javaEnabled(),
                browserJavascriptEnabled: true,
                browserLanguage: navigator.language,
                browserColorDepth: screen.colorDepth,
                browserScreenHeight: screen.height,
                browserScreenWidth: screen.width,
                browserTZ: new Date().getTimezoneOffset(),
                browserUserAgent: navigator.userAgent,
                challengeWindowSize: "FULLSCREEN",
                deviceChannel: "Browser"
            };
        }

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

            // Get card details
            const cardNumber = document.getElementById('card-number').value.replace(/\s/g, '');
            const expiry = document.getElementById('expiry').value.split('/');
            const cvc = document.getElementById('cvc').value;

            // Build payment request
            const paymentData = {
                amount: 99.99,
                reference: 'ORDER-12345',
                payerEmail: 'customer@example.com',
                cardDetails: {
                    number: cardNumber,
                    expiryMonth: expiry[0],
                    expiryYear: '20' + expiry[1],
                    cvc: cvc
                },

                browserData: collectBrowserData()
            };

            try {
                // Call your backend
                const response = await fetch('/api/process-payment', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(paymentData)
                });

                const result = await response.json();

                if (!result.success) {
                    // Show error
                    document.getElementById('error-message').textContent = result.error;
                    document.getElementById('error-message').style.display = 'block';
                    return;
                }

                if (result.completed) {
                    // Frictionless - payment complete!
                    window.location.href = '/order-confirmation?payment=' + result.paymentId;
                } else if (result.requiredAction) {
                    // Handle 3D Secure
                    handle3DSecure(result.requiredAction);
                }
            } catch (error) {
                document.getElementById('error-message').textContent = 'Payment processing failed. Please try again.';
                document.getElementById('error-message').style.display = 'block';
            }
        });

        // Handle 3D Secure challenge
        function handle3DSecure(requiredAction) {
            const iframe = document.createElement('iframe');
            iframe.src = requiredAction.url;
            iframe.style.width = '100%';
            iframe.style.height = '600px';
            iframe.style.border = 'none';
            document.getElementById('payment-container').appendChild(iframe);
            document.getElementById('payment-form').style.display = 'none';

            // Listen for completion
            window.addEventListener('message', function(event) {
                if (event.data && event.data.type === 'PAYMENT_COMPLETE') {
                    if (event.data.status === 'COMPLETE') {
                        window.location.href = '/order-confirmation?payment=' + event.data.paymentId;
                    } else {
                        document.getElementById('error-message').textContent = 'Payment was not successful.';
                        document.getElementById('error-message').style.display = 'block';
                        document.getElementById('payment-form').style.display = 'block';
                        iframe.remove();
                    }
                }
            });
        }
    </script>
</body>
</html>

Testing Your Integration #

Sandbox Environment #

Use these endpoints for testing:

– API: `https://api-sandbox.trustistecommerce.com`
  (We return the correct hosted authentication URL when needed.)

Test Card Numbers #

3D-secure test cards #

SchemeCard Number3DS flowDescription
Visa4012000000060085FrictionlessAuthentication approved immediately
Visa40663300000000043DS method frictionlessAuthentication approved after DDC step complete
Visa49387300000000013DS method challengeDDC step followed by challenge
Visa49181900000000023DS challengeImmediately challenged
Mastercard5555555555554444FrictionlessAuthentication approved immediately
Mastercard54545454545454543DS method frictionlessAuthentication approved after DDC step complete
Mastercard52000000000010213DS method challengeDDC step followed by challenge
Mastercard52957959768593953DS challengeImmediately challenged
Amex343434343434343FrictionlessAuthentication approved immediately
Amex3714496353984313DS method frictionlessAuthentication approved after DDC step complete
Amex3742454554001263DS method challengeDDC step followed by challenge
Amex3741010000006083DS challengeImmediately challenged

Declined Payments #

SchemeCard NumberCVVDescription
Visa4000000000001000100Declined due to insufficient funds
Mastercard5425233430109903100Declined due to insufficient funds
Visa4000000000001000001Declined due to incorrect CVV
Mastercard5425233430109903001Declined due to incorrect CVV
Visa4000000000001000222Declined due to issuer reason “Do not honour”
Mastercard5425233430109903222Declined due to issuer reason “Do not honour”
Amex3782822463100051000Declined due to insufficient funds

Testing Checklist #

– [ ] Frictionless payment completes successfully

– [ ] 3DS challenge displays correctly in iframe/popup

– [ ] Payment completes after 3DS authentication

– [ ] Error messages display for invalid card numbers

– [ ] Error messages display for expired cards

– [ ] Webhook endpoint receives `payment.completed` event

– [ ] Webhook endpoint receives `payment.failed` event

– [ ] Order status updates correctly from webhooks

Common Integration Patterns #

Pattern 1: Stored Cards #

If you store customer cards in your own PCI-compliant system:

// Retrieve stored card

storedCard = getStoredCard(customerId, cardId)

// Process payment with stored card

result = processPayment(orderData, storedCard, browserData)

Pattern 2: Guest Checkout #

For one-time payments without customer accounts:

// Collect card on checkout page

cardDetails = collectCardFromForm()

// Process immediately

result = processPayment(orderData, cardDetails, browserData)

Pattern 3: Retry Failed Payments #

If a payment fails, allow the customer to try a different card:

if paymentResult.status == "FAILED":

    // Show error and allow new card entry

    showError("Payment declined. Please try a different card.")

    resetPaymentForm()

Security Best Practices #

1. Never log or store raw card details on your servers

2. Use HTTPS for all communication

3. Validate card details on the frontend before submission

4. Implement rate limiting to prevent abuse

5. Verify webhook signatures to prevent spoofing

6. Use idempotency keys to prevent duplicate charges

7. Sanitize all input to prevent injection attacks

Troubleshooting #

Payment Returns “Uncaught exception” #

– Check that all required fields are provided
– Verify card number length (13-19 digits)
– Ensure expiry date is in the future
– Check CVC length (3-4 digits)

Authentication iframe doesn’t load #

– Verify the `requiredAction.url` is not blocked by CSP
– Check browser console for errors
  (No SDK is required.)

Webhook not received #

– Verify webhook URL is publicly accessible
– Check webhook signature verification
– Review webhook logs in TrustistEcommerce portal
– Test webhook endpoint using Postman

Payment status stays “PROCESSING” #

– Wait for webhook notification (may take a few seconds)
  (If you see repeated processing, contact support.)
– Contact support if status doesn’t update within 2 minutes

Support #

For additional help:

Documentation: https://trustisttransfer.com/docs/
Email: info@trustist.com
API Status: Check the Trustist status page for any service issues

Related Documentation #

Authentication Guide

Merchant Webhooks

Payment Statuses Guide

Getting Your API Keys

Standard Payment Integration

Powered by BetterDocs

Leave a Reply

Your email address will not be published. Required fields are marked *