Documentation Index
Fetch the complete documentation index at: https://whiskypeak.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Monero Integration Guide
This guide provides detailed information about integrating Monero (XMR) payments into your application using WhiskyPay.
Integration Architecture
The Monero payment integration consists of:
- Frontend Component: React component for displaying the payment UI
- Backend Gateway: Handles payment processing and verification
- Webhook Notifications: Real-time updates about payment status
Client-Side Integration
Basic Implementation
The simplest way to integrate Monero payments is using the MoneroPaymentComponent:
import React from 'react';
import { MoneroPaymentComponent } from 'monero-payment-sdk';
function CheckoutPage() {
return (
<div className="container">
<h1>Complete Your Purchase</h1>
<MoneroPaymentComponent
gatewayUrl="https://your-gateway-url.com"
amount={0.01}
description="Premium Plan Subscription"
onPaymentComplete={(id) => console.log(`Payment complete: ${id}`)}
onPaymentError={(error) => console.error('Payment error:', error)}
/>
</div>
);
}
Advanced Implementation
For more control over the payment flow, you can use the MoneroPayment class directly:
import React, { useState, useEffect } from 'react';
import { MoneroPayment } from 'monero-payment-sdk';
function AdvancedCheckout() {
const [paymentId, setPaymentId] = useState(null);
const [address, setAddress] = useState(null);
const [status, setStatus] = useState('idle');
const [countdown, setCountdown] = useState(0);
// Initialize payment client
const moneroClient = useMemo(() =>
new MoneroPayment({ gatewayUrl: 'https://your-gateway-url.com' }),
[]
);
// Start payment process
const startPayment = async () => {
setStatus('creating');
try {
const { id, address } = await moneroClient.createInvoice({
amount: 0.01,
description: 'Premium subscription'
});
setPaymentId(id);
setAddress(address);
setStatus('pending');
// Start checking for payment
checkPaymentStatus(id);
} catch (error) {
setStatus('error');
console.error('Failed to create invoice:', error);
}
};
// Check payment status
const checkPaymentStatus = async (id) => {
try {
const info = await moneroClient.checkInvoice(id);
setStatus(info.status.toLowerCase());
// Calculate time remaining if there's an expiry
if (info.expiry) {
const expiry = new Date(info.expiry);
const remaining = Math.max(0, Math.floor((expiry.getTime() - Date.now()) / 1000));
setCountdown(remaining);
}
// Continue checking if payment is pending
if (['pending', 'confirming'].includes(info.status.toLowerCase())) {
setTimeout(() => checkPaymentStatus(id), 10000);
}
} catch (error) {
setStatus('error');
console.error('Failed to check payment:', error);
}
};
// Render component based on status
return (
<div className="container">
<h1>Advanced Checkout</h1>
{status === 'idle' && (
<button onClick={startPayment}>
Pay with Monero
</button>
)}
{status === 'creating' && <p>Creating payment...</p>}
{status === 'pending' && (
<div>
<h2>Payment Pending</h2>
<p>Send XMR to this address:</p>
<div className="address-box">{address}</div>
<button onClick={() => navigator.clipboard.writeText(address)}>
Copy Address
</button>
<p>Time remaining: {Math.floor(countdown / 60)}:{(countdown % 60).toString().padStart(2, '0')}</p>
</div>
)}
{status === 'confirming' && (
<div>
<h2>Payment Confirming</h2>
<p>We've detected your payment and are waiting for confirmations.</p>
</div>
)}
{status === 'received' && (
<div>
<h2>Payment Complete</h2>
<p>Thank you for your payment!</p>
</div>
)}
{status === 'expired' && (
<div>
<h2>Payment Expired</h2>
<p>The payment window has expired.</p>
<button onClick={startPayment}>
Try Again
</button>
</div>
)}
{status === 'error' && (
<div>
<h2>Payment Error</h2>
<p>An error occurred processing your payment.</p>
<button onClick={startPayment}>
Try Again
</button>
</div>
)}
</div>
);
}
Server-Side Integration
Setting Up the Backend
To fully integrate Monero payments, you’ll need server-side components to:
- Verify payments
- Process webhooks
- Update your database
- Send notifications
Here’s an example using Node.js and Express:
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const router = express.Router();
// Environment variables
const GATEWAY_URL = process.env.GATEWAY_URL;
const API_KEY = process.env.MONERO_API_KEY;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Create a new payment
router.post('/create-payment', async (req, res) => {
try {
const { amount, description, customerId, productId } = req.body;
// Validate inputs
if (!amount || amount <= 0) {
return res.status(400).json({ error: 'Invalid amount' });
}
// Create payment with the gateway
const response = await axios.get(`${GATEWAY_URL}/api/monero/new`, {
params: { amount, description },
headers: { 'X-Auth-Token': API_KEY }
});
const { id, address } = response.data;
// Store payment details in your database
await db.payments.create({
paymentId: id,
customerId,
productId,
amount,
status: 'pending',
createdAt: new Date()
});
// Return payment details to the client
return res.json({ id, address });
} catch (error) {
console.error('Error creating payment:', error);
return res.status(500).json({ error: 'Failed to create payment' });
}
});
// Check payment status
router.get('/check-payment/:id', async (req, res) => {
try {
const { id } = req.params;
// Validate payment ID
if (!id || id.length !== 16) {
return res.status(400).json({ error: 'Invalid payment ID' });
}
// Check payment with the gateway
const response = await axios.get(`${GATEWAY_URL}/api/monero/info`, {
params: { id },
headers: { 'X-Auth-Token': API_KEY }
});
// Return payment status to the client
return res.json(response.data);
} catch (error) {
console.error('Error checking payment:', error);
return res.status(500).json({ error: 'Failed to check payment' });
}
});
// Webhook handler
router.post('/webhook', async (req, res) => {
try {
const { id, status, signature, timestamp } = req.body;
// Verify webhook signature
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(`${id}:${status}:${timestamp}`)
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Update payment status in your database
await db.payments.update(
{ status },
{ where: { paymentId: id } }
);
// Handle completed payments
if (status === 'Received') {
const payment = await db.payments.findOne({
where: { paymentId: id }
});
// Activate the purchased product/service
await activateProduct(payment.customerId, payment.productId);
// Send confirmation email to customer
await sendConfirmationEmail(payment.customerId, payment.productId);
}
return res.status(200).json({ success: true });
} catch (error) {
console.error('Error processing webhook:', error);
return res.status(500).json({ error: 'Failed to process webhook' });
}
});
module.exports = router;
Database Schema
A basic schema for storing Monero payments:
CREATE TABLE monero_payments (
id SERIAL PRIMARY KEY,
payment_id VARCHAR(16) NOT NULL,
customer_id VARCHAR(255) NOT NULL,
product_id VARCHAR(255) NOT NULL,
amount DECIMAL(16, 12) NOT NULL,
status VARCHAR(50) NOT NULL,
address VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_payment_id ON monero_payments(payment_id);
CREATE INDEX idx_customer_id ON monero_payments(customer_id);
Advanced Features
Payment Expiration Handling
Monero payments typically have an expiration window. You should implement logic to handle expired payments:
// Check for expired payments and clean up
async function handleExpiredPayments() {
const expiredPayments = await db.payments.findAll({
where: {
status: 'pending',
createdAt: { [Op.lt]: new Date(Date.now() - 30 * 60 * 1000) }
}
});
for (const payment of expiredPayments) {
// Verify with gateway if payment is actually expired
const response = await axios.get(`${GATEWAY_URL}/api/monero/info`, {
params: { id: payment.paymentId },
headers: { 'X-Auth-Token': API_KEY }
});
if (response.data.status === 'Expired') {
// Update payment status
await db.payments.update(
{ status: 'expired' },
{ where: { id: payment.id } }
);
// Notify customer
await sendPaymentExpiredEmail(payment.customerId, payment.productId);
}
}
}
// Run this job periodically
setInterval(handleExpiredPayments, 15 * 60 * 1000);
Exchange Rate Handling
For applications that price in fiat currencies, implement exchange rate conversion:
async function getCurrentXMRRate() {
const response = await axios.get('https://api.coingecko.com/api/v3/simple/price', {
params: {
ids: 'monero',
vs_currencies: 'usd'
}
});
return response.data.monero.usd;
}
async function createPaymentWithFiatAmount(fiatAmount, currency = 'usd') {
// Get current XMR exchange rate
const xmrRate = await getCurrentXMRRate();
// Calculate XMR amount with some buffer for rate fluctuations
const xmrAmount = (fiatAmount / xmrRate) * 1.01; // 1% buffer
// Create payment with XMR amount
const payment = await createMoneroPayment(xmrAmount);
return {
...payment,
fiatAmount,
currency,
exchangeRate: xmrRate
};
}
Security Considerations
Securing Your Integration
- Validate Webhook Signatures: Always verify webhook signatures to prevent fraud
- API Key Protection: Store API keys securely as environment variables
- Payment Data Validation: Validate all payment data on both client and server
- Address Verification: Validate Monero addresses using proper regex patterns
- Rate Limiting: Implement rate limiting on payment creation endpoints
- SSL/TLS: Ensure all API communications use HTTPS
Monero Address Validation
Use proper validation for Monero addresses:
function isValidMoneroAddress(address) {
// Standard Monero address (95 characters)
const standardRegex = /^4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$/;
// Integrated address (106 characters)
const integratedRegex = /^8[0-9AB][1-9A-HJ-NP-Za-km-z]{104}$/;
// Subaddress (95 characters)
const subaddressRegex = /^8[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$/;
return (
standardRegex.test(address) ||
integratedRegex.test(address) ||
subaddressRegex.test(address)
);
}
Troubleshooting
Common Issues and Solutions
| Issue | Possible Cause | Solution |
|---|
| Payment not detected | Network delays | Wait for block confirmations |
| Expired payments | Customer took too long | Implement automatic retry flow |
| Gateway connection errors | API key issues | Verify API key and permissions |
| Webhook not received | Incorrect webhook URL | Verify URL in dashboard |
| Payment amount mismatch | Exchange rate fluctuation | Add buffer to XMR amount |
- Payment Status Check: Use the API to check payment status manually
- Gateway Logs: Review logs in the WhiskyPay dashboard
- Network Monitoring: Use block explorers to verify transactions
Next Steps