Payment Integration Architecture: Cards, Wallets, Invoices, and Order Status
Building a unified payment integration architecture that handles cards, digital wallets, invoices, and order status tracking. Technical insights and real-world trade-offs.

Payment integration is rarely as simple as dropping in a single SDK. In our builds at DigiForge, we've seen projects balloon because teams underestimated the complexity of handling multiple payment methods — cards, digital wallets, invoiced payments — and then tying them all back to an order lifecycle. Each payment type has its own quirks, and stitching them together without a coherent architecture leads to fragile code and painful reconciliation. This article breaks down what a unified payment integration layer looks like, drawing on both established patterns and newer developments like stablecoin-backed cards.
Cards: Tokenization, Network Tokens, and the Rise of Stablecoin-Backed Cards
Card payments remain the backbone of ecommerce and many SaaS businesses. The golden rule is to never handle raw card numbers. Tokenization via a PCI-compliant gateway (Stripe, Braintree, Adyen) is table stakes. But there's a subtler layer: network tokens. These are device-specific, single-use tokens issued by card networks themselves, offering better authorization rates and reduced fraud. We usually push clients to adopt network tokenization early, especially if they process recurring payments, because token lifespans are longer and updates are handled by the network.
A newer wave is the stablecoin-backed card. As of late 2025, crypto card volume had reached an $18 billion annualized run rate, growing at 106% annually since 2023 (Wirex/Crossmint press release). The architecture here is different: instead of pulling from a bank account or credit line, the card draws from a stablecoin wallet. For developers, that means integrating with a wallet provider (like Crossmint's smart wallet) and a card issuer (like Wirex). The challenge is compliance — the press release notes that fintechs previously had to assemble wallet, issuer, and compliance frameworks separately. At DigiForge, we've worked on similar multi-vendor integrations, and we've found that an abstraction layer with a unified transaction model is essential. Otherwise, debugging a failed payment becomes a goose chase across three provider dashboards.
Digital Wallets: UPI, Google Pay, and Hosted vs. API-Driven Integration
Digital wallets are not monolithic. Google Pay (now part of the broader Google Payments ecosystem) operates differently from India's UPI-based wallets like Paytm, and both differ from app-specific wallets. The architectural decision is whether to use a hosted checkout (simpler, less control) or an API-driven integration (more complex, richer UX).
For wallets like Google Pay, Apple Pay, and PayPal, the common pattern is a wallet button that triggers a sheet or redirect. The integration is typically done through the payment gateway's unified checkout — Stripe's Payment Element, for example, renders all wallets automatically. That's fine for many cases, but we've run into limitations when you need custom styling or want to collect additional data before the payment. A deeper integration via the wallet's own SDK gives more control but commits you to maintaining separate code paths.
UPI-based wallets like Paytm operate differently. UPI (Unified Payments Interface) is an instant payment system that allows direct bank-to-bank transfers. When integrating Paytm as a payment method, the typical flow is: the user selects Paytm, the backend generates a payment request, the user authorizes in the Paytm app, and Paytm sends a callback. The challenge here is idempotency — UPI payments can succeed on the bank side but report failure to the merchant due to network timeouts. We always build a reconciliation job that compares our order status with Paytm's transaction status every few minutes.
One pattern we've found reliable: treat every wallet payment as a two-phase commit. Phase one creates a pending order, phase two confirms via webhook. Never mark an order as complete on a redirect callback alone.
Invoices: Payment Requests and Reconciliation
Invoiced payments — where a business generates a bill and the customer pays later — introduce a different set of problems. The IRS Payments page (irs.gov/payments) is an example of a large-scale invoicing system: you look up your balance (via a notice or online account), then pay using bank account (Direct Pay) or payment plan. The architecture needs to handle both the invoice lifecycle (issued, sent, overdue, paid) and the payment lifecycle (initiated, pending, settled, failed).
For B2B SaaS or custom web applications, we often build an invoicing module that generates PDFs and hosts a payment portal. The portal must accept multiple methods: card, wallet, or bank transfer. The trick is to link the invoice ID to the payment transaction ID in the gateway, and then use webhooks to update the invoice status. A common mistake is to rely on the payment gateway's hosted invoice feature but lose control over the reconciliation. We prefer to store our own invoice records and use the gateway as a payment processor only.
// Example webhook handler for invoice payment confirmation
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const invoiceId = session.metadata.invoice_id;
await db.invoices.update({ id: invoiceId }, { status: 'paid' });
}
res.json({ received: true });
});
The code above is simplistic but illustrates the core idea: map the gateway's event to your own domain model. The metadata field is your lifeline. Without it, you'd have to query Stripe to match sessions, which adds latency and complexity.
Order Status: Mapping Payment Events to Lifecycle
An order can go through many statuses: pending_payment, payment_received, processing, fulfilled, cancelled. The payment event should be only one of many triggers. The key architectural decision is whether to use a state machine or a simpler status field. We strongly favor state machines for any system with more than four statuses or with complex transitions (e.g., an order can go from 'payment_received' to 'processing' to 'shipped', but also from 'pending_payment' to 'cancelled'). Rails' StateMachines gem or a custom finite state machine in Node.js works well.
Webhooks from payment gateways should feed directly into the state machine. For example, a payment_intent.succeeded event should transition the order from 'pending_payment' to 'payment_received'. But guard against race conditions: if the webhook arrives twice (most gateways guarantee at-least-once delivery), your state machine must be idempotent — i.e., transitioning from the same current state to the same next state should be a no-op. We store an event_id with each webhook to deduplicate.
Idempotency is not optional. If your payment webhook handler is not idempotent, you will eventually double-charge a customer or create ghost orders. Testing this under normal load is hard; we simulate delayed and duplicate webhooks in our CI pipeline.
Order status also needs to be exposed to customers. A simple status page (like a progress bar) works, but only if the underlying transitions are accurate. We've seen systems where the frontend polls an API that caches the order status — but the cache may be stale if the webhook hasn't been processed. The solution is to use a real-time channel (WebSocket or Server-Sent Events) that pushes status changes, so the UI updates immediately when the webhook is processed. For simpler projects, a short-lived cache with a TTL of 30 seconds is acceptable.
DigiForge's Take: Build a Payment Integration Layer, Not a Mess
After integrating dozens of payment methods across projects, our advice is to abstract the payment processing behind a thin service layer. This layer should normalize each payment provider's events into a common event format, handle retries, and maintain a transaction log. The order system never talks directly to Stripe, Paytm, or any issuer — it talks to your payment service. This makes it much easier to add new payment methods (say, a stablecoin card) without touching the order logic.
We also recommend treating invoices as a separate domain, not just a status on an order. An invoice has its own lifecycle (draft, sent, overdue, paid) and can be associated with multiple orders (e.g., a monthly subscription). Similarly, order status should be driven by a state machine that handles all possible transitions, including partial payments, refunds, and chargebacks.
If you're building a payment integration from scratch, start by mapping out all the payment methods you plan to support and identify the common events they emit. Then design your state machine and webhook handler. Get that right, and the rest is plumbing. If you'd like a second opinion on your architecture, the team at DigiForge has seen enough corner cases to save you a few late nights.
Sources
- Wirex and Crossmint Announce Card Integration to Connect Stablecoin Wallets and Real-World Spending
- Wirex and Crossmint Announce Card Integration to Connect Stablecoin Wallets and Real-World Spending
- payments.google.com
- Payments | Internal Revenue Service
- Paytm: Secure & Fast UPI Payments, Recharge Mobile & Pay Bills


