PaymentsFintechCustom Solutions

Beyond Stripe: when to build your own payments

March 10, 2024 • 12 min read

When you might outgrow Stripe

  • Multi-party marketplaces – splitting money between several sellers, plus escrow
  • Your own fraud rules – risk logic tied to how your business actually works
  • Heavy compliance – healthcare, finance, anything regulated
  • High volume, thin margins – where the per-transaction fee starts to hurt

What you get in return

  • Checkout that fits your flow – not the other way around
  • The reporting you actually want – instead of what the dashboard gives you
  • Your own payment data – it lives in your systems
  • Possibly lower fees – at the right volume, anyway

Use Stripe. For most teams that's the whole answer, and I'd reach for it again without thinking. But some businesses hit its edges. At The Good Guys we ended up building our own payment orchestration, mostly because we had B2B transactions, installment plans, and a couple of legacy finance systems that weren't going anywhere and weren't going to bend to fit a standard checkout. This is when that's worth doing, and how not to set fire to your PCI scope while you do it.

Off-the-shelf vs. your own

Stripe/Standard Solutions
• Quick integration
• Limited customization
• Fixed fee structure
• Standard fraud rules
Custom Payment System
• Tailored business logic
• Flexible fee structures
• Custom fraud detection
• Direct processor relationships

Building your own buys you control. It also signs you up for compliance work that doesn't end. Someone owns PCI forever now, and that someone is you.

Keeping PCI scope small

Get PCI scope right first. Every part of your system that touches a raw card number is a part the auditors get to inspect, so the trick is to make that surface as small as you can. Here's roughly how we laid it out:

// PCI-compliant payment architecture
Payment Gateway Integration:
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (PCI DSS Scope: Minimal)                              │
│ • Token-based payments only                                    │
│ • No cardholder data storage                                   │
│ • HTTPS everywhere                                             │
│ • CSP headers and security policies                           │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│ Payment Orchestration Layer (PCI DSS Level 1)                 │
│ • Vault tokenization service                                  │
│ • Multi-processor routing                                     │
│ • Fraud detection engine                                      │
│ • Secure key management (HSM)                                 │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│ Payment Processors                                             │
│ • Primary: Direct bank relationships                          │
│ • Secondary: Stripe/Adyen for fallback                       │
│ • Regional: Local processors for specific markets            │
└─────────────────────────────────────────────────────────────────┘

A marketplace split, in code

Marketplace payments are where it gets fiddly. One buyer pays once, but the money has to split across the sellers, your platform fee, and the processor. Get the order of operations wrong and you end up holding funds you have no right to hold, which is a regulatory problem, not a bug. Here's the shape of it:

// Custom marketplace payment flow
public class MarketplacePaymentService
{
    public async Task<PaymentResult> ProcessMarketplacePayment(
        MarketplacePaymentRequest request)
    {
        // Step 1: Validate all parties and amounts
        var validation = await ValidateMarketplaceTransaction(request);
        if (!validation.IsValid)
            return PaymentResult.Failed(validation.Errors);

        // Step 2: Create escrow hold
        var escrowResult = await CreateEscrowHold(request.TotalAmount);
        if (!escrowResult.Success)
            return PaymentResult.Failed("Escrow creation failed");

        // Step 3: Process payment to escrow
        var paymentResult = await ProcessPaymentToEscrow(
            request.BuyerPaymentMethod, 
            request.TotalAmount,
            escrowResult.EscrowId
        );

        if (!paymentResult.Success)
        {
            await ReleaseEscrowHold(escrowResult.EscrowId);
            return PaymentResult.Failed("Payment processing failed");
        }

        // Step 4: Calculate and distribute funds
        var distributions = CalculateDistributions(request);
        var distributionResults = new List<DistributionResult>();

        foreach (var distribution in distributions)
        {
            var result = await TransferFromEscrow(
                escrowResult.EscrowId,
                distribution.RecipientId,
                distribution.Amount,
                distribution.Description
            );
            distributionResults.Add(result);
        }

        // Step 5: Handle any failed distributions
        var failedDistributions = distributionResults
            .Where(r => !r.Success).ToList();

        if (failedDistributions.Any())
        {
            await HandleFailedDistributions(
                escrowResult.EscrowId, 
                failedDistributions
            );
        }

        return PaymentResult.Success(paymentResult.TransactionId);
    }

    private List<Distribution> CalculateDistributions(
        MarketplacePaymentRequest request)
    {
        var distributions = new List<Distribution>();
        var remainingAmount = request.TotalAmount;

        // Platform fee (our commission)
        var platformFee = request.TotalAmount * 0.03m; // 3%
        distributions.Add(new Distribution
        {
            RecipientId = "platform_account",
            Amount = platformFee,
            Description = "Platform commission"
        });
        remainingAmount -= platformFee;

        // Payment processing fee
        var processingFee = request.TotalAmount * 0.029m + 0.30m; // 2.9% + 30¢
        distributions.Add(new Distribution
        {
            RecipientId = "payment_processor_account",
            Amount = processingFee,
            Description = "Payment processing fee"
        });
        remainingAmount -= processingFee;

        // Distribute to sellers proportionally
        foreach (var seller in request.Sellers)
        {
            var sellerAmount = (seller.ItemValue / request.ItemsTotal) * remainingAmount;
            distributions.Add(new Distribution
            {
                RecipientId = seller.SellerId,
                Amount = sellerAmount,
                Description = $"Payment for items sold"
            });
        }

        return distributions;
    }
}

Things I'd insist on next time

  • Security from day one – PCI, tokenization and encryption aren't features you bolt on later.
  • Assume things will fail – every step needs error handling, a rollback path, and an audit trail you can read at 2am.
  • Reconciliation isn't an afterthought – finance will need the numbers to line up, and regulators will ask.
  • Plan for more than one country – currencies, local payment methods and rules differ more than you'd think.
  • It has to stay fast under load – payments are the one place users won't forgive a spinner.

The maths on when it pays off

# Custom Payment System ROI Analysis

Development Investment:
- Initial development: 6-12 months, $200K-500K
- PCI compliance: $50K-100K annually
- Ongoing maintenance: 2-3 FTE developers
- Security audits: $25K-50K annually

Break-even Analysis (Monthly):
┌─────────────────────┬─────────────┬─────────────┬─────────────┐
│ Transaction Volume  │ Stripe Cost │ Custom Cost │ Savings     │
├─────────────────────┼─────────────┼─────────────┼─────────────┤
│ $100K              │ $2,900      │ $25K        │ -$22,100    │
│ $500K              │ $14,500     │ $30K        │ -$15,500    │
│ $1M                 │ $29,000     │ $35K        │ -$6,000     │
│ $5M                 │ $145,000    │ $50K        │ +$95,000    │
│ $10M                │ $290,000    │ $65K        │ +$225,000   │
└─────────────────────┴─────────────┴─────────────┴─────────────┘

Sweet Spot: $3M+ monthly transaction volume
Additional Benefits:
• Complete control over user experience
• Custom fraud detection and risk management
• Direct processor relationships (better rates)
• Advanced analytics and reporting capabilities

So none of this is a pitch to ditch Stripe. For most teams it's a bad trade. Work out what you actually need, add up the cost of owning it for years and not just building it once, and check you've got the security and compliance people to do it properly. If the numbers still say build, build. They usually don't.

Share: