NexusBilling & Pricing
Quick Start - Payment Integration
Hands-on examples for integrating FortiBlox Nexus billing - credit card and cryptocurrency payments
Quick Start - Payment Integration
Get started quickly with complete, production-ready code examples for both credit card and cryptocurrency payments.
Prerequisites
Before you begin:
# Install required packages
npm install @stripe/stripe-js @solana/web3.js @solana/spl-tokenYou'll need:
- FortiBlox API key
- Stripe Publishable Key (for credit card payments)
- X1 Blockchain wallet (for crypto payments)
Example 1: Subscribe with Credit Card
Complete example with error handling and best practices.
Frontend (React)
import React, { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
// Initialize Stripe with your Publishable Key
const stripePromise = loadStripe('pk_test_YOUR_PUBLISHABLE_KEY');
function SubscriptionPage() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
async function handleSubscribe(tier) {
setLoading(true);
setError(null);
try {
// Step 1: Create checkout session
const response = await fetch('https://nexus.fortiblox.com/api/billing/create-checkout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
tier: tier,
success_url: `${window.location.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${window.location.origin}/pricing`
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Failed to create checkout session');
}
const session = await response.json();
// Step 2: Redirect to Stripe Checkout
const stripe = await stripePromise;
const result = await stripe.redirectToCheckout({
sessionId: session.id
});
if (result.error) {
// Handle Stripe error
setError(result.error.message);
console.error('Stripe error:', result.error);
}
} catch (err) {
setError(err.message);
console.error('Subscription error:', err);
} finally {
setLoading(false);
}
}
return (
<div className="subscription-page">
<h1>Choose Your Plan</h1>
{error && (
<div className="error-message">
<strong>Error:</strong> {error}
</div>
)}
<div className="pricing-cards">
<PricingCard
tier="developer"
name="Developer"
price={49}
features={[
'50M credits/month',
'500 requests/second',
'Email support',
'gRPC Testnet access'
]}
onSubscribe={handleSubscribe}
loading={loading}
/>
<PricingCard
tier="business"
name="Business"
price={499}
features={[
'500M credits/month',
'2,000 requests/second',
'Priority support',
'Webhooks',
'gRPC Testnet access'
]}
onSubscribe={handleSubscribe}
loading={loading}
popular={true}
/>
<PricingCard
tier="professional"
name="Professional"
price={999}
features={[
'2B credits/month',
'5,000 requests/second',
'Dedicated support',
'Webhooks',
'gRPC Mainnet access'
]}
onSubscribe={handleSubscribe}
loading={loading}
/>
</div>
</div>
);
}
function PricingCard({ tier, name, price, features, onSubscribe, loading, popular }) {
return (
<div className={`pricing-card ${popular ? 'popular' : ''}`}>
{popular && <div className="badge">Most Popular</div>}
<h2>{name}</h2>
<div className="price">
<span className="currency">$</span>
<span className="amount">{price}</span>
<span className="period">/month</span>
</div>
<ul className="features">
{features.map((feature, index) => (
<li key={index}>✓ {feature}</li>
))}
</ul>
<button
onClick={() => onSubscribe(tier)}
disabled={loading}
className="subscribe-button"
>
{loading ? 'Processing...' : 'Subscribe'}
</button>
</div>
);
}
export default SubscriptionPage;Success Page (Handle Return from Stripe)
import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
function SuccessPage() {
const [searchParams] = useSearchParams();
const [status, setStatus] = useState('verifying');
const [subscription, setSubscription] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const sessionId = searchParams.get('session_id');
if (!sessionId) {
setError('No session ID provided');
setStatus('error');
return;
}
verifySession(sessionId);
}, [searchParams]);
async function verifySession(sessionId) {
try {
const response = await fetch('https://nexus.fortiblox.com/api/billing/verify-session', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ session_id: sessionId })
});
if (!response.ok) {
throw new Error('Failed to verify session');
}
const result = await response.json();
if (result.success) {
setSubscription(result.subscription);
setStatus('success');
// Optional: Track conversion
trackConversion(result.subscription.tier, result.subscription.amount);
} else {
throw new Error(result.message || 'Verification failed');
}
} catch (err) {
setError(err.message);
setStatus('error');
}
}
function trackConversion(tier, amount) {
// Analytics tracking
if (window.gtag) {
window.gtag('event', 'purchase', {
transaction_id: subscription?.id,
value: amount / 100,
currency: 'USD',
items: [{
item_id: tier,
item_name: `FortiBlox ${tier} tier`,
price: amount / 100
}]
});
}
}
if (status === 'verifying') {
return (
<div className="success-page">
<div className="spinner"></div>
<h2>Verifying your subscription...</h2>
</div>
);
}
if (status === 'error') {
return (
<div className="success-page error">
<h2>Payment Verification Failed</h2>
<p>{error}</p>
<a href="/pricing" className="button">Try Again</a>
</div>
);
}
return (
<div className="success-page">
<div className="success-icon">✓</div>
<h1>Subscription Activated!</h1>
<div className="subscription-details">
<h3>Your {subscription.tier} subscription is now active</h3>
<div className="details">
<div className="detail-item">
<span className="label">Credits:</span>
<span className="value">{subscription.credits_allocated.toLocaleString()}</span>
</div>
<div className="detail-item">
<span className="label">Rate Limit:</span>
<span className="value">{subscription.rate_limit} requests/second</span>
</div>
<div className="detail-item">
<span className="label">Next Billing:</span>
<span className="value">
{new Date(subscription.current_period_end).toLocaleDateString()}
</span>
</div>
</div>
</div>
<div className="actions">
<a href="/dashboard" className="button primary">Go to Dashboard</a>
<a href="/docs" className="button secondary">View API Docs</a>
</div>
</div>
);
}
export default SuccessPage;Backend API (Node.js/Express)
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const router = express.Router();
// Create Stripe Checkout Session
router.post('/billing/create-checkout', async (req, res) => {
try {
const { tier, success_url, cancel_url } = req.body;
const userId = req.user.id; // From auth middleware
// Get price ID for tier
const priceIds = {
developer: process.env.STRIPE_PRICE_DEVELOPER,
business: process.env.STRIPE_PRICE_BUSINESS,
professional: process.env.STRIPE_PRICE_PROFESSIONAL
};
const priceId = priceIds[tier];
if (!priceId) {
return res.status(400).json({ error: 'Invalid tier' });
}
// Create or retrieve Stripe customer
let customerId = req.user.stripe_customer_id;
if (!customerId) {
const customer = await stripe.customers.create({
email: req.user.email,
metadata: {
user_id: userId
}
});
customerId = customer.id;
// Save customer ID to database
await db.query(
'UPDATE users SET stripe_customer_id = $1 WHERE id = $2',
[customerId, userId]
);
}
// Create checkout session
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{
price: priceId,
quantity: 1
}],
success_url: success_url,
cancel_url: cancel_url,
metadata: {
user_id: userId,
tier: tier
},
allow_promotion_codes: true,
billing_address_collection: 'required',
automatic_tax: { enabled: true }
});
res.json({ id: session.id, url: session.url });
} catch (error) {
console.error('Checkout error:', error);
res.status(500).json({ error: error.message });
}
});
// Verify session after redirect
router.post('/billing/verify-session', async (req, res) => {
try {
const { session_id } = req.body;
const session = await stripe.checkout.sessions.retrieve(session_id);
if (session.payment_status === 'paid') {
// Get subscription details
const subscription = await stripe.subscriptions.retrieve(session.subscription);
res.json({
success: true,
subscription: {
id: subscription.id,
tier: session.metadata.tier,
status: subscription.status,
amount: subscription.items.data[0].price.unit_amount,
current_period_end: new Date(subscription.current_period_end * 1000),
credits_allocated: getTierCredits(session.metadata.tier),
rate_limit: getTierRateLimit(session.metadata.tier)
}
});
} else {
res.json({ success: false, message: 'Payment not completed' });
}
} catch (error) {
console.error('Verification error:', error);
res.status(500).json({ error: error.message });
}
});
// Handle Stripe webhooks
router.post('/billing/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
switch (event.type) {
case 'customer.subscription.created':
case 'customer.subscription.updated':
await handleSubscriptionUpdate(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCancelled(event.data.object);
break;
case 'invoice.paid':
await handleInvoicePaid(event.data.object);
break;
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object);
break;
}
res.json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(400).send(`Webhook Error: ${error.message}`);
}
});
async function handleSubscriptionUpdate(subscription) {
const userId = subscription.metadata.user_id;
const tier = subscription.metadata.tier;
await db.query(`
UPDATE users
SET
subscription_tier = $1,
subscription_status = $2,
stripe_subscription_id = $3,
credits_allocated = $4,
credits_used = 0,
updated_at = NOW()
WHERE id = $5
`, [tier, subscription.status, subscription.id, getTierCredits(tier), userId]);
console.log(`Subscription updated for user ${userId}: ${tier}`);
}
async function handleSubscriptionCancelled(subscription) {
const userId = subscription.metadata.user_id;
await db.query(`
UPDATE users
SET
subscription_tier = 'free',
subscription_status = 'cancelled',
stripe_subscription_id = NULL,
credits_allocated = 1000000,
updated_at = NOW()
WHERE id = $1
`, [userId]);
console.log(`Subscription cancelled for user ${userId}`);
}
function getTierCredits(tier) {
const credits = {
free: 1000000,
developer: 50000000,
business: 500000000,
professional: 2000000000
};
return credits[tier] || credits.free;
}
function getTierRateLimit(tier) {
const limits = {
free: 100,
developer: 500,
business: 2000,
professional: 5000
};
return limits[tier] || limits.free;
}
module.exports = router;Example 2: Pay with USDC (Crypto)
Complete example using X1 Blockchain and Phantom wallet.
Frontend (React + Phantom Wallet)
import React, { useState, useEffect } from 'react';
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import {
getAssociatedTokenAddress,
createTransferInstruction,
TOKEN_PROGRAM_ID
} from '@solana/spl-token';
const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
const SOLANA_NETWORK = 'https://api.mainnet-beta.solana.com';
function CryptoPaymentPage() {
const [wallet, setWallet] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [paymentDetails, setPaymentDetails] = useState(null);
const [txSignature, setTxSignature] = useState(null);
useEffect(() => {
// Check if Phantom is installed
if (window.solana?.isPhantom) {
setWallet(window.solana);
}
}, []);
async function connectWallet() {
try {
if (!wallet) {
alert('Please install Phantom wallet');
window.open('https://phantom.app', '_blank');
return;
}
await wallet.connect();
console.log('Connected to wallet:', wallet.publicKey.toString());
} catch (err) {
setError('Failed to connect wallet: ' + err.message);
}
}
async function initiatePayment(tier) {
setLoading(true);
setError(null);
try {
// Step 1: Get payment details from API
const response = await fetch('https://nexus.fortiblox.com/api/crypto/initiate-payment', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ tier, token: 'USDC' })
});
if (!response.ok) {
throw new Error('Failed to initiate payment');
}
const details = await response.json();
setPaymentDetails(details);
// Step 2: Send payment
await sendUSDCPayment(details);
} catch (err) {
setError(err.message);
console.error('Payment error:', err);
} finally {
setLoading(false);
}
}
async function sendUSDCPayment(details) {
if (!wallet.publicKey) {
await connectWallet();
return;
}
try {
const connection = new Connection(SOLANA_NETWORK);
// Convert amount to lamports (6 decimals for USDC)
const amount = details.amount * Math.pow(10, 6);
// Get token accounts
const fromTokenAccount = await getAssociatedTokenAddress(
USDC_MINT,
wallet.publicKey
);
const toTokenAccount = await getAssociatedTokenAddress(
USDC_MINT,
new PublicKey(details.payment_address)
);
// Create transfer instruction
const transferInstruction = createTransferInstruction(
fromTokenAccount,
toTokenAccount,
wallet.publicKey,
amount,
[],
TOKEN_PROGRAM_ID
);
// Create transaction
const transaction = new Transaction().add(transferInstruction);
transaction.feePayer = wallet.publicKey;
// Get recent blockhash
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
// Sign and send transaction
const signed = await wallet.signTransaction(transaction);
const signature = await connection.sendRawTransaction(signed.serialize());
setTxSignature(signature);
console.log('Transaction sent:', signature);
// Wait for confirmation
await connection.confirmTransaction(signature, 'confirmed');
console.log('Transaction confirmed');
// Verify payment with backend
await verifyPayment(details.payment_id, signature);
} catch (err) {
throw new Error('Transaction failed: ' + err.message);
}
}
async function verifyPayment(paymentId, signature) {
try {
const response = await fetch('https://nexus.fortiblox.com/api/crypto/verify-payment', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
payment_id: paymentId,
signature: signature
})
});
const result = await response.json();
if (result.success) {
// Payment verified, subscription activated
window.location.href = '/success?crypto=true&signature=' + signature;
} else {
throw new Error('Payment verification failed');
}
} catch (err) {
throw new Error('Verification failed: ' + err.message);
}
}
return (
<div className="crypto-payment-page">
<h1>Pay with Cryptocurrency</h1>
<p className="discount-badge">Save 8% compared to credit card pricing</p>
{!wallet?.publicKey ? (
<div className="connect-wallet">
<h2>Connect Your Wallet</h2>
<p>You'll need an X1 Blockchain wallet with USDC to continue</p>
<button onClick={connectWallet} className="button primary">
Connect Phantom Wallet
</button>
</div>
) : (
<div className="wallet-connected">
<p>Connected: {wallet.publicKey.toString().slice(0, 8)}...{wallet.publicKey.toString().slice(-8)}</p>
{error && (
<div className="error-message">{error}</div>
)}
{txSignature && (
<div className="success-message">
<h3>Payment Sent!</h3>
<p>Transaction: {txSignature.slice(0, 16)}...</p>
<a
href={`https://solscan.io/tx/${txSignature}`}
target="_blank"
rel="noopener noreferrer"
>
View on Solscan
</a>
</div>
)}
<div className="pricing-cards">
<CryptoPricingCard
tier="developer"
name="Developer"
price={45}
onPay={initiatePayment}
loading={loading}
/>
<CryptoPricingCard
tier="business"
name="Business"
price={459}
onPay={initiatePayment}
loading={loading}
popular={true}
/>
<CryptoPricingCard
tier="professional"
name="Professional"
price={919}
onPay={initiatePayment}
loading={loading}
/>
</div>
</div>
)}
</div>
);
}
function CryptoPricingCard({ tier, name, price, onPay, loading, popular }) {
const cardPrice = 49; // Original price for comparison
const savings = cardPrice - price;
const savingsPercent = Math.round((savings / cardPrice) * 100);
return (
<div className={`pricing-card ${popular ? 'popular' : ''}`}>
{popular && <div className="badge">Most Popular</div>}
<h2>{name}</h2>
<div className="price">
<div className="crypto-price">
<span className="amount">{price}</span>
<span className="currency">USDC</span>
</div>
<div className="savings">
Save {savingsPercent}% (${savings}/mo)
</div>
</div>
<button
onClick={() => onPay(tier)}
disabled={loading}
className="subscribe-button"
>
{loading ? 'Processing...' : `Pay ${price} USDC`}
</button>
</div>
);
}
export default CryptoPaymentPage;Backend API (Crypto Payments)
const express = require('express');
const { Connection, PublicKey } = require('@solana/web3.js');
const router = express.Router();
const SOLANA_CONNECTION = new Connection('https://api.mainnet-beta.solana.com');
const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
// Payment address where users send USDC
const PAYMENT_WALLET = process.env.CRYPTO_PAYMENT_WALLET;
// Initiate crypto payment
router.post('/crypto/initiate-payment', async (req, res) => {
try {
const { tier, token } = req.body;
const userId = req.user.id;
// Get price for tier (8% discount)
const prices = {
developer: 45,
business: 459,
professional: 919
};
const amount = prices[tier];
if (!amount) {
return res.status(400).json({ error: 'Invalid tier' });
}
// Create payment record
const paymentId = `pay_${Date.now()}_${userId}`;
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
await db.query(`
INSERT INTO crypto_payments (
id, user_id, tier, amount, token, status, expires_at
) VALUES ($1, $2, $3, $4, $5, $6, $7)
`, [paymentId, userId, tier, amount, token, 'pending', expiresAt]);
res.json({
payment_id: paymentId,
amount: amount,
token: token,
payment_address: PAYMENT_WALLET,
expires_at: expiresAt,
qr_code: generateQRCode(PAYMENT_WALLET, amount, token)
});
} catch (error) {
console.error('Payment initiation error:', error);
res.status(500).json({ error: error.message });
}
});
// Verify crypto payment
router.post('/crypto/verify-payment', async (req, res) => {
try {
const { payment_id, signature } = req.body;
// Get payment record
const payment = await db.query(
'SELECT * FROM crypto_payments WHERE id = $1',
[payment_id]
);
if (!payment.rows.length) {
return res.status(404).json({ error: 'Payment not found' });
}
const paymentData = payment.rows[0];
// Verify transaction on X1 Blockchain
const transaction = await SOLANA_CONNECTION.getTransaction(signature, {
commitment: 'confirmed'
});
if (!transaction) {
return res.status(400).json({ error: 'Transaction not found' });
}
// Verify transaction details
const verified = await verifyTransaction(
transaction,
PAYMENT_WALLET,
paymentData.amount,
paymentData.token
);
if (!verified) {
return res.status(400).json({ error: 'Transaction verification failed' });
}
// Update payment status
await db.query(`
UPDATE crypto_payments
SET status = 'confirmed', signature = $1, confirmed_at = NOW()
WHERE id = $2
`, [signature, payment_id]);
// Activate subscription
await activateSubscription(paymentData.user_id, paymentData.tier);
res.json({
success: true,
message: 'Payment verified and subscription activated',
subscription: {
tier: paymentData.tier,
status: 'active',
expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
}
});
} catch (error) {
console.error('Verification error:', error);
res.status(500).json({ error: error.message });
}
});
async function verifyTransaction(transaction, expectedRecipient, expectedAmount, token) {
// Verify transaction succeeded
if (transaction.meta.err) {
return false;
}
// Verify recipient and amount
// (Simplified - in production, parse transaction data more thoroughly)
const accountKeys = transaction.transaction.message.accountKeys;
const recipientKey = new PublicKey(expectedRecipient);
// Check if recipient is in transaction
const hasRecipient = accountKeys.some(key =>
key.equals(recipientKey)
);
if (!hasRecipient) {
return false;
}
// In production: Parse transfer instruction and verify exact amount
// This is a simplified check
return true;
}
async function activateSubscription(userId, tier) {
const credits = {
developer: 50000000,
business: 500000000,
professional: 2000000000
};
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
await db.query(`
UPDATE users
SET
subscription_tier = $1,
subscription_status = 'active',
credits_allocated = $2,
credits_used = 0,
subscription_expires_at = $3,
updated_at = NOW()
WHERE id = $4
`, [tier, credits[tier], expiresAt, userId]);
console.log(`Crypto subscription activated for user ${userId}: ${tier}`);
}
module.exports = router;Example 3: Check Subscription Status
// Utility function to check subscription status
async function getSubscriptionStatus(apiKey) {
try {
const response = await fetch('https://nexus.fortiblox.com/api/subscription', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const subscription = await response.json();
return {
tier: subscription.tier,
status: subscription.status,
isActive: subscription.status === 'active',
isPastDue: subscription.status === 'past_due',
isCancelled: subscription.status === 'cancelled',
creditsUsed: subscription.credits_used,
creditsRemaining: subscription.credits_remaining,
creditsTotal: subscription.credits_allocated,
usagePercent: (subscription.credits_used / subscription.credits_allocated) * 100,
resetDate: new Date(subscription.current_period_end),
daysUntilReset: Math.ceil(
(new Date(subscription.current_period_end) - new Date()) / (1000 * 60 * 60 * 24)
)
};
} catch (error) {
console.error('Failed to get subscription status:', error);
return null;
}
}
// Usage in your app
const status = await getSubscriptionStatus(YOUR_API_KEY);
if (status.isPastDue) {
showWarning('Your payment failed. Please update your payment method.');
} else if (status.usagePercent > 90) {
showWarning(`You've used ${status.usagePercent.toFixed(1)}% of your credits. Consider upgrading.`);
} else if (status.daysUntilReset <= 3) {
showInfo(`Your credits reset in ${status.daysUntilReset} days.`);
}Example 4: Handle Rate Limiting
// Implement exponential backoff for rate limits
async function makeAPIRequest(url, options, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limit hit
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, retries);
console.warn(`Rate limit hit. Retrying after ${retryAfter} seconds...`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
retries++;
continue;
}
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
if (retries === maxRetries - 1) {
throw error;
}
retries++;
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, retries)));
}
}
}
// Usage
const balance = await makeAPIRequest('https://nexus.fortiblox.com/api/rpc/getBalance', {
method: 'POST',
headers: {
'Authorization': `Bearer ${YOUR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ address: 'x1abc...' })
});Testing
Test Credit Card Payments
// Use Stripe test cards
const TEST_CARDS = {
success: '4242424242424242',
decline: '4000000000000002',
requires3DS: '4000002500003155'
};
// In development, use test Publishable Key
const stripe = loadStripe('pk_test_YOUR_TEST_KEY');Test Crypto Payments
// Use X1 Blockchain Devnet for testing
const TEST_CONNECTION = new Connection('https://api.devnet.solana.com');
// Use test USDC on Devnet
const TEST_USDC_MINT = new PublicKey('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU');
// Test payment with smaller amounts
const testPayment = {
amount: 0.01, // 0.01 USDC for testing
token: 'USDC',
network: 'devnet'
};Production Checklist
Before deploying to production:
- Switch to live Stripe keys (
pk_live_,sk_live_) - Update Stripe webhook endpoint to production URL
- Test full payment flow on production
- Set up error monitoring (Sentry, etc.)
- Configure proper CORS settings
- Enable rate limiting on payment endpoints
- Set up payment failure notifications
- Test webhook delivery
- Verify transaction on X1 Blockchain mainnet
- Set up analytics tracking
- Configure proper error messages
- Test mobile responsiveness
- Set up backup payment verification
Support
Need help integrating payments?
Email: [email protected] Discord: Join our community Documentation: Full billing docs
Last Updated: 2025-11-21