Skip to main content

Callbacks

Handle payment events and results using callback handlers to create a seamless user experience and integrate with your application logic.

Overview

Altruon JS uses an event-based callback system for handling payment lifecycle events. You register callback handlers using the .on() method, which can be chained during initialization.

altruon
.init("pk_sandbox_your_key", "your-domain")
.on("onSuccess", (data) => {
// Handle successful payment
})
.on("onFailure", (error) => {
// Handle payment failure
})
.on("onDataRequired", (data) => {
// Handle missing required data
})
.on("onActionRequired", (data) => {
// Handle required actions (3DS, QR codes, etc.)
});

Callback Events

onSuccess

Triggered whenever a payment and subscription finalizes successfully.

When it's called:

  • Payment has been processed successfully
  • Subscription has been created/updated
  • All required validations have passed

Usage:

altruon.on("onSuccess", (data) => {
console.log('Payment successful!');
console.log('Transaction ID:', data.transactionId);

// Send transaction ID to your backend for detailed information
fetch('/api/payment-success', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionId: data.transactionId })
}).then(() => {
// Redirect to success page
window.location.href = `/success?txn=${data.transactionId}`;
});
});

Response Data Structure:

{
transactionId: string; // Unique transaction identifier
message: string; // Success message (e.g., "Payment completed successfully")
}

Example Response:

{
"transactionId": "b9b6a88e-82a7-43d6-acd3-18421d78f594",
"message": "Payment completed successfully"
}

Note: The onSuccess callback only provides the transactionId. To get complete details about the transaction, subscription, customer, and invoice, you must call the Transaction Details API from your backend.

Implementation Example:

altruon.on("onSuccess", (data) => {
// Track successful conversion
analytics.track('Payment Success', {
transactionId: data.transactionId
});

// Show success message
showNotification('success', data.message);

// Send to backend for processing
notifyBackend(data.transactionId);

// Redirect after short delay
setTimeout(() => {
window.location.href = `/success?txn=${data.transactionId}`;
}, 1500);
});

async function notifyBackend(transactionId) {
try {
const response = await fetch('/api/webhooks/payment-success', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionId })
});

if (!response.ok) {
console.error('Failed to notify backend');
}
} catch (error) {
console.error('Error notifying backend:', error);
}
}

onFailure

Triggered whenever a payment and subscription fails.

When it's called:

  • Payment was declined by the payment gateway
  • Card validation failed
  • Insufficient funds
  • Network or processing errors
  • Any other payment-related failures

Usage:

altruon.on("onFailure", (error) => {
console.error('Payment failed:', error);

// Display error message to user
showErrorMessage(error.message);

// Log for debugging
logError(error);
});

Response Data Structure:

{
status: string; // Error status code (e.g., "failed", "declined")
message: string; // Human-readable error message
}

Example Response:

{
"status": "declined",
"message": "Your card was declined. Please try a different payment method."
}

Common Status Values:

StatusDescriptionSuggested Action
declinedCard was declined by issuerAsk user to try different card
failedGeneral payment failureShow error message and retry option
invalid_cardCard details are invalidAsk user to verify card information
insufficient_fundsNot enough fundsSuggest different payment method
expired_cardCard has expiredAsk for updated card details
processing_errorPayment processing errorRetry payment or contact support

Implementation Example:

altruon.on("onFailure", (error) => {
// Track failure for analytics
analytics.track('Payment Failed', {
status: error.status,
message: error.message
});

// Show user-friendly error
const errorDiv = document.getElementById('error-message');
errorDiv.innerHTML = `
<div class="error-banner">
<strong>Payment Failed</strong>
<p>${error.message}</p>
<button onclick="retryPayment()">Try Again</button>
</div>
`;
errorDiv.style.display = 'block';

// Enable payment button again
document.getElementById('pay-button').disabled = false;
});

onDataRequired

Triggered whenever a payment cannot complete due to lacking required data.

When it's called:

  • Required customer information is missing
  • Additional verification data is needed
  • Billing or shipping address is incomplete
  • Payment method requires extra details

Usage:

altruon.on("onDataRequired", (data) => {
console.warn('Additional data required:', data.requiredFields);

// Show form to collect missing data
showDataCollectionForm(data.requiredFields);

// Display message to user
alert(data.message);
});

Response Data Structure:

{
message: string; // Description of what's needed
details: string; // Additional context or instructions
requiredFields: string; // Comma-separated list of required field names
}

Example Response:

{
"message": "Additional information required to complete payment",
"details": "Please provide the missing billing information",
"requiredFields": "billingAddress.zipCode,phone,document"
}

Common Required Fields:

Field NameDescription
billingAddress.streetStreet address
billingAddress.cityCity name
billingAddress.stateState/province
billingAddress.zipCodePostal/ZIP code
billingAddress.countryCountry code
phoneCustomer phone number
documentTax ID or document number (CPF, CNPJ, etc.)
firstNameCustomer first name
lastNameCustomer last name
emailCustomer email address

Implementation Example:

altruon.on("onDataRequired", (data) => {
// Parse required fields
const fields = data.requiredFields.split(',').map(f => f.trim());

// Show notification
showNotification('warning', data.message);

// Display form for missing fields
const formHtml = fields.map(field => {
const fieldName = field.split('.').pop(); // Get last part (e.g., "zipCode")
return `
<div class="form-field">
<label for="${field}">${formatFieldName(fieldName)}</label>
<input type="text" id="${field}" name="${field}" required />
</div>
`;
}).join('');

document.getElementById('additional-data-form').innerHTML = `
<div class="modal">
<h3>Additional Information Required</h3>
<p>${data.details}</p>
<form id="missing-data-form">
${formHtml}
<button type="submit">Continue</button>
</form>
</div>
`;

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

// Collect data
const formData = new FormData(e.target);

// Update Altruon with missing data
fields.forEach(field => {
const value = formData.get(field);
if (field.startsWith('billingAddress.')) {
// Update address
const addressField = field.split('.')[1];
altruon.setAddresses({
billing: { [addressField]: value }
});
} else if (field === 'phone' || field === 'email') {
altruon.setCustomer({ [field]: value });
}
});

// Retry payment
retryPayment();
});
});

function formatFieldName(field) {
// Convert camelCase to Title Case
return field
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());
}

onActionRequired

Triggered whenever a payment submission results in an extra action required to finalize the payment flow.

When it's called:

  • 3D Secure (3DS) authentication is required
  • Payment needs user redirection to external page
  • QR code payment needs to be displayed
  • Additional verification steps are needed

Usage:

altruon.on("onActionRequired", (data) => {
console.log('Action required:', data.actionType);

if (data.actionType === 'redirect') {
// Redirect user to authentication page
window.location.href = data.data.url;
} else if (data.actionType === 'qrDataDisplay') {
// Display QR code for payment
displayQRCode(data.data);
}
});

Response Data Structure:

{
actionType: string; // Type of action required: "redirect" | "qrDataDisplay"
data: object; // Action-specific data
message: string; // Description of the required action
}

Action Type: redirect

Used when the user needs to be redirected to complete payment (e.g., 3D Secure authentication, bank authorization).

Example Response:

{
"actionType": "redirect",
"message": "Please complete authentication to finalize your payment",
"data": {
"url": "https://secure.bank.com/3ds/authenticate?token=abc123",
"method": "GET",
"returnUrl": "https://yourdomain.com/payment-return"
}
}

Implementation Example:

altruon.on("onActionRequired", (data) => {
if (data.actionType === 'redirect') {
// Show loading message
showModal({
title: 'Authentication Required',
message: data.message,
showSpinner: true
});

// Store current state
sessionStorage.setItem('paymentInProgress', 'true');

// Redirect to authentication page
window.location.href = data.data.url;
}
});

Action Type: qrDataDisplay

Used for QR code-based payments (e.g., PIX in Brazil, UPI in India, QR code payments).

Example Response:

{
"actionType": "qrDataDisplay",
"message": "Scan the QR code with your banking app to complete payment",
"data": {
"qrCode": "00020126580014br.gov.bcb.pix...",
"qrCodeImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"pixKey": "user@example.com",
"amount": "29.90",
"expiresAt": "2024-11-01T18:30:00Z"
}
}

Data Object for QR Code:

FieldTypeDescription
qrCodestringRaw QR code data string (can be used to generate QR)
qrCodeImagestringBase64-encoded PNG image of the QR code
pixKeystringPIX key (for PIX payments)
amountstringPayment amount
expiresAtstringISO 8601 timestamp when QR code expires

Implementation Example:

altruon.on("onActionRequired", (data) => {
if (data.actionType === 'qrDataDisplay') {
// Display QR code modal
const modal = document.createElement('div');
modal.className = 'qr-payment-modal';
modal.innerHTML = `
<div class="modal-content">
<h2>Complete Your Payment</h2>
<p>${data.message}</p>

<div class="qr-code-container">
<img src="${data.data.qrCodeImage}" alt="Payment QR Code" />
</div>

<div class="payment-details">
<p><strong>Amount:</strong> ${formatCurrency(data.data.amount)}</p>
${data.data.pixKey ? `<p><strong>PIX Key:</strong> ${data.data.pixKey}</p>` : ''}
</div>

<div class="qr-code-text">
<p>Or copy the code below:</p>
<div class="code-box">
<code>${data.data.qrCode}</code>
<button onclick="copyToClipboard('${data.data.qrCode}')">
Copy Code
</button>
</div>
</div>

<div class="countdown">
<p>Code expires in: <span id="countdown"></span></p>
</div>

<button onclick="closeQRModal()">Cancel</button>
</div>
`;

document.body.appendChild(modal);

// Start countdown timer
startCountdown(data.data.expiresAt);

// Poll for payment completion
pollPaymentStatus();
}
});

function startCountdown(expiresAt) {
const expiryTime = new Date(expiresAt).getTime();

const countdownInterval = setInterval(() => {
const now = new Date().getTime();
const timeLeft = expiryTime - now;

if (timeLeft <= 0) {
clearInterval(countdownInterval);
document.getElementById('countdown').textContent = 'Expired';
showExpiredMessage();
return;
}

const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);

document.getElementById('countdown').textContent =
`${minutes}:${seconds.toString().padStart(2, '0')}`;
}, 1000);
}

function pollPaymentStatus() {
// Poll backend to check if payment was completed
const pollInterval = setInterval(async () => {
const response = await fetch('/api/check-payment-status');
const data = await response.json();

if (data.status === 'completed') {
clearInterval(pollInterval);
closeQRModal();
showSuccessMessage('Payment completed successfully!');
}
}, 3000); // Check every 3 seconds

// Stop polling after 10 minutes
setTimeout(() => clearInterval(pollInterval), 10 * 60 * 1000);
}

function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showNotification('success', 'Code copied to clipboard!');
});
}

Getting Transaction Details

The onSuccess callback only returns the transactionId. To retrieve complete information about the transaction, subscription, customer, and invoice, call the Transaction Details API from your backend.

Transaction Details Endpoint

Endpoints:

  • Sandbox: https://{your-domain}.api.sandbox.altruon.io/api/v1/transaction/{transactionId}/details
  • Production: https://{your-domain}.api.altruon.io/api/v1/transaction/{transactionId}/details

Authentication:

headers: {
'Content-Type': 'application/json',
'x-secret-key': 'sk_sandbox_test_XXXXXX...'
}

Security Note: This endpoint requires your secret API key and must only be called from your backend. Never expose your secret key in client-side code.

Response Structure

{
transaction: {
id: string; // Altruon transaction ID
idAtGateway: string; // Transaction ID at payment gateway
urlAtGateway: string; // Direct URL to view transaction in gateway dashboard
type: string; // "initialPayment", "recurringPayment", etc.
amount: string; // Amount in cents
currency: string; // Currency code (e.g., "USD", "EUR")
status: string; // "success", "failed", "pending"
gateway: string; // Payment gateway name
errorMessage: string | null; // Error message if failed
};
customer: {
idAtBillingPlatform: string; // Customer ID in billing platform
urlAtBilling: string; // Direct URL to view customer in billing dashboard
billingPlatform: string; // Billing platform name (e.g., "stripe")
};
invoice: {
idAtBillingPlatform: string; // Invoice ID in billing platform
urlAtBilling: string; // Direct URL to view invoice in billing dashboard
amount: string; // Invoice amount in cents
currency: string; // Currency code
billingPlatform: string; // Billing platform name
};
subscription: {
idAtBillingPlatform: string; // Subscription ID in billing platform
urlAtBilling: string; // Direct URL to view subscription in billing dashboard
nextBillingDate: string; // ISO 8601 timestamp of next billing
billingPlatform: string; // Billing platform name
};
}

New Feature: The response now includes direct URLs (urlAtGateway and urlAtBilling) that link to the respective entity pages in your payment gateway and billing provider dashboards. These URLs allow your team to quickly access detailed information in Stripe, Checkout.com, or other provider dashboards for support and reconciliation purposes.

Example Response

{
"transaction": {
"id": "66a22937-a351-417e-81c1-9ac5164be9ab",
"idAtGateway": "pay_wthwuqodnobedlulbexcjfylma",
"urlAtGateway": "https://dashboard.sandbox.checkout.com/payments/all-payments/payment/pay_wthwuqodnobedlulbexcjfylma",
"type": "initialPayment",
"amount": "12300",
"currency": "EUR",
"status": "success",
"gateway": "checkoutcom",
"errorMessage": null
},
"customer": {
"idAtBillingPlatform": "cus_TM8zJeFCKE15oB",
"urlAtBilling": "https://dashboard.stripe.com/customers/cus_TM8zJeFCKE15oB",
"billingPlatform": "stripe"
},
"invoice": {
"idAtBillingPlatform": "in_1SPQhq4hYau76GhIbWx8zT8H",
"urlAtBilling": "https://dashboard.stripe.com/invoices/in_1SPQhq4hYau76GhIbWx8zT8H",
"amount": "12300",
"currency": "EUR",
"billingPlatform": "stripe"
},
"subscription": {
"idAtBillingPlatform": "sub_1SPQhq4hYau76GhIFvpT8HSx",
"urlAtBilling": "https://dashboard.stripe.com/subscriptions/sub_1SPQhq4hYau76GhIFvpT8HSx",
"nextBillingDate": "2025-12-03T16:34:10Z",
"billingPlatform": "stripe"
}
}

Backend Implementation Examples

Node.js / Express

// Backend endpoint to handle successful payment
app.post('/api/payment-success', async (req, res) => {
const { transactionId } = req.body;

try {
// Fetch transaction details from Altruon
const response = await fetch(
`https://merchant.api.sandbox.altruon.io/api/v1/transaction/${transactionId}/details`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-secret-key': process.env.ALTRUON_SECRET_KEY
}
}
);

if (!response.ok) {
throw new Error(`Failed to fetch transaction details: ${response.status}`);
}

const details = await response.json();

// Process the transaction details
await processSuccessfulPayment(details);

// Store in your database
await db.transactions.create({
transactionId: details.transaction.id,
gatewayTransactionId: details.transaction.idAtGateway,
stripeCustomerId: details.customer.idAtBillingPlatform,
stripeSubscriptionId: details.subscription.idAtBillingPlatform,
stripeInvoiceId: details.invoice.idAtBillingPlatform,
amount: details.transaction.amount,
currency: details.transaction.currency,
status: details.transaction.status,
nextBillingDate: details.subscription.nextBillingDate
});

// Send confirmation email
await sendConfirmationEmail({
customerEmail: await getCustomerEmail(details.customer.idAtBillingPlatform),
transactionId: details.transaction.id,
amount: details.transaction.amount,
currency: details.transaction.currency
});

res.json({ success: true, details });

} catch (error) {
console.error('Error fetching transaction details:', error);
res.status(500).json({ error: 'Failed to process payment' });
}
});

async function processSuccessfulPayment(details) {
// Activate user account
// Grant access to product/service
// Update subscription status
// Log transaction
console.log('Processing payment:', details.transaction.id);
}

Python / Flask

from flask import Flask, request, jsonify
import requests
import os

app = Flask(__name__)

@app.route('/api/payment-success', methods=['POST'])
def payment_success():
data = request.get_json()
transaction_id = data.get('transactionId')

try:
# Fetch transaction details from Altruon
response = requests.get(
f'https://merchant.api.sandbox.altruon.io/api/v1/transaction/{transaction_id}/details',
headers={
'Content-Type': 'application/json',
'x-secret-key': os.environ.get('ALTRUON_SECRET_KEY')
}
)

response.raise_for_status()
details = response.json()

# Process the transaction
process_successful_payment(details)

# Store in database
db.session.add(Transaction(
transaction_id=details['transaction']['id'],
gateway_transaction_id=details['transaction']['idAtGateway'],
stripe_customer_id=details['customer']['idAtBillingPlatform'],
stripe_subscription_id=details['subscription']['idAtBillingPlatform'],
stripe_invoice_id=details['invoice']['idAtBillingPlatform'],
amount=details['transaction']['amount'],
currency=details['transaction']['currency'],
status=details['transaction']['status'],
next_billing_date=details['subscription']['nextBillingDate']
))
db.session.commit()

# Send confirmation email
send_confirmation_email(details)

return jsonify({'success': True, 'details': details})

except requests.exceptions.RequestException as e:
print(f'Error fetching transaction details: {e}')
return jsonify({'error': 'Failed to process payment'}), 500

def process_successful_payment(details):
# Activate user account
# Grant access to product/service
# Update subscription status
print(f"Processing payment: {details['transaction']['id']}")

PHP / Laravel

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use App\Models\Transaction;

class PaymentController extends Controller
{
public function paymentSuccess(Request $request)
{
$transactionId = $request->input('transactionId');

try {
// Fetch transaction details from Altruon
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'x-secret-key' => env('ALTRUON_SECRET_KEY')
])->get(
"https://merchant.api.sandbox.altruon.io/api/v1/transaction/{$transactionId}/details"
);

if (!$response->successful()) {
throw new \Exception('Failed to fetch transaction details');
}

$details = $response->json();

// Process the transaction
$this->processSuccessfulPayment($details);

// Store in database
Transaction::create([
'transaction_id' => $details['transaction']['id'],
'gateway_transaction_id' => $details['transaction']['idAtGateway'],
'stripe_customer_id' => $details['customer']['idAtBillingPlatform'],
'stripe_subscription_id' => $details['subscription']['idAtBillingPlatform'],
'stripe_invoice_id' => $details['invoice']['idAtBillingPlatform'],
'amount' => $details['transaction']['amount'],
'currency' => $details['transaction']['currency'],
'status' => $details['transaction']['status'],
'next_billing_date' => $details['subscription']['nextBillingDate']
]);

// Send confirmation email
$this->sendConfirmationEmail($details);

return response()->json([
'success' => true,
'details' => $details
]);

} catch (\Exception $e) {
\Log::error('Error fetching transaction details: ' . $e->getMessage());
return response()->json([
'error' => 'Failed to process payment'
], 500);
}
}

private function processSuccessfulPayment($details)
{
// Activate user account
// Grant access to product/service
// Update subscription status
\Log::info("Processing payment: {$details['transaction']['id']}");
}
}

Frontend Implementation

When payment succeeds, send the transaction ID to your backend:

// Frontend - onSuccess callback
altruon.on("onSuccess", async (data) => {
console.log('Payment successful!');

try {
// Send transaction ID to your backend
const response = await fetch('/api/payment-success', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userAuthToken}` // Your auth token
},
body: JSON.stringify({
transactionId: data.transactionId
})
});

if (!response.ok) {
throw new Error('Backend processing failed');
}

const result = await response.json();
console.log('Transaction processed:', result);

// Redirect to success page with transaction ID
window.location.href = `/success?txn=${data.transactionId}`;

} catch (error) {
console.error('Error processing payment:', error);
// Show error but payment was successful
alert('Payment completed but there was an issue. Please contact support with transaction ID: ' + data.transactionId);
}
});

Using Transaction Details

Once you have the full transaction details, you can:

  1. Store subscription information for future billing cycles
  2. Link payment to your user account using the customer ID
  3. Track transactions using both Altruon and gateway IDs
  4. Handle recurring billing using the next billing date
  5. Access original invoices in your billing platform using invoice IDs
  6. Provide customer support with complete transaction context
  7. Quick dashboard access using the direct URLs to view entities in provider dashboards

Using Dashboard URLs

The urlAtGateway and urlAtBilling fields provide direct links to view entities in your payment gateway and billing provider dashboards:

Transaction Dashboard URL (urlAtGateway):

  • Opens the transaction details page in your payment gateway (e.g., Checkout.com, Stripe Payments)
  • Useful for reviewing payment details, viewing logs, and investigating issues
  • Example: https://dashboard.sandbox.checkout.com/payments/all-payments/payment/pay_wthwuqodnobedlulbexcjfylma

Billing Dashboard URLs (urlAtBilling):

  • Customer URL: Opens the customer profile in your billing platform (e.g., Stripe Customers)
  • Invoice URL: Opens the invoice details in your billing platform (e.g., Stripe Invoices)
  • Subscription URL: Opens the subscription details in your billing platform (e.g., Stripe Subscriptions)
  • Useful for account management, subscription modifications, and billing support

Example Use Case - Customer Support:

// Display dashboard links in your admin panel
function renderSupportPanel(transactionDetails) {
return `
<div class="support-panel">
<h3>Quick Links</h3>
<ul>
<li>
<a href="${transactionDetails.transaction.urlAtGateway}" target="_blank">
View Payment in ${transactionDetails.transaction.gateway} Dashboard
</a>
</li>
<li>
<a href="${transactionDetails.customer.urlAtBilling}" target="_blank">
View Customer in ${transactionDetails.customer.billingPlatform} Dashboard
</a>
</li>
<li>
<a href="${transactionDetails.invoice.urlAtBilling}" target="_blank">
View Invoice in ${transactionDetails.invoice.billingPlatform} Dashboard
</a>
</li>
<li>
<a href="${transactionDetails.subscription.urlAtBilling}" target="_blank">
View Subscription in ${transactionDetails.subscription.billingPlatform} Dashboard
</a>
</li>
</ul>
</div>
`;
}

Example: Display transaction details on success page

// success-page.js
async function loadTransactionDetails() {
const urlParams = new URLSearchParams(window.location.search);
const transactionId = urlParams.get('txn');

try {
// Fetch from your backend (which calls Altruon API)
const response = await fetch(`/api/transaction/${transactionId}`);
const data = await response.json();

// Display details
document.getElementById('transaction-details').innerHTML = `
<h2>Payment Successful!</h2>
<div class="details">
<p><strong>Transaction ID:</strong> ${data.transaction.id}</p>
<p><strong>Amount:</strong> ${formatCurrency(data.transaction.amount, data.transaction.currency)}</p>
<p><strong>Subscription ID:</strong> ${data.subscription.idAtBillingPlatform}</p>
<p><strong>Next Billing Date:</strong> ${formatDate(data.subscription.nextBillingDate)}</p>
<p><strong>Invoice ID:</strong> ${data.invoice.idAtBillingPlatform}</p>
</div>
`;
} catch (error) {
console.error('Error loading transaction details:', error);
}
}

Complete Integration Example

Here's a complete example showing all four callbacks working together:

<!DOCTYPE html>
<html>
<head>
<title>Checkout</title>
<script src="https://cdn.sandbox.altruon.io/altruon-sdk.min.js"></script>
<style>
.error-message, .warning-message, .success-message {
padding: 12px;
border-radius: 6px;
margin-bottom: 16px;
display: none;
}
.error-message {
background: #FEE2E2;
color: #DC2626;
}
.warning-message {
background: #FEF3C7;
color: #D97706;
}
.success-message {
background: #D1FAE5;
color: #059669;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 32px;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
z-index: 1000;
max-width: 500px;
display: none;
}
.modal.active {
display: block;
}
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: none;
}
.overlay.active {
display: block;
}
</style>
</head>
<body>
<h1>Complete Your Purchase</h1>

<div id="error-message" class="error-message"></div>
<div id="warning-message" class="warning-message"></div>
<div id="success-message" class="success-message"></div>

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

<div id="overlay" class="overlay"></div>
<div id="modal" class="modal"></div>

<script>
// Get session ID from your server
const sessionId = '<%= sessionId %>';

// Initialize Altruon with all callbacks
altruon
.init("pk_sandbox_your_key", "your-domain")
.on("onSuccess", async (data) => {
console.log('✅ Payment successful!', data);

// Hide any errors
hideAllMessages();

// Show success message
showMessage('success', data.message);

// Track analytics
if (window.gtag) {
gtag('event', 'purchase', {
transaction_id: data.transactionId
});
}

// Send transaction ID to backend for detailed processing
try {
await fetch('/api/payment-success', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionId: data.transactionId })
});
} catch (error) {
console.error('Failed to notify backend:', error);
}

// Redirect after delay
setTimeout(() => {
window.location.href = `/success?txn=${data.transactionId}`;
}, 2000);
})
.on("onFailure", (error) => {
console.error('❌ Payment failed:', error);

// Show error message
showMessage('error', error.message);

// Track failure
if (window.gtag) {
gtag('event', 'payment_failed', {
error_status: error.status,
error_message: error.message
});
}

// Re-enable payment button
enablePaymentForm();
})
.on("onDataRequired", (data) => {
console.warn('⚠️ Additional data required:', data);

// Show warning message
showMessage('warning', data.message);

// Parse required fields
const fields = data.requiredFields.split(',').map(f => f.trim());

// Show modal with form for missing data
showDataCollectionModal(fields, data.details);
})
.on("onActionRequired", (data) => {
console.log('🔔 Action required:', data.actionType);

if (data.actionType === 'redirect') {
// Handle redirect (3DS, etc.)
showMessage('warning', data.message);
sessionStorage.setItem('paymentInProgress', 'true');

setTimeout(() => {
window.location.href = data.data.url;
}, 1500);

} else if (data.actionType === 'qrDataDisplay') {
// Handle QR code display
showQRCodeModal(data);
}
});

// Set session and render component
altruon.setSession(sessionId);
altruon.renderComponent('#payment-container');

// Helper functions
function showMessage(type, message) {
const messageDiv = document.getElementById(`${type}-message`);
messageDiv.textContent = message;
messageDiv.style.display = 'block';

// Auto-hide after 5 seconds (except errors)
if (type !== 'error') {
setTimeout(() => {
messageDiv.style.display = 'none';
}, 5000);
}
}

function hideAllMessages() {
document.getElementById('error-message').style.display = 'none';
document.getElementById('warning-message').style.display = 'none';
document.getElementById('success-message').style.display = 'none';
}

function showDataCollectionModal(fields, details) {
const modal = document.getElementById('modal');
const overlay = document.getElementById('overlay');

const formFields = fields.map(field => {
const fieldName = field.split('.').pop();
const label = fieldName.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());

return `
<div style="margin-bottom: 12px;">
<label style="display: block; margin-bottom: 4px;">${label}</label>
<input
type="text"
id="field-${field}"
style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"
required
/>
</div>
`;
}).join('');

modal.innerHTML = `
<h2>Additional Information Required</h2>
<p style="color: #666; margin-bottom: 20px;">${details}</p>
<form id="data-form">
${formFields}
<button
type="submit"
style="width: 100%; padding: 12px; background: #3B82F6; color: white; border: none; border-radius: 6px; cursor: pointer;"
>
Continue
</button>
</form>
`;

modal.classList.add('active');
overlay.classList.add('active');

document.getElementById('data-form').addEventListener('submit', (e) => {
e.preventDefault();

// Collect and update data
fields.forEach(field => {
const value = document.getElementById(`field-${field}`).value;
// Update Altruon with the data
// ... implementation depends on field type
});

modal.classList.remove('active');
overlay.classList.remove('active');

// Trigger payment retry
retryPayment();
});
}

function showQRCodeModal(data) {
const modal = document.getElementById('modal');
const overlay = document.getElementById('overlay');

modal.innerHTML = `
<h2>Scan to Pay</h2>
<p style="margin-bottom: 20px;">${data.message}</p>
<div style="text-align: center; margin-bottom: 20px;">
<img src="${data.data.qrCodeImage}" alt="QR Code" style="max-width: 300px;" />
</div>
<p style="text-align: center; color: #666;">
Waiting for payment confirmation...
</p>
<button
onclick="closeModal()"
style="width: 100%; padding: 12px; background: #6B7280; color: white; border: none; border-radius: 6px; cursor: pointer; margin-top: 20px;"
>
Cancel
</button>
`;

modal.classList.add('active');
overlay.classList.add('active');
}

function closeModal() {
document.getElementById('modal').classList.remove('active');
document.getElementById('overlay').classList.remove('active');
}

function enablePaymentForm() {
// Re-enable form elements
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => btn.disabled = false);
}

function retryPayment() {
// Manually trigger payment if needed
const iframe = document.querySelector('#payment-container iframe');
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({ type: "makePayment" }, "*");
}
}
</script>
</body>
</html>

Best Practices

1. Always Handle All Callbacks

Even if you don't expect certain scenarios, always implement all four callbacks for a robust integration:

altruon
.init("pk_sandbox_your_key", "your-domain")
.on("onSuccess", handleSuccess)
.on("onFailure", handleFailure)
.on("onDataRequired", handleDataRequired)
.on("onActionRequired", handleActionRequired);

2. Provide Clear User Feedback

Always inform users about what's happening:

altruon.on("onFailure", (error) => {
// ✅ Good: Clear, actionable message
showNotification('error', error.message, {
action: 'Try Again',
onAction: () => retryPayment()
});
});

3. Track Events for Analytics

Monitor payment flow success and failure rates:

altruon
.on("onSuccess", (data) => {
analytics.track('Payment Success', {
transactionId: data.transactionId
});
})
.on("onFailure", (error) => {
analytics.track('Payment Failed', {
status: error.status,
message: error.message
});
})
.on("onDataRequired", (data) => {
analytics.track('Data Required', {
fields: data.requiredFields
});
})
.on("onActionRequired", (data) => {
analytics.track('Action Required', {
actionType: data.actionType
});
});

4. Handle Mobile Redirect Scenarios

For mobile redirects (3DS, bank authentication), ensure users can return:

// Before redirect
altruon.on("onActionRequired", (data) => {
if (data.actionType === 'redirect') {
// Save state
sessionStorage.setItem('paymentInProgress', 'true');
sessionStorage.setItem('returnUrl', window.location.href);

// Redirect
window.location.href = data.data.url;
}
});

// On return page load
window.addEventListener('load', () => {
if (sessionStorage.getItem('paymentInProgress') === 'true') {
// Check payment status with your backend
checkPaymentStatus();
}
});

5. Implement Graceful Error Recovery

Allow users to retry failed payments easily:

altruon.on("onFailure", (error) => {
showError(error.message, {
actions: [
{ label: 'Try Again', onClick: retryPayment },
{ label: 'Use Different Card', onClick: changePaymentMethod }
]
});
});

Testing Callbacks

Test onSuccess

Simulate successful payment using test card: 4111 1111 1111 1111

Test onFailure

Simulate declined payment using test card: 4000 0000 0000 0002

Test onDataRequired

Leave required fields empty and attempt payment

Test onActionRequired (Redirect)

Use test card requiring 3DS: 4000 0027 6000 3184

Test onActionRequired (QR Code)

Select PIX or UPI payment method to see QR code flow


Troubleshooting

Callbacks Not Firing

Problem: Callbacks are not being triggered

Solution:

  1. Ensure callbacks are registered before renderComponent()
  2. Check browser console for errors
  3. Verify session is set correctly
// ✅ Correct order
altruon
.init("pk_sandbox_key", "domain")
.on("onSuccess", handler)
.on("onFailure", handler);

altruon.setSession(sessionId);
altruon.renderComponent("#container");

Multiple Callback Triggers

Problem: Callbacks fire multiple times

Solution: Ensure you're not re-initializing Altruon:

// ✅ Initialize only once
if (!window.altruonInitialized) {
altruon.init("pk_sandbox_key", "domain");
window.altruonInitialized = true;
}

QR Code Not Displaying

Problem: QR code image is broken or not showing

Solution: Check that qrCodeImage contains valid base64 data:

altruon.on("onActionRequired", (data) => {
if (data.actionType === 'qrDataDisplay') {
// Verify image data
console.log('QR Image:', data.data.qrCodeImage.substring(0, 50));

// Use fallback if image is missing
if (data.data.qrCodeImage) {
displayQRImage(data.data.qrCodeImage);
} else if (data.data.qrCode) {
generateQRCode(data.data.qrCode); // Use library to generate
}
}
});

Next Steps