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(),
}),
});Secure Input Fields (Recommended)
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'}), 500curl -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'}), 500curl -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
- Understand result codes to handle all payment outcomes
- Implement 3D Secure for card authentication
- Set up webhooks for asynchronous notifications
- Configure checkout settings in the Dashboard