Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Stripe

Stripe payment processing for cards, Apple Pay, Google Pay, and ACH transfers

Stripe Integration

SDK

GitHubstripe/stripe-node

4.4K896

Stripe is the primary payment processor for online card payments, Apple Pay, Google Pay, and ACH transfers. The integration uses the Stripe PaymentIntents API via a provider adapter pattern.

Architecture

Stripe is one of three payment providers behind the PaymentProviderAdapter interface. The adapter is located at lib/payments/providers/stripe.ts and uses a singleton Stripe client from lib/stripe.ts.

Checkout -> Provider Registry -> StripeAdapter -> Stripe PaymentIntents API

The adapter handles these payment method types:

MethodType Constant
Credit/Debit CardsCARD
ACH Bank TransferACH
Apple PayAPPLE_PAY
Google PayGOOGLE_PAY

Environment Variables

All Stripe keys must be set before the payment system will function. The application throws on startup if STRIPE_SECRET_KEY is missing.

VariableDescriptionRequired
STRIPE_SECRET_KEYStripe secret API key (sk_live or sk_test)Yes
STRIPE_WEBHOOK_SECRETWebhook endpoint signing secret (whsec_)Yes
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYPublic key for client-side ElementsYes

Setup

Install the Stripe SDK

The project uses the stripe npm package. It is already listed in package.json.

npm install stripe

Configure environment variables

Add your Stripe keys to .env.local:

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

Set up the webhook endpoint

Register https://your-domain.com/api/webhooks/stripe in the Stripe Dashboard under Developers > Webhooks. Subscribe to these events:

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • payment_intent.canceled
  • charge.refunded

Stripe Client

The singleton client is initialized in lib/stripe.ts with retry and timeout configuration:

stripeClient = new Stripe(secretKey, {
  apiVersion: '2025-10-29.clover',
  maxNetworkRetries: 2,
  timeout: 30000, // 30 seconds
})

Access it via getStripe(). The function throws if no secret key is configured.

Payment Flow

Creating a Payment

The StripeAdapter.createPayment() method creates a PaymentIntent with order metadata, shipping address, and optional future usage setup:

const params = {
  amount: request.amount,       // in cents
  currency: request.currency,
  receipt_email: request.customerEmail,
  metadata: {
    orderId: request.orderId,
    orderNumber: request.orderNumber,
    customerName: request.customerName,
  },
}

The response includes a clientSecret that the frontend uses to confirm payment with Stripe Elements.

Confirming a Payment

confirmPayment() retrieves the PaymentIntent status from Stripe and maps it to the unified status enum:

Stripe StatusMapped Status
requires_payment_methodREQUIRES_CONFIRMATION
requires_actionREQUIRES_ACTION
processingPROCESSING
succeededSUCCEEDED
OtherFAILED

Refunds

The adapter supports full and partial refunds via stripe.refunds.create(). Reason codes map to Stripe's allowed values: duplicate, fraudulent, or requested_by_customer.

Webhook Handler

Route: app/api/webhooks/stripe/route.ts

The webhook handler performs these operations:

  1. Signature verification using stripe.webhooks.constructEvent()
  2. Idempotency check via the WebhookEvent database table (prevents duplicate processing)
  3. Event routing based on event.type

Handled Events

EventAction
payment_intent.succeededMarks order as CONFIRMED, updates payment status to SUCCEEDED, deducts reserved inventory, sends confirmation email
payment_intent.payment_failedUpdates payment status to FAILED
payment_intent.canceledUpdates order to CANCELLED, payment to FAILED
charge.refundedUpdates order/payment status, creates refund record, restores inventory on full refunds, creates audit log

Partial refunds do not restore inventory automatically. An administrator must manually adjust inventory for partial refund scenarios.

Inventory Management

On payment_intent.succeeded, the handler calls deductReservedInventoryInTx() inside a Prisma transaction. Each item deduction is idempotent -- it checks for an existing ORDER_COMPLETION inventory transaction before proceeding.

After the transaction commits, checkAndUpdateAlerts() fires asynchronously to update low-stock alerts.

Customer Management

The adapter supports creating Stripe customers (cus_xxx) and listing/detaching saved card payment methods. This enables returning customers to use saved cards:

await adapter.createCustomer(email, name, { userId: user.id })
await adapter.listPaymentMethods(customerId)
await adapter.detachPaymentMethod(paymentMethodId)

Key Files

FilePurpose
lib/stripe.tsSingleton Stripe client
lib/payments/providers/stripe.tsStripeAdapter implementation
lib/payments/types.tsShared payment type definitions
lib/stripe/webhooks.tsWebhook event handler functions
app/api/webhooks/stripe/route.tsWebhook HTTP endpoint

How is this guide?

On this page