PlexySDK DOCS

Advanced Flow

Build custom payment experiences with full control over the checkout process

Advanced Flow

The Advanced flow gives you complete control over the payment experience. You build your own payment form, collect payment details, and handle the full payment lifecycle through three API calls.

The Advanced flow requires more development effort but provides maximum flexibility. If you're just getting started, consider the Sessions flow first.

How Advanced Flow Works

Call /v2/paymentMethods to get payment methods available for your shopper.

Render payment inputs in your own UI and collect the required payment data.

Submit the payment details to /v2/payments to initiate the payment.

If required, handle 3D Secure or redirects by calling /v2/payments/details.

Step 1: Get Payment Methods

First, request available payment methods for the transaction. The response depends on your account configuration, the shopper's country, and the payment amount.

import { Plexy } from '@plexy/plexy-web';

const plexy = new Plexy({
  apiKey: process.env.PLEXY_API_KEY,
});

async function getPaymentMethods(amount, currency, countryCode) {
  const response = await plexy.paymentMethods.list({
    amount: {
      value: amount,
      currency: currency
    },
    countryCode: countryCode,
    shopperLocale: 'en-US',
    channel: 'Web'
  });

  return response.paymentMethods;
}

// Example usage
const paymentMethods = await getPaymentMethods(10000, 'KZT', 'KZ');
console.log(paymentMethods);
// [
//   { type: 'scheme', name: 'Credit Card', brands: ['visa', 'mc', 'amex'] },
//   { type: 'applepay', name: 'Apple Pay', configuration: {...} }
// ]
import os
from plexy import Plexy

plexy = Plexy(api_key=os.environ['PLEXY_API_KEY'])

def get_payment_methods(amount: int, currency: str, country_code: str) -> list:
    """Get available payment methods for the transaction."""
    response = plexy.payment_methods.list(
        amount={
            'value': amount,
            'currency': currency
        },
        country_code=country_code,
        shopper_locale='en-US',
        channel='Web'
    )

    return response.payment_methods

# Example usage
payment_methods = get_payment_methods(10000, 'KZT', 'KZ')
print(payment_methods)
# [
#   {'type': 'scheme', 'name': 'Credit Card', 'brands': ['visa', 'mc', 'amex']},
#   {'type': 'applepay', 'name': 'Apple Pay', 'configuration': {...}}
# ]
curl -X POST https://api.plexypay.com/v2/paymentMethods \
  -H "x-api-key: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": {
      "value": 10000,
      "currency": "KZT"
    },
    "countryCode": "KZ",
    "shopperLocale": "en-US",
    "channel": "Web"
  }'

Response:

{
  "paymentMethods": [
    {
      "type": "scheme",
      "name": "Credit Card",
      "brands": ["visa", "mc", "amex"]
    },
    {
      "type": "applepay",
      "name": "Apple Pay",
      "configuration": {
        "merchantId": "merchant.com.plexy.example",
        "merchantName": "Your Store"
      }
    }
  ]
}

Step 2: Collect Payment Details

Build your payment form to collect the required data. For card payments, you must use client-side encryption to protect sensitive card data.

Card Encryption (Client-Side)

Never send raw card numbers to your server. Always encrypt card data client-side using the Plexy encryption library.

import { PlexyEncryption } from '@plexy/plexy-web/browser';

// Initialize with your client encryption key (from Plexy Dashboard)
const encryption = new PlexyEncryption({
  publicKey: 'YOUR_CLIENT_ENCRYPTION_KEY',
});

// Collect card data from your form
const cardData = {
  cardNumber: '4111111111111111',
  expiryMonth: '03',
  expiryYear: '2030',
  securityCode: '737',
  holderName: 'John Smith',
};

// Encrypt the sensitive fields
const encryptedCard = await encryption.encrypt({
  encryptedCardNumber: cardData.cardNumber,
  encryptedExpiryMonth: cardData.expiryMonth,
  encryptedExpiryYear: cardData.expiryYear,
  encryptedSecurityCode: cardData.securityCode,
});

// Send encrypted data to your server
const response = await fetch('/api/pay', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    paymentMethod: {
      type: 'scheme',
      ...encryptedCard,
      holderName: cardData.holderName,
    },
    browserInfo: encryption.getBrowserInfo(),
  }),
});

For easier PCI compliance, use Plexy's secure input fields:

import { PlexySecureFields } from '@plexy/plexy-web/browser';

const secureFields = new PlexySecureFields({
  clientKey: 'YOUR_CLIENT_KEY',
  locale: 'en-US',
});

// Create secure input fields
const cardNumber = secureFields.create('cardNumber', {
  placeholder: 'Card number',
  style: {
    base: { fontSize: '16px', color: '#333' },
    invalid: { color: '#dc3545' },
  },
});

const expiryDate = secureFields.create('expiryDate', {
  placeholder: 'MM/YY',
});

const securityCode = secureFields.create('securityCode', {
  placeholder: 'CVV',
});

// Mount to your form
cardNumber.mount('#card-number');
expiryDate.mount('#expiry-date');
securityCode.mount('#security-code');

// When the form is submitted
async function handleSubmit() {
  const { encryptedData, error } = await secureFields.encrypt();

  if (error) {
    console.error('Encryption error:', error);
    return;
  }

  // Send encrypted data to your server
  const response = await fetch('/api/pay', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      encryptedPaymentMethod: encryptedData,
      browserInfo: secureFields.getBrowserInfo(),
    }),
  });
}

Step 3: Make a Payment

Submit the payment to the /v2/payments endpoint from your server.

import { Plexy } from '@plexy/plexy-web';

const plexy = new Plexy({
  apiKey: process.env.PLEXY_API_KEY,
});

async function makePayment(paymentData) {
  const payment = await plexy.payments.create({
    amount: {
      value: paymentData.amount,
      currency: paymentData.currency
    },
    reference: paymentData.orderId,
    paymentMethod: {
      type: 'scheme',
      encryptedCardNumber: paymentData.encryptedCardNumber,
      encryptedExpiryMonth: paymentData.encryptedExpiryMonth,
      encryptedExpiryYear: paymentData.encryptedExpiryYear,
      encryptedSecurityCode: paymentData.encryptedSecurityCode,
      holderName: paymentData.holderName
    },
    shopperReference: paymentData.customerId,
    shopperEmail: paymentData.email,
    shopperIP: paymentData.ipAddress,
    browserInfo: paymentData.browserInfo,
    returnUrl: `https://your-site.com/checkout/complete?orderId=${paymentData.orderId}`,
    metadata: {
      orderId: paymentData.orderId
    }
  });

  return handlePaymentResponse(payment);
}

function handlePaymentResponse(payment) {
  switch (payment.resultCode) {
    case 'Authorised':
      return {
        status: 'success',
        paymentId: payment.pspReference
      };

    case 'RedirectShopper':
      // Customer needs to complete redirect (e.g., bank authentication)
      return {
        status: 'redirect',
        action: payment.action
      };

    case 'IdentifyShopper':
    case 'ChallengeShopper':
      // 3D Secure authentication required
      return {
        status: '3ds',
        action: payment.action
      };

    case 'Pending':
      // Payment is processing (common for bank transfers)
      return {
        status: 'pending',
        paymentId: payment.pspReference
      };

    case 'Refused':
      return {
        status: 'refused',
        reason: payment.refusalReason,
        reasonCode: payment.refusalReasonCode
      };

    default:
      return {
        status: 'error',
        resultCode: payment.resultCode
      };
  }
}

// Express route example
app.post('/api/pay', async (req, res) => {
  try {
    const result = await makePayment({
      ...req.body,
      ipAddress: req.ip
    });
    res.json(result);
  } catch (error) {
    console.error('Payment error:', error);
    res.status(500).json({ status: 'error', message: 'Payment failed' });
  }
});
import os
from plexy import Plexy
from flask import Flask, request, jsonify

plexy = Plexy(api_key=os.environ['PLEXY_API_KEY'])

def make_payment(payment_data: dict) -> dict:
    """Process a payment with the provided data."""
    payment = plexy.payments.create(
        amount={
            'value': payment_data['amount'],
            'currency': payment_data['currency']
        },
        reference=payment_data['order_id'],
        payment_method={
            'type': 'scheme',
            'encryptedCardNumber': payment_data['encrypted_card_number'],
            'encryptedExpiryMonth': payment_data['encrypted_expiry_month'],
            'encryptedExpiryYear': payment_data['encrypted_expiry_year'],
            'encryptedSecurityCode': payment_data['encrypted_security_code'],
            'holderName': payment_data['holder_name']
        },
        shopper_reference=payment_data.get('customer_id'),
        shopper_email=payment_data.get('email'),
        shopper_ip=payment_data.get('ip_address'),
        browser_info=payment_data.get('browser_info'),
        return_url=f"https://your-site.com/checkout/complete?orderId={payment_data['order_id']}",
        metadata={
            'orderId': payment_data['order_id']
        }
    )

    return handle_payment_response(payment)


def handle_payment_response(payment) -> dict:
    """Parse the payment response and return appropriate action."""
    result_code = payment.result_code

    if result_code == 'Authorised':
        return {
            'status': 'success',
            'paymentId': payment.psp_reference
        }

    if result_code == 'RedirectShopper':
        return {
            'status': 'redirect',
            'action': payment.action
        }

    if result_code in ['IdentifyShopper', 'ChallengeShopper']:
        return {
            'status': '3ds',
            'action': payment.action
        }

    if result_code == 'Pending':
        return {
            'status': 'pending',
            'paymentId': payment.psp_reference
        }

    if result_code == 'Refused':
        return {
            'status': 'refused',
            'reason': payment.refusal_reason,
            'reasonCode': payment.refusal_reason_code
        }

    return {
        'status': 'error',
        'resultCode': result_code
    }


# Flask route example
app = Flask(__name__)

@app.route('/api/pay', methods=['POST'])
def pay():
    try:
        data = request.json
        data['ip_address'] = request.remote_addr
        result = make_payment(data)
        return jsonify(result)
    except Exception as e:
        print(f'Payment error: {e}')
        return jsonify({'status': 'error', 'message': 'Payment failed'}), 500
curl -X POST https://api.plexypay.com/v2/payments \
  -H "x-api-key: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": {
      "value": 10000,
      "currency": "KZT"
    },
    "reference": "order_12345",
    "paymentMethod": {
      "type": "scheme",
      "encryptedCardNumber": "adyenjs_0_1_25...",
      "encryptedExpiryMonth": "adyenjs_0_1_25...",
      "encryptedExpiryYear": "adyenjs_0_1_25...",
      "encryptedSecurityCode": "adyenjs_0_1_25...",
      "holderName": "John Smith"
    },
    "shopperReference": "customer_12345",
    "shopperEmail": "customer@example.com",
    "shopperIP": "192.168.1.1",
    "browserInfo": {
      "acceptHeader": "text/html",
      "colorDepth": 24,
      "language": "en-US",
      "javaEnabled": false,
      "screenHeight": 1080,
      "screenWidth": 1920,
      "userAgent": "Mozilla/5.0...",
      "timeZoneOffset": -180
    },
    "returnUrl": "https://your-site.com/checkout/complete?orderId=order_12345"
  }'

Response (Successful):

{
  "resultCode": "Authorised",
  "pspReference": "8835511210681324",
  "merchantReference": "order_12345"
}

Response (3D Secure Required):

{
  "resultCode": "IdentifyShopper",
  "action": {
    "type": "threeDS2",
    "subtype": "fingerprint",
    "token": "eyJ0aHJlZURTTWV0aG9kTm90...",
    "paymentMethodType": "scheme"
  }
}

Step 4: Handle Additional Actions

If the payment requires additional action (3D Secure, redirect), handle it client-side and submit the result.

Handling 3D Secure

import { Plexy3DS } from '@plexy/plexy-web/browser';

async function handle3DSAction(action) {
  const threeDS = new Plexy3DS({
    clientKey: 'YOUR_CLIENT_KEY',
    environment: 'live',
  });

  // Create and mount the 3DS component
  const threeDS2Component = threeDS.create('threeDS2', {
    challengeWindowSize: '05', // Full screen
    onComplete: async (result) => {
      // Submit the authentication result to your server
      await submitPaymentDetails(result.data);
    },
    onError: (error) => {
      console.error('3DS error:', error);
      showErrorMessage('Authentication failed');
    },
  });

  // Handle the action from the payment response
  threeDS2Component.handleAction(action);
}

async function submitPaymentDetails(details) {
  const response = await fetch('/api/payments/details', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ details }),
  });

  const result = await response.json();
  handleFinalResult(result);
}

Handling Redirects

For payment methods that require a redirect (e.g., bank transfers):

function handleRedirectAction(action) {
  if (action.type === 'redirect') {
    // Store payment data for when customer returns
    sessionStorage.setItem(
      'pendingPayment',
      JSON.stringify({
        orderId: currentOrderId,
        timestamp: Date.now(),
      }),
    );

    // Redirect to payment provider
    window.location.href = action.url;
  }
}

// On your return URL page
async function handleReturn() {
  const urlParams = new URLSearchParams(window.location.search);
  const redirectResult = urlParams.get('redirectResult');

  if (redirectResult) {
    const response = await fetch('/api/payments/details', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        details: { redirectResult },
      }),
    });

    const result = await response.json();
    handleFinalResult(result);
  }
}

Server-Side: Submit Additional Details

async function submitPaymentDetails(details) {
  const response = await plexy.payments.details({
    details: details
  });

  return handlePaymentResponse(response);
}

// Express route
app.post('/api/payments/details', async (req, res) => {
  try {
    const result = await submitPaymentDetails(req.body.details);
    res.json(result);
  } catch (error) {
    console.error('Payment details error:', error);
    res.status(500).json({ status: 'error' });
  }
});
def submit_payment_details(details: dict) -> dict:
    """Submit additional payment details (3DS, redirect result)."""
    response = plexy.payments.details(details=details)
    return handle_payment_response(response)

# Flask route
@app.route('/api/payments/details', methods=['POST'])
def payment_details():
    try:
        data = request.json
        result = submit_payment_details(data['details'])
        return jsonify(result)
    except Exception as e:
        print(f'Payment details error: {e}')
        return jsonify({'status': 'error'}), 500
curl -X POST https://api.plexypay.com/v2/payments/details \
  -H "x-api-key: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "details": {
      "redirectResult": "eyJ0cmFuc1N0YXR1cyI6IlkiL..."
    }
  }'

Response:

{
  "resultCode": "Authorised",
  "pspReference": "8835511210681324",
  "merchantReference": "order_12345"
}

Complete Integration Example

Here's a complete example combining all the steps:

// server.js - Express backend
import express from 'express';
import { Plexy } from '@plexy/plexy-web';

const app = express();
app.use(express.json());

const plexy = new Plexy({
  apiKey: process.env.PLEXY_API_KEY,
});

// Step 1: Get payment methods
app.post('/api/payment-methods', async (req, res) => {
  try {
    const { amount, currency, countryCode } = req.body;

    const response = await plexy.paymentMethods.list({
      amount: { value: amount, currency },
      countryCode,
      channel: 'Web'
    });

    res.json(response);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Step 3: Make payment
app.post('/api/payments', async (req, res) => {
  try {
    const payment = await plexy.payments.create({
      amount: req.body.amount,
      reference: req.body.reference,
      paymentMethod: req.body.paymentMethod,
      shopperReference: req.body.shopperReference,
      shopperEmail: req.body.shopperEmail,
      shopperIP: req.ip,
      browserInfo: req.body.browserInfo,
      returnUrl: req.body.returnUrl
    });

    res.json(payment);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Step 4: Handle additional details
app.post('/api/payments/details', async (req, res) => {
  try {
    const response = await plexy.payments.details({
      details: req.body.details
    });

    res.json(response);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));
# server.py - Flask backend
import os
from flask import Flask, request, jsonify
from plexy import Plexy

app = Flask(__name__)
plexy = Plexy(api_key=os.environ['PLEXY_API_KEY'])

# Step 1: Get payment methods
@app.route('/api/payment-methods', methods=['POST'])
def get_payment_methods():
    try:
        data = request.json
        response = plexy.payment_methods.list(
            amount={'value': data['amount'], 'currency': data['currency']},
            country_code=data['countryCode'],
            channel='Web'
        )
        return jsonify(response.to_dict())
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# Step 3: Make payment
@app.route('/api/payments', methods=['POST'])
def make_payment():
    try:
        data = request.json
        payment = plexy.payments.create(
            amount=data['amount'],
            reference=data['reference'],
            payment_method=data['paymentMethod'],
            shopper_reference=data.get('shopperReference'),
            shopper_email=data.get('shopperEmail'),
            shopper_ip=request.remote_addr,
            browser_info=data.get('browserInfo'),
            return_url=data['returnUrl']
        )
        return jsonify(payment.to_dict())
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# Step 4: Handle additional details
@app.route('/api/payments/details', methods=['POST'])
def payment_details():
    try:
        data = request.json
        response = plexy.payments.details(details=data['details'])
        return jsonify(response.to_dict())
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(port=3000)

Alternative Payment Methods

Different payment methods require different data. Here are examples for common methods:

Apple Pay

const payment = await plexy.payments.create({
  amount: { value: 10000, currency: 'KZT' },
  reference: 'order_12345',
  paymentMethod: {
    type: 'applepay',
    applePayToken: applePayPaymentData, // Token from Apple Pay JS API
  },
  returnUrl: 'https://your-site.com/checkout/complete',
});

Google Pay

const payment = await plexy.payments.create({
  amount: { value: 10000, currency: 'KZT' },
  reference: 'order_12345',
  paymentMethod: {
    type: 'googlepay',
    googlePayToken: googlePayPaymentData, // Token from Google Pay API
  },
  returnUrl: 'https://your-site.com/checkout/complete',
});

Error Handling

Implement robust error handling for production:

import { PlexyError, PlexyValidationError, PlexyAuthError } from '@plexy/plexy-web';

try {
  const payment = await plexy.payments.create({ ... });
} catch (error) {
  if (error instanceof PlexyValidationError) {
    // Invalid request data
    console.error('Validation error:', error.errors);
    return { status: 'validation_error', errors: error.errors };
  }

  if (error instanceof PlexyAuthError) {
    // API key issue
    console.error('Authentication error');
    return { status: 'auth_error' };
  }

  if (error instanceof PlexyError) {
    // General API error
    console.error('API error:', error.code, error.message);
    return { status: 'api_error', code: error.code };
  }

  // Unknown error
  console.error('Unknown error:', error);
  return { status: 'error' };
}

Next Steps

On this page