Payments Guide
This guide covers implementing pay-to-play and in-game purchases using the Arcadia Game SDK.
Overview
The Arcadia Game SDK handles all payment complexity for you. You simply request a payment with an amount and token type, and the SDK:
- Prompts the player to approve the transaction
- Sends the transaction to the blockchain
- Returns complete payment details immediately
- Handles fee calculation and developer payouts automatically
Payment Types
Pay-to-Play
One-time payment required to access or start a game. Typically used for:
- Game access fees
- Premium game modes
- One-time unlocks
In-Game Purchases
Purchases made during gameplay. Typically used for:
- Items and equipment
- Upgrades and power-ups
- Cosmetics and skins
- Currency packs
Payment Flow
sequenceDiagram
participant Game
participant SDK
participant Arcadia
participant Wallet
participant Blockchain
Game->>SDK: payment.payToPlay(amount, token)
SDK->>Arcadia: Payment request via postMessage
Arcadia->>Wallet: Request transaction approval
Wallet->>Blockchain: Send transaction
Blockchain-->>Wallet: Transaction signature
Wallet-->>Arcadia: Transaction confirmed
Arcadia->>SDK: Payment result with details
SDK-->>Game: PaymentResult object
Pay-to-Play Implementation
Basic Example
import { ArcadiaSDK, PaymentFailedError } from '@arcadiasol/sdk';
try {
const result = await arcadia.payment.payToPlay(0.5, 'SOL');
// Payment successful!
console.log('Payment successful!');
console.log('Transaction:', result.txSignature);
console.log('Amount:', result.amount, result.token);
console.log('Timestamp:', result.timestamp);
// Start game
startGame();
} catch (error) {
if (error instanceof PaymentFailedError) {
console.error('Payment failed:', error.message);
showError('Payment failed. Please try again.');
}
}
With Payment Verification
async function handlePayToPlay(requiredAmount: number) {
try {
const result = await arcadia.payment.payToPlay(requiredAmount, 'SOL');
// Verify payment details
if (result.success &&
result.amount === requiredAmount &&
result.token === 'SOL') {
// Payment verified - proceed
await markPlayerAsPaid(walletAddress);
await logPayment(result);
startGame();
} else {
throw new Error('Payment verification failed');
}
} catch (error) {
handlePaymentError(error);
}
}
Complete Pay-to-Play Flow
class GamePaymentManager {
private arcadia: ArcadiaSDK;
private walletAddress: string | null = null;
constructor(arcadia: ArcadiaSDK) {
this.arcadia = arcadia;
}
async checkPaymentRequired(): Promise<boolean> {
if (!this.walletAddress) {
this.walletAddress = await this.arcadia.getWalletAddress();
}
// Check if player has already paid
const playerData = await this.loadPlayerData(this.walletAddress!);
return !playerData.hasPaid;
}
async processPayToPlay(amount: number, token: 'SOL' | 'USDC'): Promise<boolean> {
try {
const result = await this.arcadia.payment.payToPlay(amount, token);
// Verify payment
if (!result.success || result.amount !== amount) {
throw new Error('Payment verification failed');
}
// Update player data
await this.markAsPaid(this.walletAddress!, result);
// Log payment for analytics
await this.logPayment(result);
return true;
} catch (error) {
console.error('Payment error:', error);
return false;
}
}
private async markAsPaid(walletAddress: string, paymentResult: PaymentResult) {
const playerData = await this.loadPlayerData(walletAddress);
playerData.hasPaid = true;
playerData.paymentTx = paymentResult.txSignature;
playerData.paidAt = paymentResult.timestamp;
await this.savePlayerData(walletAddress, playerData);
}
private async logPayment(result: PaymentResult) {
// Log to your analytics service
await fetch('/api/analytics/payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'pay_to_play',
...result,
}),
});
}
private async loadPlayerData(walletAddress: string) {
// Load player data
const response = await fetch(`/api/player-data?wallet=${walletAddress}`);
return response.json();
}
private async savePlayerData(walletAddress: string, data: any) {
// Save player data
await fetch('/api/player-data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress, data }),
});
}
}
In-Game Purchases
Basic Example
async function purchaseItem(itemId: string, price: number) {
try {
const result = await arcadia.payment.purchaseItem(itemId, price, 'SOL');
// Verify purchase
if (result.success && result.amount === price) {
// Add item to inventory
addItemToInventory(itemId);
// Update player data
await savePurchase(walletAddress, {
itemId,
...result,
});
showSuccess('Item purchased successfully!');
}
} catch (error) {
console.error('Purchase failed:', error);
showError('Purchase failed. Please try again.');
}
}
Item Shop Implementation
class ItemShop {
private arcadia: ArcadiaSDK;
private walletAddress: string | null = null;
constructor(arcadia: ArcadiaSDK) {
this.arcadia = arcadia;
}
async buyItem(item: ShopItem) {
if (!this.walletAddress) {
this.walletAddress = await this.arcadia.getWalletAddress();
if (!this.walletAddress) {
showError('Please connect your wallet');
return;
}
}
try {
// Show loading state
showLoading(`Purchasing ${item.name}...`);
// Process purchase
const result = await this.arcadia.payment.purchaseItem(
item.id,
item.price,
item.token
);
// Verify purchase
if (result.success &&
result.amount === item.price &&
result.token === item.token) {
// Add item to inventory
await this.addToInventory(this.walletAddress, item.id);
// Update player balance
await this.updatePlayerBalance(this.walletAddress, -item.price);
// Log purchase
await this.logPurchase(item, result);
showSuccess(`${item.name} purchased successfully!`);
} else {
throw new Error('Purchase verification failed');
}
} catch (error) {
console.error('Purchase error:', error);
showError('Purchase failed: ' + error.message);
} finally {
hideLoading();
}
}
private async addToInventory(walletAddress: string, itemId: string) {
const playerData = await this.loadPlayerData(walletAddress);
if (!playerData.inventory) {
playerData.inventory = [];
}
playerData.inventory.push(itemId);
await this.savePlayerData(walletAddress, playerData);
}
private async updatePlayerBalance(walletAddress: string, amount: number) {
// Update player's in-game balance
const playerData = await this.loadPlayerData(walletAddress);
playerData.balance = (playerData.balance || 0) + amount;
await this.savePlayerData(walletAddress, playerData);
}
private async logPurchase(item: ShopItem, result: PaymentResult) {
await fetch('/api/analytics/purchase', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
itemId: item.id,
itemName: item.name,
...result,
}),
});
}
}
Payment Result Structure
The PaymentResult object contains all payment details:
interface PaymentResult {
success: boolean; // Whether payment was successful
txSignature: string; // Blockchain transaction signature
amount: number; // Amount paid by user
token: 'SOL' | 'USDC'; // Token type used
timestamp: string; // ISO timestamp when payment completed
purchaseId?: string; // Arcadia purchase ID (for tracking)
platformFee?: number; // Platform fee deducted
developerAmount?: number; // Amount received by developer (after fees)
error?: string; // Error message if payment failed
}
Using Payment Details
const result = await arcadia.payment.payToPlay(0.5, 'SOL');
// Store in your database
await savePaymentRecord({
walletAddress: walletAddress,
txSignature: result.txSignature,
amount: result.amount,
token: result.token,
timestamp: result.timestamp,
purchaseId: result.purchaseId,
platformFee: result.platformFee,
developerAmount: result.developerAmount,
});
// Use for analytics
trackEvent('payment_completed', {
amount: result.amount,
token: result.token,
platformFee: result.platformFee,
});
Error Handling
Common Payment Errors
import {
PaymentFailedError,
WalletNotConnectedError,
InvalidAmountError,
InvalidTokenError,
TimeoutError,
} from '@arcadiasol/sdk';
try {
const result = await arcadia.payment.payToPlay(0.5, 'SOL');
} catch (error) {
if (error instanceof WalletNotConnectedError) {
showError('Please connect your wallet to make a payment');
} else if (error instanceof PaymentFailedError) {
showError('Payment failed: ' + error.message);
// Check error.txSignature if available
} else if (error instanceof InvalidAmountError) {
showError('Invalid payment amount');
} else if (error instanceof InvalidTokenError) {
showError('Invalid token type. Use SOL or USDC');
} else if (error instanceof TimeoutError) {
showError('Payment request timed out. Please try again.');
} else {
showError('An unexpected error occurred');
}
}
Retry Logic
async function payToPlayWithRetry(amount: number, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const result = await arcadia.payment.payToPlay(amount, 'SOL');
return result;
} catch (error) {
if (i === maxRetries - 1) {
throw error; // Last attempt failed
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
Best Practices
- Verify Payment Amount - Always verify the returned amount matches what you requested
- Store Transaction Signatures - Keep transaction signatures for record-keeping
- Handle Errors Gracefully - Provide clear error messages to users
- Show Loading States - Indicate when payment is processing
- Don't Block on Payment - Payments return immediately after transaction is sent
- Log All Payments - Keep records for analytics and debugging
- Validate Before Processing - Check payment details before granting items/access
Payment Timing
Payments return immediately after the transaction is sent to the blockchain. The SDK doesn't wait for full confirmation, making the payment experience instant for players.
However, you should:
- Trust the payment result for immediate game actions
- Use transaction signatures for verification if needed
- Handle edge cases where transactions might fail after initial success
Next Steps
- Review API Reference for complete payment method documentation
- Check Examples for complete payment implementations
- Read Error Handling for detailed error information