Crypto Payments in E-commerce: Custom Payment Gateway
A typical mistake when integrating crypto into e-commerce: treating it as just another payment method in the existing checkout. In reality, it's a different flow — crypto payments have no instant finality (except L2), no chargebacks, the exchange rate changes while the user walks to their wallet, and partial payment is a real edge case, not a theoretical one. In our experience, we've identified typical bottlenecks: incorrect gas calculation, lack of rollback for partial payments, and problems with transaction verification in L2. Below is a full breakdown of the architecture, from provider selection to accounting reporting.
Custom integration pays off 3 times faster than hosted solutions for volumes from 100 payments per day. We've been integrating crypto payments since the early days of this field and have seen stores lose up to 12% of revenue due to improper handling of nuances.
Choosing an Approach: Hosted vs Custom
Compare three options:
| Parameter | Ready Service (NOWPayments, CoinGate) | Custom via Provider API | Fully Custom On-Chain |
|---|---|---|---|
| Launch time | 1-2 days | 3-5 days | 2-4 weeks |
| Commission | 0.5–1% | 0.1–0.5% (network only) | Network gas only |
| Supported coins | Fixed list | Any ERC-20/BEP-20 | Any (Solana, Bitcoin) |
| Privacy | Third party sees transactions | Provider sees only payments | Full privacy |
Ready services are for volumes up to a few hundred payments per month. Custom integration is justified when:
- You need a specific set of coins/networks not supported by the provider
- Privacy requirements (client doesn't want third parties to see transactions)
- High volumes where provider commissions are significant
- Specific logic (e.g., automatic conversion via DEX)
Below — custom integration, as it requires more technical solutions. Our experience: 30+ projects, 12-month code warranty.
WooCommerce: Custom Payment Gateway Plugin
WooCommerce provides the abstract class WC_Payment_Gateway — just extend it:
class WC_Crypto_Gateway extends WC_Payment_Gateway {
public function __construct() {
$this->id = 'crypto_payment';
$this->title = 'Pay with Cryptocurrency';
$this->method_description = 'Bitcoin, Ethereum, USDT and others';
$this->supports = ['products'];
$this->init_form_fields();
$this->init_settings();
add_action('woocommerce_update_options_payment_gateways_' . $this->id,
[$this, 'process_admin_options']);
add_action('woocommerce_api_crypto_payment', [$this, 'handle_webhook']);
}
public function process_payment($order_id): array {
$order = wc_get_order($order_id);
// Create payment in external service or generate address
$payment = $this->create_crypto_payment($order);
// Save data for displaying instructions
$order->update_meta_data('_crypto_payment_id', $payment['id']);
$order->update_meta_data('_crypto_pay_address', $payment['address']);
$order->update_meta_data('_crypto_pay_amount', $payment['amount']);
$order->update_meta_data('_crypto_expires_at', $payment['expires_at']);
$order->set_status('pending', 'Awaiting crypto payment');
$order->save();
return [
'result' => 'success',
'redirect' => $this->get_return_url($order),
];
}
public function handle_webhook(): void {
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYMENT_SIGNATURE'] ?? '';
if (!$this->verify_signature($payload, $signature)) {
wp_die('Invalid signature', 401);
}
$data = json_decode($payload, true);
$order = wc_get_order($data['order_id']);
if (!$order) wp_die('Order not found', 404);
if ($data['status'] === 'confirmed') {
$order->payment_complete($data['transaction_hash']);
$order->add_order_note(
sprintf('Crypto payment confirmed. TX: %s', $data['transaction_hash'])
);
}
wp_die('OK', 200);
}
}
The thank you page (after redirect) should show the address, QR code, and amount with a timer. WooCommerce calls get_return_url() which leads to the standard thank you page — you can customize it via the woocommerce_thankyou_{gateway_id} action.
Shopify: Using the Payment Apps API
Shopify does not allow arbitrary custom PHP. To integrate crypto, you need to create a Shopify App via the Partner Dashboard and use the Payments Apps API.
The principle: your app registers as a payment provider. During checkout, Shopify makes an HTTP request to your endpoint with order data, you return a URL for redirect to your payment page, and after confirmation, you send resolved/rejected via GraphQL mutation.
// Shopify calls this endpoint
app.post('/shopify/payment', async (req, res) => {
const { gid, amount, currency, cancelUrl, kind } = req.body;
// Create internal payment
const payment = await createCryptoInvoice({
shopifyOrderGid: gid,
fiatAmount: parseFloat(amount),
fiatCurrency: currency,
});
// Redirect to our payment page
res.json({
redirect_url: `${process.env.APP_URL}/pay/${payment.id}`,
});
});
// After payment confirmation
async function notifyShopifyPaymentComplete(paymentGid: string, txHash: string) {
const mutation = `
mutation PaymentSessionResolve($id: ID!) {
paymentSessionResolve(id: $id) {
paymentSession {
id
state { ... on PaymentSessionStateResolved { code } }
}
userErrors { field message }
}
}
`;
await shopifyGraphQL(mutation, { id: paymentGid });
}
How Does the Exchange Rate and Timer Affect UX and Revenue?
The user sees a price of $99, clicks "pay with crypto", and lands on a page with an amount of 0.0271 ETH. This amount is valid for 15–30 minutes. If the user is slow or the rate changes significantly, a refresh mechanism is needed.
The timer on the payment page should not be decorative — when it expires, the invoice should be automatically updated:
// Client-side code
let expiresAt = new Date(invoice.expiresAt);
const timer = setInterval(async () => {
const remaining = expiresAt.getTime() - Date.now();
if (remaining <= 0) {
clearInterval(timer);
// Request a new invoice with the current rate
const refreshed = await fetch(`/api/payment/${invoiceId}/refresh`, {
method: 'POST'
});
const newInvoice = await refreshed.json();
expiresAt = new Date(newInvoice.expiresAt);
updateUI(newInvoice); // Update QR and amount
}
}, 1000);
On the backend, when refreshing — recalculate the crypto amount at the current rate, update the database record, using the same address (if using unique address per payment).
Reconciliation and Reporting
For accounting, you need to convert the crypto amount to fiat at the time of receipt. Record in the database: crypto_amount, crypto_currency, fiat_amount, fiat_currency, exchange_rate, confirmed_at. The rate source — Chainlink (on-chain) or CoinGecko API (off-chain) with timestamp. This is critical for tax accounting.
What to Do About Partial Payment?
Edge case: user sent 0.02 ETH instead of 0.0271 ETH. Without special logic, the order will hang. Solution: at the worker level, check that the received amount >= expected, otherwise mark as partially_paid and generate a second invoice for the remainder. Include support for multi-transactions in a single order.
Why Choose Custom Integration?
Custom integration gives you full control over the stack: from blockchain selection to error handling logic. You are not dependent on provider commissions and limitations. At volumes from 100 payments per day, the savings on commissions exceed development costs within 3 months. We also implement protection against reentrancy attacks and optimize gas consumption for mass operations.
Gas Optimization for High Payment Volumes
Use a gas price oracle and batch transactions through a relayer. This reduces commission costs by up to 40%.What's Included in the Work
- Technical audit of your current store (CMS, hosting, payment modules)
- Architecture selection: hosted or custom, on-chain or L2
- Development of the payment gateway (WooCommerce, Shopify, custom)
- Integration with wallets (MetaMask, WalletConnect, Ledger)
- Configuration of webhooks and order statuses
- Development of the payment page with QR code and timer
- Integration of a rate oracle (Chainlink) or exchange API
- Full testnet testing
- Smart contract audit (Slither, Mythril) — if necessary
- Documentation and staff training
- Post-release support for 1 month
Request a consultation — we'll get back to you within 3 hours and show an example of a similar integration for your niche. Order a pilot integration on a test domain — 3 days to ensure compatibility.







