Prompt

How do I set up a transaction processing API for auth and capture flows in a SaaS billing system?

Latest observation

Jul 17, 2026 · OpenAI APIWeb search: off

To set up a transaction processing API for authorization and capture flows in a SaaS billing system, design it around a clear payment lifecycle, idempotent endpoints, and strong state management.

1) Understand the payment flow

For card payments, the common flow is:

  1. Authorize — reserve funds on the customer’s card.
  2. Capture — finalize the charge and move the money.
  3. Optionally Void — cancel an authorization before capture.
  4. Optionally Refund — return money after capture.

For SaaS billing, this is useful when you:

  • want to validate a card before starting a subscription,
  • want to charge after usage is known,
  • want to separate subscription activation from actual payment settlement.

2) Core API resources

Use these main resources:

  • Customers
  • Payment methods
  • Payment intents / transactions
  • Authorizations
  • Captures
  • Refunds
  • Invoices / charges if you have billing-specific objects

A good abstraction is a Transaction or PaymentIntent that tracks the full lifecycle.

Example transaction states

  • created
  • requires_payment_method
  • authorized
  • partially_captured
  • captured
  • voided
  • failed
  • refunded

3) Suggested API endpoints

Create a payment intent

POST /v1/payment_intents

Request:

{
  "customer_id": "cus_123",
  "amount": 10000,
  "currency": "usd",
  "capture_method": "manual",
  "payment_method_id": "pm_123",
  "description": "Subscription activation"
}

Response:

{
  "id": "pi_123",
  "status": "requires_action",
  "amount": 10000,
  "currency": "usd",
  "capture_method": "manual"
}

Authorize payment

Depending on your design, authorization may happen automatically when creating the payment intent or via a separate endpoint:

POST /v1/payment_intents/{id}/authorize

Response:

{
  "id": "pi_123",
  "status": "authorized",
  "authorization_id": "auth_456",
  "authorized_amount": 10000,
  "expires_at": "2026-07-24T12:00:00Z"
}

Capture payment

POST /v1/payment_intents/{id}/capture

Request:

{
  "amount": 7500
}

Response:

{
  "id": "pi_123",
  "status": "partially_captured",
  "captured_amount": 7500,
  "remaining_authorized_amount": 2500
}

Void authorization

POST /v1/payment_intents/{id}/void

Refund captured payment

POST /v1/refunds

Request:

{
  "payment_intent_id": "pi_123",
  "amount": 2500
}

4) Handle SaaS-specific billing logic

Common SaaS use cases

  • Trial to paid conversion
    • Validate card at signup with a small authorization or zero-dollar verification.
  • Monthly subscription billing
    • Create invoice first, then authorize and capture at renewal.
  • Usage-based billing
    • Accumulate usage, then authorize/capture after rating.
  • Proration
    • Charge or refund difference when plan changes.

Subscription workflow example

  1. Customer subscribes.
  2. System creates invoice for first period.
  3. Payment API authorizes funds.
  4. Capture occurs immediately or at invoice finalization.
  5. Subscription becomes active once capture succeeds.

5) Design for idempotency

Every payment endpoint should support an idempotency key to prevent double charges due to retries.

Example header:

Idempotency-Key: 2c1b5a2c-3c8c-4f7d-9f13-1f2d7a9c7d11

Store the first successful response and return it for repeated requests with the same key.


6) State machine matters

Enforce valid transitions only.

Examples:

  • created -> authorized
  • authorized -> captured
  • authorized -> voided
  • captured -> refunded
  • authorized -> expired

Reject invalid transitions like:

  • capturing a voided authorization
  • refunding an uncaptured authorization
  • authorizing an already captured intent

This prevents inconsistent billing records.


7) Webhooks for asynchronous updates

Payment processors often respond asynchronously, especially with 3DS or delayed settlement.

Useful events:

  • payment_intent.authorized
  • payment_intent.captured
  • payment_intent.failed
  • payment_intent.voided
  • refund.created
  • chargeback.opened

Your API should:

  • publish webhook events,
  • verify webhook signatures,
  • allow replay protection,
  • make handlers idempotent.

8) Integrate with a PSP

Usually you should not build card processing yourself. Instead, integrate with a PSP like Stripe, Adyen, Braintree, or Checkout.com.

Your internal API can:

  • normalize payment state,
  • abstract provider differences,
  • keep billing code independent of a processor.

Map your states to the PSP’s primitives:

  • authorization
  • capture
  • refund
  • void

9) Security and compliance

Important requirements:

  • Do not store raw card data unless PCI compliant.
  • Use tokenized payment methods from the PSP.
  • Encrypt sensitive data at rest.
  • Use TLS everywhere.
  • Restrict API keys and scope them properly.
  • Log payment events without exposing PAN/CVV.
  • Support fraud checks and SCA/3DS where applicable.

10) Database model example

A simple schema might include:

payment_intents

  • id
  • customer_id
  • amount
  • currency
  • capture_method
  • status
  • provider
  • provider_payment_id
  • created_at
  • updated_at

payment_transactions

  • id
  • payment_intent_id
  • type (authorization, capture, void, refund)
  • amount
  • status
  • provider_reference
  • created_at

payment_events

  • id
  • provider_event_id
  • type
  • payload
  • processed_at

This gives you an audit trail.


11) Error handling

Standardize errors:

  • 400 invalid_request
  • 401 unauthorized
  • 402 payment_required
  • 409 invalid_state
  • 422 payment_method_declined
  • 429 rate_limited
  • 500 internal_error

Example:

{
  "error": {
    "code": "invalid_state",
    "message": "Cannot capture a payment that is not authorized."
  }
}

12) Minimal implementation strategy

If you want the simplest practical setup:

  1. Create a PaymentIntent record.
  2. Send request to PSP to authorize payment.
  3. Store PSP authorization ID and status.
  4. On capture, call PSP capture endpoint.
  5. Update local status from webhook or synchronous response.
  6. Use idempotency keys for every mutation.
  7. Build a state machine and audit log.

13) Example lifecycle

Full charge at signup

  • Create intent with capture_method=automatic
  • PSP authorizes and captures in one call
  • Mark subscription active

Manual capture for invoice

  • Create intent with capture_method=manual
  • Authorize when invoice is finalized
  • Capture after internal approval or usage finalization
  • Void if invoice is canceled before capture

If you want, I can also provide:

  1. a REST API spec for auth/capture/refund endpoints,
  2. a PostgreSQL schema,
  3. or a sample implementation in Node.js, Python, or Go.

Observation history

Unlock observation history

Create a free Obsurfable account to unlock historical responses, model comparisons, and deeper brand intelligence.

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, and Meta AI.