Table of contents

Code examples

Introduction

This document provides production-ready code examples in multiple programming languages for integrating with Precium's Server-to-Server API.

Python

Installation

BASH

pip install requests

Complete Integration Class

PYTHON

"""
Precium S2S Integration - Python
Requires: requests
"""
import requests
from typing import Optional, Dict, Any
from dataclasses import dataclass
from enum import Enum
import json

class PreciumEnvironment(Enum):
    SANDBOX = "https://gate.reviopay.com/api/v1"
    PRODUCTION = "https://gate.reviopay.com/api/v1"

@dataclass
class CardData:
    cardholder_name: str
    card_number: str
    expires: str  # MM/YY format
    cvc: str
    remote_ip: str

@dataclass
class BrowserFingerprint:
    user_agent: str
    accept_header: str
    language: str
    java_enabled: bool
    javascript_enabled: bool
    color_depth: int
    utc_offset: int
    screen_width: int
    screen_height: int

class PreciumS2SClient:
    """Precium Server-to-Server API Client"""

    def __init__(
        self,
        standard_api_key: str,
        s2s_api_key: str,
        brand_id: str,
        environment: PreciumEnvironment = PreciumEnvironment.SANDBOX
    ):
        self.standard_api_key = standard_api_key
        self.s2s_api_key = s2s_api_key
        self.brand_id = brand_id
        self.base_url = environment.value

    def _headers(self, use_s2s: bool = False) -> Dict[str, str]:
        """Generate request headers with appropriate API key."""
        api_key = self.s2s_api_key if use_s2s else self.standard_api_key
        return {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json"
        }

    def _request(
        self,
        method: str,
        endpoint: str,
        data: Optional[Dict] = None,
        use_s2s: bool = False
    ) -> Dict[str, Any]:
        """Make an API request."""
        url = f"{self.base_url}{endpoint}"
        response = requests.request(
            method=method,
            url=url,
            headers=self._headers(use_s2s),
            json=data
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Client Management
    # =========================================================================

    def create_client(self, email: str, phone: Optional[str] = None) -> Dict:
        """
        Create a new client record.

        Args:
            email: Customer's email address
            phone: Optional phone number in international format

        Returns:
            Client object with ID
        """
        payload = {"email": email}
        if phone:
            payload["phone"] = phone
        return self._request("POST", "/clients/", payload)

    def get_client(self, client_id: str) -> Dict:
        """Retrieve a client by ID."""
        return self._request("GET", f"/clients/{client_id}/")

    # =========================================================================
    # Purchase Management
    # =========================================================================

    def create_purchase(
        self,
        client_id: str,
        amount_cents: int,
        currency: str,
        product_name: str,
        success_redirect: str,
        failure_redirect: str,
        force_recurring: bool = False,
        metadata: Optional[Dict] = None
    ) -> Dict:
        """
        Create a new purchase.

        Args:
            client_id: The client's ID
            amount_cents: Amount in cents (e.g., 29900 for R299.00)
            currency: ISO 4217 currency code (e.g., "ZAR")
            product_name: Description of the product/service
            success_redirect: URL for successful payment redirect
            failure_redirect: URL for failed payment redirect
            force_recurring: Set True to generate token for future MIT
            metadata: Optional custom key-value data

        Returns:
            Purchase object with direct_post_url
        """
        payload = {
            "client_id": client_id,
            "brand_id": self.brand_id,
            "currency": currency,
            "products": [{"name": product_name, "price": amount_cents}],
            "success_redirect": success_redirect,
            "failure_redirect": failure_redirect,
        }
        if force_recurring:
            payload["force_recurring"] = True
        if metadata:
            payload["metadata"] = metadata

        return self._request("POST", "/purchases/", payload)

    def create_mit_purchase(
        self,
        client_id: str,
        amount_cents: int,
        currency: str,
        product_name: str,
        card_token: str,
        network_transaction_id: str,
        original_amount_cents: int
    ) -> Dict:
        """
        Create a purchase for Merchant-Initiated Transaction (recurring).

        Args:
            client_id: The client's ID
            amount_cents: Amount in cents
            currency: ISO 4217 currency code
            product_name: Description of the charge
            card_token: Token from original CIT transaction
            network_transaction_id: Network transaction ID from original
            original_amount_cents: Amount from original tokenization

        Returns:
            Purchase object ready for MIT charge
        """
        payload = {
            "client_id": client_id,
            "brand_id": self.brand_id,
            "currency": currency,
            "products": [{"name": product_name, "price": amount_cents}],
            "payment_method_details": {
                "card_token": card_token,
                "previous_network_transaction_id": network_transaction_id,
                "original_amount_cents": original_amount_cents
            },
            "is_recurring": True
        }
        return self._request("POST", "/purchases/", payload)

    def get_purchase(self, purchase_id: str) -> Dict:
        """Retrieve purchase details by ID."""
        return self._request("GET", f"/purchases/{purchase_id}/")

    # =========================================================================
    # Card Data Submission (Direct Post)
    # =========================================================================

    def submit_card_data(
        self,
        direct_post_url: str,
        card: CardData,
        fingerprint: BrowserFingerprint,
        remember_card: bool = False
    ) -> Dict:
        """
        Submit card data to the direct post URL.

        This method handles the S2S card submission for CIT with internal 3DS.

        Args:
            direct_post_url: The URL from purchase creation
            card: Card details
            fingerprint: Browser fingerprint data for 3DS
            remember_card: Whether to tokenize the card

        Returns:
            Response containing 3DS parameters or ready status
        """
        payload = {
            "cardholder_name": card.cardholder_name,
            "card_number": card.card_number,
            "expires": card.expires,
            "cvc": card.cvc,
            "remember_card": str(remember_card).lower(),
            "remote_ip": card.remote_ip,
            "user_agent": fingerprint.user_agent,
            "accept_header": fingerprint.accept_header,
            "language": fingerprint.language,
            "java_enabled": str(fingerprint.java_enabled).lower(),
            "javascript_enabled": str(fingerprint.javascript_enabled).lower(),
            "color_depth": str(fingerprint.color_depth),
            "utc_offset": str(fingerprint.utc_offset),
            "screen_width": str(fingerprint.screen_width),
            "screen_height": str(fingerprint.screen_height),
        }

        headers = {
            "Authorization": f"Bearer {self.s2s_api_key}",
            "Content-Type": "application/x-www-form-urlencoded"
        }

        response = requests.post(
            direct_post_url,
            headers=headers,
            data=payload
        )
        response.raise_for_status()
        return response.json()

    def submit_token_for_mit(
        self,
        direct_post_url: str,
        card_token: str,
        server_ip: str
    ) -> Dict:
        """
        Submit token for MIT transaction.

        Args:
            direct_post_url: The URL from MIT purchase creation
            card_token: The stored card token
            server_ip: Your server's IP address

        Returns:
            Response indicating ready for charge
        """
        payload = {
            "card_token": card_token,
            "remote_ip": server_ip
        }

        headers = {
            "Authorization": f"Bearer {self.s2s_api_key}",
            "Content-Type": "application/x-www-form-urlencoded"
        }

        response = requests.post(
            direct_post_url,
            headers=headers,
            data=payload
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Charge and Refund
    # =========================================================================

    def charge_purchase(self, purchase_id: str) -> Dict:
        """
        Finalize the transaction and charge the card.

        Call this after 3DS completion or for MIT transactions.

        Args:
            purchase_id: The purchase ID to charge

        Returns:
            Transaction result with payment details and token
        """
        return self._request("POST", f"/purchases/{purchase_id}/charge/")

    def refund_purchase(
        self,
        purchase_id: str,
        amount_cents: Optional[int] = None
    ) -> Dict:
        """
        Process a refund for a purchase.

        Args:
            purchase_id: The purchase ID to refund
            amount_cents: Amount to refund in cents (None for full refund)

        Returns:
            Refund confirmation
        """
        payload = {}
        if amount_cents:
            payload["amount"] = amount_cents
        return self._request("POST", f"/purchases/{purchase_id}/refund/", payload)

    # =========================================================================
    # Pre-Authorization Operations
    # =========================================================================

    def capture_purchase(
        self,
        purchase_id: str,
        amount_cents: Optional[int] = None
    ) -> Dict:
        """
        Capture a pre-authorized transaction.

        Args:
            purchase_id: The purchase ID to capture
            amount_cents: Amount to capture in cents (None for full capture)

        Returns:
            Capture confirmation
        """
        payload = {}
        if amount_cents:
            payload["amount"] = amount_cents
        return self._request("POST", f"/purchases/{purchase_id}/capture/", payload)

    def void_purchase(self, purchase_id: str) -> Dict:
        """
        Void/cancel a pre-authorized or captured transaction (before settlement).

        Args:
            purchase_id: The purchase ID to void

        Returns:
            Void confirmation
        """
        return self._request("POST", f"/purchases/{purchase_id}/cancel/")

    def void_refund(self, purchase_id: str, refund_id: str) -> Dict:
        """
        Void a refund before settlement.

        Args:
            purchase_id: The original purchase ID
            refund_id: The refund ID to void

        Returns:
            Void confirmation
        """
        return self._request("POST", f"/purchases/{purchase_id}/refund/{refund_id}/cancel/")

    # =========================================================================
    # Zero Authorization (Card Validation)
    # =========================================================================

    def create_zero_auth_purchase(
        self,
        client_id: str,
        currency: str,
        success_redirect: str,
        failure_redirect: str,
        force_recurring: bool = True
    ) -> Dict:
        """
        Create a zero-amount authorization for card validation.

        Args:
            client_id: The client's ID
            currency: ISO 4217 currency code
            success_redirect: URL for successful validation
            failure_redirect: URL for failed validation
            force_recurring: Set True to tokenize the card

        Returns:
            Purchase object for zero authorization
        """
        payload = {
            "client_id": client_id,
            "brand_id": self.brand_id,
            "currency": currency,
            "products": [{"name": "Card Validation", "price": 0}],
            "success_redirect": success_redirect,
            "failure_redirect": failure_redirect,
        }
        if force_recurring:
            payload["force_recurring"] = True
        return self._request("POST", "/purchases/", payload)

    # =========================================================================
    # External 3DS (MPI)
    # =========================================================================

    def create_external_3ds_purchase(
        self,
        client_id: str,
        amount_cents: int,
        currency: str,
        product_name: str,
        success_redirect: str,
        failure_redirect: str,
        authentication_transaction_id: str,
        cavv: str,
        eci: str,
        xid: Optional[str] = None,
        ds_trans_id: Optional[str] = None
    ) -> Dict:
        """
        Create a purchase with external 3DS authentication.

        Args:
            client_id: The client's ID
            amount_cents: Amount in cents
            currency: ISO 4217 currency code
            product_name: Description of product/service
            success_redirect: URL for successful payment
            failure_redirect: URL for failed payment
            authentication_transaction_id: Your MPI's transaction ID
            cavv: Cardholder Authentication Verification Value
            eci: Electronic Commerce Indicator
            xid: Transaction ID (3DS1 only)
            ds_trans_id: Directory Server Transaction ID (3DS2 only)

        Returns:
            Purchase object with direct_post_url
        """
        payment_details = {
            "card": {
                "is_external_3DS": True,
                "authentication_transaction_id": authentication_transaction_id,
                "cavv": cavv,
                "eci_raw": eci
            }
        }
        if xid:
            payment_details["card"]["xid"] = xid
        if ds_trans_id:
            payment_details["card"]["ds_trans_id"] = ds_trans_id

        payload = {
            "client_id": client_id,
            "brand_id": self.brand_id,
            "currency": currency,
            "products": [{"name": product_name, "price": amount_cents}],
            "payment_method_details": payment_details,
            "success_redirect": success_redirect,
            "failure_redirect": failure_redirect,
        }
        return self._request("POST", "/purchases/", payload)

# =============================================================================
# Usage Examples
# =============================================================================

def example_cit_with_internal_3ds():
    """Example: Customer-Initiated Transaction with Internal 3DS"""

    client = PreciumS2SClient(
        standard_api_key='your_standard_api_key',
        s2s_api_key='your_s2s_api_key',
        brand_id='your_brand_id'
    )

    # Step 1: Create client
    customer = client.create_client(email='customer@example.com')
    print(f"Created client: {customer['id']}")

    # Step 2: Create purchase
    purchase = client.create_purchase(
        client_id=customer['id'],
        amount_cents=29900,
        currency='ZAR',
        product_name='Premium Subscription',
        success_redirect='https://yoursite.com/success',
        failure_redirect='https://yoursite.com/failure',
        force_recurring=True  # Generate token for future recurring
    )
    print(f"Created purchase: {purchase['id']}")
    print(f"Direct post URL: {purchase['direct_post_url']}")

    # Step 3: Submit card data
    card = CardData(
        cardholder_name='John Smith',
        card_number='4000000000001091',
        expires='12/28',
        cvc='123',
        remote_ip='196.21.45.123'
    )

    fingerprint = BrowserFingerprint(
        user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        accept_header='text/html,application/xhtml+xml',
        language='en-ZA',
        java_enabled=False,
        javascript_enabled=True,
        color_depth=24,
        utc_offset=-120,
        screen_width=1920,
        screen_height=1080
    )

    result = client.submit_card_data(
        direct_post_url=purchase['direct_post_url'],
        card=card,
        fingerprint=fingerprint,
        remember_card=True
    )

    # Step 4: Handle 3DS (in production, redirect customer)
    if '3ds' in result:
        print("3DS authentication required")
        print(f"3DS URL: {result['3ds']['3ds_url']}")
        print(f"MD: {result['3ds']['MD']}")
        # Redirect customer to 3DS URL...

    # Step 5: After 3DS completion, charge
    charge_result = client.charge_purchase(purchase['id'])
    print(f"Charge result: {charge_result['status']}")

    if charge_result['status'] == 'paid':
        # Store token for future MIT
        token = charge_result['payment_method_details']['card']['token']
        network_txn_id = charge_result['payment_method_details']['card']['network_transaction_id']
        print(f"Token for recurring: {token}")
        print(f"Network Transaction ID: {network_txn_id}")

def example_mit_recurring():
    """Example: Merchant-Initiated Recurring Transaction"""

    client = PreciumS2SClient(
        standard_api_key='your_standard_api_key',
        s2s_api_key='your_s2s_api_key',
        brand_id='your_brand_id'
    )

    # Stored from original CIT transaction
    stored_token = "tok_abc123def456..."
    stored_network_txn_id = "MCC123456789012345"
    original_amount = 29900

    # Create MIT purchase
    purchase = client.create_mit_purchase(
        client_id='existing_client_id',
        amount_cents=29900,
        currency='ZAR',
        product_name='Monthly Subscription - February 2026',
        card_token=stored_token,
        network_transaction_id=stored_network_txn_id,
        original_amount_cents=original_amount
    )

    # Submit token
    client.submit_token_for_mit(
        direct_post_url=purchase['direct_post_url'],
        card_token=stored_token,
        server_ip='10.0.0.1'  # Your server IP
    )

    # Charge
    result = client.charge_purchase(purchase['id'])
    print(f"MIT charge result: {result['status']}")

def example_zero_authorization():
    """Example: Zero Authorization for Card Validation"""

    client = PreciumS2SClient(
        standard_api_key='your_standard_api_key',
        s2s_api_key='your_s2s_api_key',
        brand_id='your_brand_id'
    )

    # Create client
    customer = client.create_client(email='customer@example.com')

    # Create zero-amount purchase for card validation
    purchase = client.create_zero_auth_purchase(
        client_id=customer['id'],
        currency='ZAR',
        success_redirect='https://yoursite.com/validation/success',
        failure_redirect='https://yoursite.com/validation/failure',
        force_recurring=True  # Tokenize for future use
    )
    print(f"Zero auth purchase: {purchase['id']}")

    # Submit card data (same as standard flow)
    card = CardData(
        cardholder_name='John Smith',
        card_number='4000000000001091',
        expires='12/28',
        cvc='123',
        remote_ip='196.21.45.123'
    )

    fingerprint = BrowserFingerprint(
        user_agent='Mozilla/5.0',
        accept_header='text/html',
        language='en-ZA',
        java_enabled=False,
        javascript_enabled=True,
        color_depth=24,
        utc_offset=-120,
        screen_width=1920,
        screen_height=1080
    )

    result = client.submit_card_data(
        direct_post_url=purchase['direct_post_url'],
        card=card,
        fingerprint=fingerprint,
        remember_card=True
    )

    # Handle 3DS if required, then charge to validate
    # Card is validated without any charge

def example_pre_authorization():
    """Example: Pre-Authorization with Later Capture"""

    client = PreciumS2SClient(
        standard_api_key='your_standard_api_key',
        s2s_api_key='your_s2s_api_key',
        brand_id='your_brand_id'
    )

    # Create client
    customer = client.create_client(email='guest@hotel.com')

    # Pre-authorize for hotel stay (estimated amount)
    purchase = client.create_purchase(
        client_id=customer['id'],
        amount_cents=500000,  # R5,000 estimated
        currency='ZAR',
        product_name='Hotel Stay - 3 nights (estimated)',
        success_redirect='https://hotel.com/booking/confirmed',
        failure_redirect='https://hotel.com/booking/failed',
        force_recurring=False
    )

    # Submit card data and complete 3DS (same as standard flow)
    # ... card submission code ...

    # Later: Guest checks out, actual bill is R4,500
    # Partial capture
    capture_result = client.capture_purchase(
        purchase_id=purchase['id'],
        amount_cents=450000  # Capture only R4,500
    )
    print(f"Captured: {capture_result['status']}")

    # Alternative: Guest cancels before checkout
    # Void the pre-authorization
    # void_result = client.void_purchase(purchase['id'])
    # print(f"Voided: {void_result['status']}")

def example_partial_refund():
    """Example: Partial Refund Flow"""

    client = PreciumS2SClient(
        standard_api_key='your_standard_api_key',
        s2s_api_key='your_s2s_api_key',
        brand_id='your_brand_id'
    )

    # Assume we have a completed purchase
    purchase_id = "existing_paid_purchase_id"

    # Customer returns one item worth R100
    refund_result = client.refund_purchase(
        purchase_id=purchase_id,
        amount_cents=10000  # R100 partial refund
    )
    print(f"Partial refund: {refund_result['status']}")
    print(f"Refund ID: {refund_result.get('refund_id')}")

    # Can issue multiple partial refunds up to original amount

def example_external_3ds():
    """Example: External 3DS (MPI) Flow"""

    client = PreciumS2SClient(
        standard_api_key='your_standard_api_key',
        s2s_api_key='your_s2s_api_key',
        brand_id='your_brand_id'  # Must be Non-3DS brand
    )

    # Create client
    customer = client.create_client(email='customer@example.com')

    # After completing 3DS with your MPI, create purchase with auth data
    purchase = client.create_external_3ds_purchase(
        client_id=customer['id'],
        amount_cents=50000,
        currency='ZAR',
        product_name='External 3DS Purchase',
        success_redirect='https://yoursite.com/success',
        failure_redirect='https://yoursite.com/failure',
        authentication_transaction_id='GAux5DosxbhI35RL8nc0',
        cavv='AAABBBCCCdddEEEfff111222333=',
        eci='05',
        ds_trans_id='a1b2c3d4-e5f6-7890-abcd-ef1234567890'  # 3DS2
    )

    # Submit card data (no browser fingerprint needed)
    card = CardData(
        cardholder_name='John Smith',
        card_number='4000000000001091',
        expires='12/28',
        cvc='000',  # Can use placeholder for external 3DS
        remote_ip='8.8.8.8'
    )

    # ... submit card and charge ...

if __name__ == "__main__":
    example_cit_with_internal_3ds()

Node.js

Installation

BASH

npm install axios

Complete Integration Module

JSX

/**
 * Precium S2S Integration - Node.js
 * Requires: axios
 */

const axios = require('axios');

const ENVIRONMENTS = {
  SANDBOX: 'https://gate.reviopay.com/api/v1',
  PRODUCTION: 'https://gate.reviopay.com/api/v1'
};

class PreciumS2SClient {
  /**
   * Initialize the Precium S2S client
   * @param {Object} config Configuration object
   * @param {string} config.standardApiKey Standard API key for general requests
   * @param {string} config.s2sApiKey S2S API key for card data submission
   * @param {string} config.brandId Your brand ID
   * @param {string} [config.environment='SANDBOX'] Environment to use
   */
  constructor({ standardApiKey, s2sApiKey, brandId, environment = 'SANDBOX' }) {
    this.standardApiKey = standardApiKey;
    this.s2sApiKey = s2sApiKey;
    this.brandId = brandId;
    this.baseUrl = ENVIRONMENTS[environment];
  }

  /**
   * Get headers for API requests
   * @private
   */
  _getHeaders(useS2S = false) {
    const apiKey = useS2S ? this.s2sApiKey : this.standardApiKey;
    return {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };
  }

  /**
   * Make an API request
   * @private
   */
  async _request(method, endpoint, data = null, useS2S = false) {
    const url = `${this.baseUrl}${endpoint}`;
    const response = await axios({
      method,
      url,
      headers: this._getHeaders(useS2S),
      data
    });
    return response.data;
  }

  // ===========================================================================
  // Client Management
  // ===========================================================================

  /**
   * Create a new client record
   * @param {string} email Customer's email address
   * @param {string} [phone] Optional phone number
   * @returns {Promise<Object>} Client object with ID
   */
  async createClient(email, phone = null) {
    const payload = { email };
    if (phone) payload.phone = phone;
    return this._request('POST', '/clients/', payload);
  }

  /**
   * Get client by ID
   * @param {string} clientId Client ID
   * @returns {Promise<Object>} Client object
   */
  async getClient(clientId) {
    return this._request('GET', `/clients/${clientId}/`);
  }

  // ===========================================================================
  // Purchase Management
  // ===========================================================================

  /**
   * Create a new purchase
   * @param {Object} options Purchase options
   * @returns {Promise<Object>} Purchase object with direct_post_url
   */
  async createPurchase({
    clientId,
    amountCents,
    currency,
    productName,
    successRedirect,
    failureRedirect,
    forceRecurring = false,
    metadata = null
  }) {
    const payload = {
      client_id: clientId,
      brand_id: this.brandId,
      currency,
      products: [{ name: productName, price: amountCents }],
      success_redirect: successRedirect,
      failure_redirect: failureRedirect
    };

    if (forceRecurring) payload.force_recurring = true;
    if (metadata) payload.metadata = metadata;

    return this._request('POST', '/purchases/', payload);
  }

  /**
   * Create MIT (recurring) purchase
   * @param {Object} options MIT purchase options
   * @returns {Promise<Object>} Purchase object
   */
  async createMITPurchase({
    clientId,
    amountCents,
    currency,
    productName,
    cardToken,
    networkTransactionId,
    originalAmountCents
  }) {
    const payload = {
      client_id: clientId,
      brand_id: this.brandId,
      currency,
      products: [{ name: productName, price: amountCents }],
      payment_method_details: {
        card_token: cardToken,
        previous_network_transaction_id: networkTransactionId,
        original_amount_cents: originalAmountCents
      },
      is_recurring: true
    };

    return this._request('POST', '/purchases/', payload);
  }

  /**
   * Get purchase by ID
   * @param {string} purchaseId Purchase ID
   * @returns {Promise<Object>} Purchase object
   */
  async getPurchase(purchaseId) {
    return this._request('GET', `/purchases/${purchaseId}/`);
  }

  // ===========================================================================
  // Card Data Submission
  // ===========================================================================

  /**
   * Submit card data to direct post URL
   * @param {string} directPostUrl URL from purchase creation
   * @param {Object} cardData Card details
   * @param {Object} fingerprint Browser fingerprint
   * @param {boolean} [rememberCard=false] Whether to tokenize
   * @returns {Promise<Object>} 3DS parameters or ready status
   */
  async submitCardData(directPostUrl, cardData, fingerprint, rememberCard = false) {
    const formData = new URLSearchParams({
      cardholder_name: cardData.cardholderName,
      card_number: cardData.cardNumber,
      expires: cardData.expires,
      cvc: cardData.cvc,
      remember_card: rememberCard.toString(),
      remote_ip: cardData.remoteIp,
      user_agent: fingerprint.userAgent,
      accept_header: fingerprint.acceptHeader,
      language: fingerprint.language,
      java_enabled: fingerprint.javaEnabled.toString(),
      javascript_enabled: fingerprint.javascriptEnabled.toString(),
      color_depth: fingerprint.colorDepth.toString(),
      utc_offset: fingerprint.utcOffset.toString(),
      screen_width: fingerprint.screenWidth.toString(),
      screen_height: fingerprint.screenHeight.toString()
    });

    const response = await axios.post(directPostUrl, formData.toString(), {
      headers: {
        'Authorization': `Bearer ${this.s2sApiKey}`,
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    return response.data;
  }

  /**
   * Submit token for MIT transaction
   * @param {string} directPostUrl URL from MIT purchase
   * @param {string} cardToken Stored card token
   * @param {string} serverIp Your server's IP
   * @returns {Promise<Object>} Ready status
   */
  async submitTokenForMIT(directPostUrl, cardToken, serverIp) {
    const formData = new URLSearchParams({
      card_token: cardToken,
      remote_ip: serverIp
    });

    const response = await axios.post(directPostUrl, formData.toString(), {
      headers: {
        'Authorization': `Bearer ${this.s2sApiKey}`,
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });

    return response.data;
  }

  // ===========================================================================
  // Charge and Refund
  // ===========================================================================

  /**
   * Charge a purchase
   * @param {string} purchaseId Purchase ID to charge
   * @returns {Promise<Object>} Transaction result
   */
  async chargePurchase(purchaseId) {
    return this._request('POST', `/purchases/${purchaseId}/charge/`);
  }

  /**
   * Refund a purchase
   * @param {string} purchaseId Purchase ID to refund
   * @param {number} [amountCents] Amount in cents (null for full refund)
   * @returns {Promise<Object>} Refund confirmation
   */
  async refundPurchase(purchaseId, amountCents = null) {
    const payload = amountCents ? { amount: amountCents } : {};
    return this._request('POST', `/purchases/${purchaseId}/refund/`, payload);
  }
}

// =============================================================================
// Usage Examples
// =============================================================================

async function exampleCITWithInternal3DS() {
  const client = new PreciumS2SClient({
    standardApiKey: 'your_standard_api_key',
    s2sApiKey: 'your_s2s_api_key',
    brandId: 'your_brand_id'
  });

  try {
    // Step 1: Create client
    const customer = await client.createClient('customer@example.com');
    console.log('Created client:', customer.id);

    // Step 2: Create purchase
    const purchase = await client.createPurchase({
      clientId: customer.id,
      amountCents: 29900,
      currency: 'ZAR',
      productName: 'Premium Subscription',
      successRedirect: 'https://yoursite.com/success',
      failureRedirect: 'https://yoursite.com/failure',
      forceRecurring: true
    });
    console.log('Created purchase:', purchase.id);

    // Step 3: Submit card data
    const cardData = {
      cardholderName: 'John Smith',
      cardNumber: '4000000000001091',
      expires: '12/28',
      cvc: '123',
      remoteIp: '196.21.45.123'
    };

    const fingerprint = {
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
      acceptHeader: 'text/html,application/xhtml+xml',
      language: 'en-ZA',
      javaEnabled: false,
      javascriptEnabled: true,
      colorDepth: 24,
      utcOffset: -120,
      screenWidth: 1920,
      screenHeight: 1080
    };

    const submitResult = await client.submitCardData(
      purchase.direct_post_url,
      cardData,
      fingerprint,
      true
    );

    // Step 4: Handle 3DS if required
    if (submitResult['3ds']) {
      console.log('3DS required - redirect customer to:', submitResult['3ds']['3ds_url']);
      // In production: redirect customer to 3DS URL
    }

    // Step 5: Charge after 3DS
    const chargeResult = await client.chargePurchase(purchase.id);
    console.log('Charge status:', chargeResult.status);

    if (chargeResult.status === 'paid') {
      console.log('Token:', chargeResult.payment_method_details.card.token);
    }

  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
  }
}

module.exports = { PreciumS2SClient, ENVIRONMENTS };

PHP

Complete Integration Class

PHP

<?php
/**
 * Precium S2S Integration - PHP
 * Requires: PHP 7.4+ with cURL
 */

declare(strict_types=1);

class PreciumS2SClient
{
    private const ENVIRONMENTS = [
        'SANDBOX' => 'https://gate.reviopay.com/api/v1',
        'PRODUCTION' => 'https://gate.reviopay.com/api/v1'
    ];

    private string $standardApiKey;
    private string $s2sApiKey;
    private string $brandId;
    private string $baseUrl;

    public function __construct(
        string $standardApiKey,
        string $s2sApiKey,
        string $brandId,
        string $environment = 'SANDBOX'
    ) {
        $this->standardApiKey = $standardApiKey;
        $this->s2sApiKey = $s2sApiKey;
        $this->brandId = $brandId;
        $this->baseUrl = self::ENVIRONMENTS[$environment];
    }

    /**
     * Make an API request
     */
    private function request(
        string $method,
        string $endpoint,
        ?array $data = null,
        bool $useS2S = false
    ): array {
        $apiKey = $useS2S ? $this->s2sApiKey : $this->standardApiKey;
        $url = $this->baseUrl . $endpoint;

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$apiKey}",
                'Content-Type: application/json',
                'Accept: application/json'
            ]
        ]);

        if ($data !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode >= 400) {
            throw new Exception("API Error: HTTP {$httpCode} - {$response}");
        }

        return json_decode($response, true);
    }

    // =========================================================================
    // Client Management
    // =========================================================================

    /**
     * Create a new client
     */
    public function createClient(string $email, ?string $phone = null): array
    {
        $payload = ['email' => $email];
        if ($phone !== null) {
            $payload['phone'] = $phone;
        }
        return $this->request('POST', '/clients/', $payload);
    }

    /**
     * Get client by ID
     */
    public function getClient(string $clientId): array
    {
        return $this->request('GET', "/clients/{$clientId}/");
    }

    // =========================================================================
    // Purchase Management
    // =========================================================================

    /**
     * Create a new purchase
     */
    public function createPurchase(
        string $clientId,
        int $amountCents,
        string $currency,
        string $productName,
        string $successRedirect,
        string $failureRedirect,
        bool $forceRecurring = false,
        ?array $metadata = null
    ): array {
        $payload = [
            'client_id' => $clientId,
            'brand_id' => $this->brandId,
            'currency' => $currency,
            'products' => [['name' => $productName, 'price' => $amountCents]],
            'success_redirect' => $successRedirect,
            'failure_redirect' => $failureRedirect
        ];

        if ($forceRecurring) {
            $payload['force_recurring'] = true;
        }
        if ($metadata !== null) {
            $payload['metadata'] = $metadata;
        }

        return $this->request('POST', '/purchases/', $payload);
    }

    /**
     * Create MIT (recurring) purchase
     */
    public function createMITPurchase(
        string $clientId,
        int $amountCents,
        string $currency,
        string $productName,
        string $cardToken,
        string $networkTransactionId,
        int $originalAmountCents
    ): array {
        $payload = [
            'client_id' => $clientId,
            'brand_id' => $this->brandId,
            'currency' => $currency,
            'products' => [['name' => $productName, 'price' => $amountCents]],
            'payment_method_details' => [
                'card_token' => $cardToken,
                'previous_network_transaction_id' => $networkTransactionId,
                'original_amount_cents' => $originalAmountCents
            ],
            'is_recurring' => true
        ];

        return $this->request('POST', '/purchases/', $payload);
    }

    // =========================================================================
    // Card Data Submission
    // =========================================================================

    /**
     * Submit card data to direct post URL
     */
    public function submitCardData(
        string $directPostUrl,
        array $cardData,
        array $fingerprint,
        bool $rememberCard = false
    ): array {
        $postData = http_build_query([
            'cardholder_name' => $cardData['cardholder_name'],
            'card_number' => $cardData['card_number'],
            'expires' => $cardData['expires'],
            'cvc' => $cardData['cvc'],
            'remember_card' => $rememberCard ? 'true' : 'false',
            'remote_ip' => $cardData['remote_ip'],
            'user_agent' => $fingerprint['user_agent'],
            'accept_header' => $fingerprint['accept_header'],
            'language' => $fingerprint['language'],
            'java_enabled' => $fingerprint['java_enabled'] ? 'true' : 'false',
            'javascript_enabled' => $fingerprint['javascript_enabled'] ? 'true' : 'false',
            'color_depth' => (string)$fingerprint['color_depth'],
            'utc_offset' => (string)$fingerprint['utc_offset'],
            'screen_width' => (string)$fingerprint['screen_width'],
            'screen_height' => (string)$fingerprint['screen_height']
        ]);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $directPostUrl,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->s2sApiKey}",
                'Content-Type: application/x-www-form-urlencoded'
            ]
        ]);

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

        return json_decode($response, true);
    }

    /**
     * Submit token for MIT
     */
    public function submitTokenForMIT(
        string $directPostUrl,
        string $cardToken,
        string $serverIp
    ): array {
        $postData = http_build_query([
            'card_token' => $cardToken,
            'remote_ip' => $serverIp
        ]);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $directPostUrl,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->s2sApiKey}",
                'Content-Type: application/x-www-form-urlencoded'
            ]
        ]);

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

        return json_decode($response, true);
    }

    // =========================================================================
    // Charge and Refund
    // =========================================================================

    /**
     * Charge a purchase
     */
    public function chargePurchase(string $purchaseId): array
    {
        return $this->request('POST', "/purchases/{$purchaseId}/charge/");
    }

    /**
     * Refund a purchase
     */
    public function refundPurchase(string $purchaseId, ?int $amountCents = null): array
    {
        $payload = $amountCents !== null ? ['amount' => $amountCents] : [];
        return $this->request('POST', "/purchases/{$purchaseId}/refund/", $payload);
    }
}

// =============================================================================
// Usage Example
// =============================================================================

/*
$client = new PreciumS2SClient(
    'your_standard_api_key',
    'your_s2s_api_key',
    'your_brand_id'
);

// Create client
$customer = $client->createClient('customer@example.com');

// Create purchase
$purchase = $client->createPurchase(
    $customer['id'],
    29900,
    'ZAR',
    'Premium Subscription',
    'https://yoursite.com/success',
    'https://yoursite.com/failure',
    true
);

// Submit card data
$cardData = [
    'cardholder_name' => 'John Smith',
    'card_number' => '4000000000001091',
    'expires' => '12/28',
    'cvc' => '123',
    'remote_ip' => '196.21.45.123'
];

$fingerprint = [
    'user_agent' => 'Mozilla/5.0',
    'accept_header' => 'text/html',
    'language' => 'en-ZA',
    'java_enabled' => false,
    'javascript_enabled' => true,
    'color_depth' => 24,
    'utc_offset' => -120,
    'screen_width' => 1920,
    'screen_height' => 1080
];

$result = $client->submitCardData(
    $purchase['direct_post_url'],
    $cardData,
    $fingerprint,
    true
);

// Charge
$chargeResult = $client->chargePurchase($purchase['id']);
*/

Ruby

RUBY

# Precium S2S Integration - Ruby
# Requires: httparty gem

require 'httparty'
require 'json'

class PreciumS2SClient
  ENVIRONMENTS = {
    sandbox: 'https://gate.reviopay.com/api/v1',
    production: 'https://gate.reviopay.com/api/v1'
  }.freeze

  def initialize(standard_api_key:, s2s_api_key:, brand_id:, environment: :sandbox)
    @standard_api_key = standard_api_key
    @s2s_api_key = s2s_api_key
    @brand_id = brand_id
    @base_url = ENVIRONMENTS[environment]
  end

  # Client Management
  def create_client(email:, phone: nil)
    payload = { email: email }
    payload[:phone] = phone if phone
    request(:post, '/clients/', payload)
  end

  def get_client(client_id)
    request(:get, "/clients/#{client_id}/")
  end

  # Purchase Management
  def create_purchase(client_id:, amount_cents:, currency:, product_name:,
                      success_redirect:, failure_redirect:, force_recurring: false)
    payload = {
      client_id: client_id,
      brand_id: @brand_id,
      currency: currency,
      products: [{ name: product_name, price: amount_cents }],
      success_redirect: success_redirect,
      failure_redirect: failure_redirect
    }
    payload[:force_recurring] = true if force_recurring
    request(:post, '/purchases/', payload)
  end

  def create_mit_purchase(client_id:, amount_cents:, currency:, product_name:,
                          card_token:, network_transaction_id:, original_amount_cents:)
    payload = {
      client_id: client_id,
      brand_id: @brand_id,
      currency: currency,
      products: [{ name: product_name, price: amount_cents }],
      payment_method_details: {
        card_token: card_token,
        previous_network_transaction_id: network_transaction_id,
        original_amount_cents: original_amount_cents
      },
      is_recurring: true
    }
    request(:post, '/purchases/', payload)
  end

  # Card Data Submission
  def submit_card_data(direct_post_url:, card_data:, fingerprint:, remember_card: false)
    form_data = {
      cardholder_name: card_data[:cardholder_name],
      card_number: card_data[:card_number],
      expires: card_data[:expires],
      cvc: card_data[:cvc],
      remember_card: remember_card.to_s,
      remote_ip: card_data[:remote_ip],
      user_agent: fingerprint[:user_agent],
      accept_header: fingerprint[:accept_header],
      language: fingerprint[:language],
      java_enabled: fingerprint[:java_enabled].to_s,
      javascript_enabled: fingerprint[:javascript_enabled].to_s,
      color_depth: fingerprint[:color_depth].to_s,
      utc_offset: fingerprint[:utc_offset].to_s,
      screen_width: fingerprint[:screen_width].to_s,
      screen_height: fingerprint[:screen_height].to_s
    }

    response = HTTParty.post(
      direct_post_url,
      headers: {
        'Authorization' => "Bearer #{@s2s_api_key}",
        'Content-Type' => 'application/x-www-form-urlencoded'
      },
      body: URI.encode_www_form(form_data)
    )
    JSON.parse(response.body)
  end

  # Charge and Refund
  def charge_purchase(purchase_id)
    request(:post, "/purchases/#{purchase_id}/charge/")
  end

  def refund_purchase(purchase_id, amount_cents: nil)
    payload = amount_cents ? { amount: amount_cents } : {}
    request(:post, "/purchases/#{purchase_id}/refund/", payload)
  end

  private

  def request(method, endpoint, payload = nil, use_s2s: false)
    api_key = use_s2s ? @s2s_api_key : @standard_api_key
    url = "#{@base_url}#{endpoint}"

    options = {
      headers: {
        'Authorization' => "Bearer #{api_key}",
        'Content-Type' => 'application/json',
        'Accept' => 'application/json'
      }
    }
    options[:body] = payload.to_json if payload

    response = HTTParty.send(method, url, options)
    JSON.parse(response.body)
  end
end

Java

JAVA

/**
 * Precium S2S Integration - Java
 * Requires: java.net.http (Java 11+), org.json
 */

package com.precium.s2s;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.HashMap;
import org.json.JSONObject;

public class PreciumS2SClient {

    private static final String BASE_URL = "https://gate.reviopay.com/api/v1";

    private final String standardApiKey;
    private final String s2sApiKey;
    private final String brandId;
    private final HttpClient httpClient;

    public PreciumS2SClient(String standardApiKey, String s2sApiKey, String brandId) {
        this.standardApiKey = standardApiKey;
        this.s2sApiKey = s2sApiKey;
        this.brandId = brandId;
        this.httpClient = HttpClient.newHttpClient();
    }

    // =========================================================================
    // Client Management
    // =========================================================================

    public JSONObject createClient(String email) throws Exception {
        JSONObject payload = new JSONObject();
        payload.put("email", email);
        return request("POST", "/clients/", payload, false);
    }

    // =========================================================================
    // Purchase Management
    // =========================================================================

    public JSONObject createPurchase(
            String clientId,
            int amountCents,
            String currency,
            String productName,
            String successRedirect,
            String failureRedirect,
            boolean forceRecurring
    ) throws Exception {
        JSONObject product = new JSONObject();
        product.put("name", productName);
        product.put("price", amountCents);

        JSONObject payload = new JSONObject();
        payload.put("client_id", clientId);
        payload.put("brand_id", brandId);
        payload.put("currency", currency);
        payload.put("products", new org.json.JSONArray().put(product));
        payload.put("success_redirect", successRedirect);
        payload.put("failure_redirect", failureRedirect);

        if (forceRecurring) {
            payload.put("force_recurring", true);
        }

        return request("POST", "/purchases/", payload, false);
    }

    // =========================================================================
    // Card Data Submission
    // =========================================================================

    public JSONObject submitCardData(
            String directPostUrl,
            Map<String, String> cardData,
            Map<String, Object> fingerprint,
            boolean rememberCard
    ) throws Exception {
        Map<String, String> formData = new HashMap<>();
        formData.put("cardholder_name", cardData.get("cardholderName"));
        formData.put("card_number", cardData.get("cardNumber"));
        formData.put("expires", cardData.get("expires"));
        formData.put("cvc", cardData.get("cvc"));
        formData.put("remember_card", String.valueOf(rememberCard));
        formData.put("remote_ip", cardData.get("remoteIp"));
        formData.put("user_agent", (String) fingerprint.get("userAgent"));
        formData.put("accept_header", (String) fingerprint.get("acceptHeader"));
        formData.put("language", (String) fingerprint.get("language"));
        formData.put("java_enabled", String.valueOf(fingerprint.get("javaEnabled")));
        formData.put("javascript_enabled", String.valueOf(fingerprint.get("javascriptEnabled")));
        formData.put("color_depth", String.valueOf(fingerprint.get("colorDepth")));
        formData.put("utc_offset", String.valueOf(fingerprint.get("utcOffset")));
        formData.put("screen_width", String.valueOf(fingerprint.get("screenWidth")));
        formData.put("screen_height", String.valueOf(fingerprint.get("screenHeight")));

        String formBody = buildFormData(formData);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(directPostUrl))
            .header("Authorization", "Bearer " + s2sApiKey)
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString(formBody))
            .build();

        HttpResponse<String> response = httpClient.send(request,
            HttpResponse.BodyHandlers.ofString());

        return new JSONObject(response.body());
    }

    // =========================================================================
    // Charge and Refund
    // =========================================================================

    public JSONObject chargePurchase(String purchaseId) throws Exception {
        return request("POST", "/purchases/" + purchaseId + "/charge/", null, false);
    }

    public JSONObject refundPurchase(String purchaseId, Integer amountCents) throws Exception {
        JSONObject payload = new JSONObject();
        if (amountCents != null) {
            payload.put("amount", amountCents);
        }
        return request("POST", "/purchases/" + purchaseId + "/refund/", payload, false);
    }

    // =========================================================================
    // Private Helpers
    // =========================================================================

    private JSONObject request(String method, String endpoint, JSONObject payload, boolean useS2S)
            throws Exception {
        String apiKey = useS2S ? s2sApiKey : standardApiKey;
        String url = BASE_URL + endpoint;

        HttpRequest.Builder builder = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .header("Accept", "application/json");

        if (method.equals("POST")) {
            String body = payload != null ? payload.toString() : "";
            builder.POST(HttpRequest.BodyPublishers.ofString(body));
        } else {
            builder.GET();
        }

        HttpResponse<String> response = httpClient.send(builder.build(),
            HttpResponse.BodyHandlers.ofString());

        return new JSONObject(response.body());
    }

    private String buildFormData(Map<String, String> data) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : data.entrySet()) {
            if (sb.length() > 0) sb.append("&");
            sb.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
            sb.append("=");
            sb.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
        }
        return sb.toString();
    }
}

C#

CSHARP

/**
 * Precium S2S Integration - C#
 * Requires: .NET 6+
 */

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Precium.S2S
{
    public class PreciumS2SClient
    {
        private const string BaseUrl = "https://gate.reviopay.com/api/v1";

        private readonly string _standardApiKey;
        private readonly string _s2sApiKey;
        private readonly string _brandId;
        private readonly HttpClient _httpClient;

        public PreciumS2SClient(string standardApiKey, string s2sApiKey, string brandId)
        {
            _standardApiKey = standardApiKey;
            _s2sApiKey = s2sApiKey;
            _brandId = brandId;
            _httpClient = new HttpClient();
        }

        // Client Management
        public async Task<JsonDocument> CreateClientAsync(string email, string? phone = null)
        {
            var payload = new Dictionary<string, object> { { "email", email } };
            if (phone != null) payload["phone"] = phone;
            return await RequestAsync("POST", "/clients/", payload);
        }

        // Purchase Management
        public async Task<JsonDocument> CreatePurchaseAsync(
            string clientId,
            int amountCents,
            string currency,
            string productName,
            string successRedirect,
            string failureRedirect,
            bool forceRecurring = false)
        {
            var payload = new Dictionary<string, object>
            {
                { "client_id", clientId },
                { "brand_id", _brandId },
                { "currency", currency },
                { "products", new[] { new { name = productName, price = amountCents } } },
                { "success_redirect", successRedirect },
                { "failure_redirect", failureRedirect }
            };

            if (forceRecurring) payload["force_recurring"] = true;

            return await RequestAsync("POST", "/purchases/", payload);
        }

        // Card Data Submission
        public async Task<JsonDocument> SubmitCardDataAsync(
            string directPostUrl,
            CardData card,
            BrowserFingerprint fingerprint,
            bool rememberCard = false)
        {
            var formData = new Dictionary<string, string>
            {
                { "cardholder_name", card.CardholderName },
                { "card_number", card.CardNumber },
                { "expires", card.Expires },
                { "cvc", card.Cvc },
                { "remember_card", rememberCard.ToString().ToLower() },
                { "remote_ip", card.RemoteIp },
                { "user_agent", fingerprint.UserAgent },
                { "accept_header", fingerprint.AcceptHeader },
                { "language", fingerprint.Language },
                { "java_enabled", fingerprint.JavaEnabled.ToString().ToLower() },
                { "javascript_enabled", fingerprint.JavascriptEnabled.ToString().ToLower() },
                { "color_depth", fingerprint.ColorDepth.ToString() },
                { "utc_offset", fingerprint.UtcOffset.ToString() },
                { "screen_width", fingerprint.ScreenWidth.ToString() },
                { "screen_height", fingerprint.ScreenHeight.ToString() }
            };

            var request = new HttpRequestMessage(HttpMethod.Post, directPostUrl)
            {
                Content = new FormUrlEncodedContent(formData)
            };
            request.Headers.Add("Authorization", $"Bearer {_s2sApiKey}");

            var response = await _httpClient.SendAsync(request);
            var content = await response.Content.ReadAsStringAsync();
            return JsonDocument.Parse(content);
        }

        // Charge and Refund
        public async Task<JsonDocument> ChargePurchaseAsync(string purchaseId)
        {
            return await RequestAsync("POST", $"/purchases/{purchaseId}/charge/", null);
        }

        public async Task<JsonDocument> RefundPurchaseAsync(string purchaseId, int? amountCents = null)
        {
            var payload = amountCents.HasValue
                ? new Dictionary<string, object> { { "amount", amountCents.Value } }
                : null;
            return await RequestAsync("POST", $"/purchases/{purchaseId}/refund/", payload);
        }

        private async Task<JsonDocument> RequestAsync(
            string method,
            string endpoint,
            Dictionary<string, object>? payload,
            bool useS2S = false)
        {
            var apiKey = useS2S ? _s2sApiKey : _standardApiKey;
            var url = $"{BaseUrl}{endpoint}";

            var request = new HttpRequestMessage(
                method == "POST" ? HttpMethod.Post : HttpMethod.Get,
                url);
            request.Headers.Add("Authorization", $"Bearer {apiKey}");
            request.Headers.Add("Accept", "application/json");

            if (payload != null)
            {
                var json = JsonSerializer.Serialize(payload);
                request.Content = new StringContent(json, Encoding.UTF8, "application/json");
            }

            var response = await _httpClient.SendAsync(request);
            var content = await response.Content.ReadAsStringAsync();
            return JsonDocument.Parse(content);
        }
    }

    public record CardData(
        string CardholderName,
        string CardNumber,
        string Expires,
        string Cvc,
        string RemoteIp);

    public record BrowserFingerprint(
        string UserAgent,
        string AcceptHeader,
        string Language,
        bool JavaEnabled,
        bool JavascriptEnabled,
        int ColorDepth,
        int UtcOffset,
        int ScreenWidth,
        int ScreenHeight);
}

Go

GO

// Precium S2S Integration - Go
// Requires: Go 1.18+

package precium

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

const baseURL = "https://gate.reviopay.com/api/v1"

type Client struct {
	StandardAPIKey string
	S2SAPIKey      string
	BrandID        string
	HTTPClient     *http.Client
}

type CardData struct {
	CardholderName string
	CardNumber     string
	Expires        string
	CVC            string
	RemoteIP       string
}

type BrowserFingerprint struct {
	UserAgent         string
	AcceptHeader      string
	Language          string
	JavaEnabled       bool
	JavascriptEnabled bool
	ColorDepth        int
	UTCOffset         int
	ScreenWidth       int
	ScreenHeight      int
}

func NewClient(standardAPIKey, s2sAPIKey, brandID string) *Client {
	return &Client{
		StandardAPIKey: standardAPIKey,
		S2SAPIKey:      s2sAPIKey,
		BrandID:        brandID,
		HTTPClient:     &http.Client{},
	}
}

// CreateClient creates a new client record
func (c *Client) CreateClient(email string, phone *string) (map[string]interface{}, error) {
	payload := map[string]interface{}{"email": email}
	if phone != nil {
		payload["phone"] = *phone
	}
	return c.request("POST", "/clients/", payload, false)
}

// CreatePurchase creates a new purchase
func (c *Client) CreatePurchase(
	clientID string,
	amountCents int,
	currency string,
	productName string,
	successRedirect string,
	failureRedirect string,
	forceRecurring bool,
) (map[string]interface{}, error) {
	payload := map[string]interface{}{
		"client_id":        clientID,
		"brand_id":         c.BrandID,
		"currency":         currency,
		"products":         []map[string]interface{}{{"name": productName, "price": amountCents}},
		"success_redirect": successRedirect,
		"failure_redirect": failureRedirect,
	}
	if forceRecurring {
		payload["force_recurring"] = true
	}
	return c.request("POST", "/purchases/", payload, false)
}

// SubmitCardData submits card data to the direct post URL
func (c *Client) SubmitCardData(
	directPostURL string,
	card CardData,
	fingerprint BrowserFingerprint,
	rememberCard bool,
) (map[string]interface{}, error) {
	form := url.Values{}
	form.Set("cardholder_name", card.CardholderName)
	form.Set("card_number", card.CardNumber)
	form.Set("expires", card.Expires)
	form.Set("cvc", card.CVC)
	form.Set("remember_card", strconv.FormatBool(rememberCard))
	form.Set("remote_ip", card.RemoteIP)
	form.Set("user_agent", fingerprint.UserAgent)
	form.Set("accept_header", fingerprint.AcceptHeader)
	form.Set("language", fingerprint.Language)
	form.Set("java_enabled", strconv.FormatBool(fingerprint.JavaEnabled))
	form.Set("javascript_enabled", strconv.FormatBool(fingerprint.JavascriptEnabled))
	form.Set("color_depth", strconv.Itoa(fingerprint.ColorDepth))
	form.Set("utc_offset", strconv.Itoa(fingerprint.UTCOffset))
	form.Set("screen_width", strconv.Itoa(fingerprint.ScreenWidth))
	form.Set("screen_height", strconv.Itoa(fingerprint.ScreenHeight))

	req, err := http.NewRequest("POST", directPostURL, strings.NewReader(form.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+c.S2SAPIKey)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result, nil
}

// ChargePurchase charges a purchase
func (c *Client) ChargePurchase(purchaseID string) (map[string]interface{}, error) {
	return c.request("POST", fmt.Sprintf("/purchases/%s/charge/", purchaseID), nil, false)
}

// RefundPurchase refunds a purchase
func (c *Client) RefundPurchase(purchaseID string, amountCents *int) (map[string]interface{}, error) {
	var payload map[string]interface{}
	if amountCents != nil {
		payload = map[string]interface{}{"amount": *amountCents}
	}
	return c.request("POST", fmt.Sprintf("/purchases/%s/refund/", purchaseID), payload, false)
}

func (c *Client) request(method, endpoint string, payload map[string]interface{}, useS2S bool) (map[string]interface{}, error) {
	apiKey := c.StandardAPIKey
	if useS2S {
		apiKey = c.S2SAPIKey
	}

	url := baseURL + endpoint

	var body io.Reader
	if payload != nil {
		jsonBytes, err := json.Marshal(payload)
		if err != nil {
			return nil, err
		}
		body = bytes.NewReader(jsonBytes)
	}

	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, err
	}
	return result, nil
}

Browser Fingerprint Collection (JavaScript)

Use this code on your frontend to collect browser fingerprint data:

JSX

function collectBrowserFingerprint() {
  return {
    userAgent: navigator.userAgent,
    acceptHeader: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    language: navigator.language || navigator.userLanguage,
    javaEnabled: navigator.javaEnabled ? navigator.javaEnabled() : false,
    javascriptEnabled: true,
    colorDepth: screen.colorDepth,
    utcOffset: new Date().getTimezoneOffset(),
    screenWidth: screen.width,
    screenHeight: screen.height
  };
}

// Usage
const fingerprint = collectBrowserFingerprint();
// Send fingerprint to your backend with the payment request

3DS Challenge Form (HTML/JavaScript)

HTML

<!DOCTYPE html>
<html>
<head>
    <title>Bank Verification</title>
</head>
<body>
    <div id='loading'>Redirecting to your bank for verification...</div>

    <form id='3ds-form' method='POST' style='display: none;'>
        <input type='hidden' name='MD' id='md' />
        <input type='hidden' name='PaReq' id='pareq' />
        <noscript>
            <p>JavaScript is required for bank verification.</p>
            <button type='submit'>Continue to Bank</button>
        </noscript>
    </form>

    <script>
        // These values come from your backend after card submission
        function initiate3DS(threeDSData) {
            const form = document.getElementById('3ds-form');
            form.action = threeDSData['3ds_url'];
            document.getElementById('md').value = threeDSData['MD'];
            document.getElementById('pareq').value = threeDSData['PaReq'];

            // Add TermUrl for the callback
            const termUrl = document.createElement('input');
            termUrl.type = 'hidden';
            termUrl.name = 'TermUrl';
            termUrl.value = threeDSData['callback_url'];
            form.appendChild(termUrl);

            form.submit();
        }

        // Call with data from your backend
        // initiate3DS(threeDSResponseFromBackend);
    </script>
</body>
</html>