Skip to main content

Payment Gateway Integrations (Stripe, PayPal, Adyen) & Subscriptions

The payments module provides unified payment processing, recurring subscription billing lifecycle management, webhook signature verification, and multi-provider adapters for Stripe, PayPal, and Adyen in Rust microservices.


1. What It Is & Architectural Purpose​

Integrating payment processors into e-commerce platforms and SaaS products requires handling complex transaction workflows: processing credit card charges, managing recurring subscription plans, handling payment failures, and verifying cryptographic webhook signatures.

The PaymentEngine in Ferrox abstracts payment providers into a single, type-safe Rust API (PaymentProvider). It ensures 100% PCI-DSS compliance by delegating raw credit card tokenization directly to payment provider SDKs while managing server-side customer billing state safely.

┌────────────────────────────────────────────────────────────────────────┐
│ Ferrox PaymentEngine │
├──────────────────────────────────┬─────────────────────────────────────┤
│ Unified Payment Provider API │ Cryptographic Webhook Verifier │
│ • Charges & Refund Management │ • Signature Verification (Stripe) │
│ • Subscription Lifecycle Engine │ • Idempotency Header Validator │
└────────────────┬─────────────────┴──────────────────┬──────────────────┘
│ Secure API Protocol
┌──────────┴────────────────────────────────────┴──────────┐
▼ ▼
┌─────────────────────────────────┐ ┌───────────────────────┐
│ Stripe API Gateway │ │ PayPal / Adyen API │
└─────────────────────────────────┘ └───────────────────────┘

2. What It Does & Key Capabilities​

  • Unified Payment Provider Trait: Standardizes charges, refunds, customer profiles, and payment intent workflows.
  • Subscription Lifecycle Manager: Manages subscription creation, plan upgrades, downgrades, cancellations, and grace periods.
  • Webhook Signature Verifier: Verifies incoming Stripe/PayPal webhook signatures to prevent forged payment events.
  • Idempotent Transaction Execution: Attaches unique idempotency keys (Idempotency-Key) to prevent double-charging users on network retries.

3. How It Works Under the Hood​

Payment Intent & Webhook Verification Sequence​

sequenceDiagram
autonumber
participant Client as Web Frontend
participant Controller as Ferrox Payment Controller
participant Engine as PaymentEngine (Stripe Adapter)
participant Stripe as Stripe API

Client->>Controller: POST /api/payments/intent (Amount: $49.99)
Controller->>Engine: create_payment_intent(customer_id, 4999, "usd")
Engine->>Stripe: POST /v1/payment_intents (Idempotency-Key: "idempotent_123")
Stripe-->>Engine: Return PaymentIntent ClientSecret
Engine-->>Controller: Return ClientSecret
Controller-->>Client: Deliver ClientSecret for Stripe Elements JS
Note over Client, Stripe: Client confirms payment on browser...
Stripe->>Controller: POST /api/webhooks/stripe (Event: payment_intent.succeeded)
Controller->>Engine: verify_webhook_signature(headers, payload, secret)
Engine-->>Controller: Signature Verified -> Fulfill Customer Order

4. Why It Was Designed This Way​

FeatureDirect Vendor SDK HardcodingFerrox PaymentEngine
Vendor PortabilityCode tied exclusively to Stripe SDK.Switch between Stripe, PayPal, or Adyen without breaking domain logic.
Double-Charge RiskNetwork retries can double-charge users without idempotency keys.Automatic UUID idempotency key injection on all payment mutations.
PCI ComplianceHigh risk of handling raw credit card data on backend.100% tokenized flow. Backend handles only payment tokens & intent IDs.

5. Practical Usage Guide & Extended Code Examples​

5.1 Creating a Payment Intent​

use ferrox_integrations::payments::{PaymentEngine, PaymentIntentRequest, Currency};

pub async fn initialize_checkout(
customer_id: &str,
amount_in_cents: i64,
) -> Result<String, PaymentError> {
let payment_engine = PaymentEngine::stripe_from_env()?;

let intent = payment_engine
.create_payment_intent(PaymentIntentRequest {
customer_id: customer_id.to_string(),
amount: amount_in_cents,
currency: Currency::USD,
description: Some("Subscription Renewal".to_string()),
idempotency_key: Some(format!("pay_intent_{}_{}", customer_id, amount_in_cents)),
})
.await?;

Ok(intent.client_secret)
}

6. Anti-Patterns: How NOT to Use It​

[!CAUTION] Anti-Pattern 1: Fulfilling Orders before Webhook Verification Never grant user access or fulfill physical orders based solely on client-side frontend redirect callbacks. Always wait for cryptographically verified backend webhook events (payment_intent.succeeded).


7. Pro-Tips & Best Practices​

[!TIP] Pro-Tip 1: Idempotency Key Design Generate deterministic idempotency keys based on entity order IDs (idempotency_key = format!("order_{}", order.id)) to guarantee retried requests return the original charge object without duplicating billing.