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

Payment Architecture

Multi-provider payment system architecture covering Stripe, PayPal, and Square integration for online and POS payments

Payment Architecture

Multi-provider payment system architecture for Jose Madrid Salsa, covering online payments and point-of-sale (POS) operations.

Overview

The platform uses a provider-agnostic payment architecture that routes each payment request to the correct processor based on the payment method type. This allows the system to support:

  • Online payments via Stripe (cards, ACH, Apple Pay, Google Pay)
  • Online payments via PayPal (PayPal wallet)
  • In-person payments via Square (terminal, POS)

All providers implement a common PaymentProviderAdapter interface, registered in a central registry that resolves the correct adapter at runtime.

                    +-------------------+
                    | Checkout / POS    |
                    | (method type)     |
                    +--------+----------+
                             |
                    +--------v----------+
                    | Provider Registry |
                    | METHOD_PROVIDER   |
                    | _MAP lookup       |
                    +--------+----------+
                             |
              +--------------+--------------+
              |              |              |
     +--------v---+  +------v-----+  +-----v------+
     |   Stripe   |  |   PayPal   |  |   Square   |
     |  Adapter   |  |  Adapter   |  |  Adapter   |
     +------------+  +------------+  +------------+
     | CARD       |  | PAYPAL     |  | SQUARE_    |
     | ACH        |  |            |  | TERMINAL   |
     | APPLE_PAY  |  |            |  |            |
     | GOOGLE_PAY |  |            |  |            |
     +------------+  +------------+  +------------+

Provider Abstraction Layer

File: lib/payments/types.ts

Core Types

PaymentProvider

'STRIPE' | 'SQUARE' | 'PAYPAL'

PaymentMethodType

'CARD' | 'ACH' | 'APPLE_PAY' | 'GOOGLE_PAY' | 'PAYPAL' | 'SQUARE_TERMINAL'

PaymentChannel

'ONLINE' | 'POS'

Request / Response Types

TypePurposeKey Fields
CreatePaymentRequestInitiate a paymentamount (cents), currency, orderId, orderNumber, customerEmail, customerName, methodType, channel, shippingAddress, setupFutureUsage
PaymentResultPayment creation resultprovider, providerPaymentId, clientSecret (Stripe), approvalUrl (PayPal), status
RefundRequestInitiate a refundproviderPaymentId, amount (cents, optional for partial), reason
RefundResultRefund resultprovider, providerRefundId, amount, status
CustomerResultCustomer creation resultproviderId, provider
SavedPaymentMethodStored payment methodid, provider, type, brand, last4, expMonth, expYear

PaymentResult Status Values

StatusDescription
REQUIRES_ACTIONCustomer action needed (3D Secure, redirect)
REQUIRES_CONFIRMATIONServer-side confirmation needed
PROCESSINGPayment is being processed
SUCCEEDEDPayment completed successfully
FAILEDPayment failed

Adapter Pattern

File: lib/payments/types.ts

Every payment provider implements the PaymentProviderAdapter interface:

interface PaymentProviderAdapter {
  readonly provider: PaymentProvider

  createPayment(request: CreatePaymentRequest): Promise<PaymentResult>
  confirmPayment(providerPaymentId: string): Promise<PaymentResult>
  refund(request: RefundRequest): Promise<RefundResult>
  createCustomer(email, name, metadata?): Promise<CustomerResult>
  listPaymentMethods(customerId: string): Promise<SavedPaymentMethod[]>
  detachPaymentMethod(paymentMethodId: string): Promise<void>
}

Adapter Responsibilities

MethodBehavior
createPaymentCreates a payment with the provider and returns a result with provider-specific data (client secret for Stripe, approval URL for PayPal)
confirmPaymentServer-side confirmation of a payment (e.g., after 3D Secure)
refundProcesses full or partial refund. Amount in cents; omit for full refund
createCustomerCreates a customer record with the provider for saved payment methods
listPaymentMethodsLists saved payment methods for a customer
detachPaymentMethodRemoves a saved payment method

Method-to-Provider Mapping

File: lib/payments/registry.ts

Each payment method type maps to exactly one provider:

Payment MethodProviderChannelDescription
CARDStripeOnlineCredit/debit card via Stripe Elements
ACHStripeOnlineACH bank transfer
APPLE_PAYStripeOnlineApple Pay via Stripe Express Checkout
GOOGLE_PAYStripeOnlineGoogle Pay via Stripe Express Checkout
PAYPALPayPalOnlinePayPal wallet (redirect-based flow)
SQUARE_TERMINALSquarePOSSquare Terminal for in-person payments

When a payment request specifies a methodType, the registry resolves the correct adapter automatically via getProviderForMethod().

Provider Registry

File: lib/payments/registry.ts

The registry is a singleton Map<PaymentProvider, PaymentProviderAdapter> with these operations:

FunctionDescription
registerProvider(adapter)Register an adapter instance at app startup
getProvider(provider)Get adapter by provider name. Throws if not registered
getProviderForMethod(methodType)Look up provider via METHOD_PROVIDER_MAP, then get adapter
getRegisteredProviders()List all registered provider names

Registration Pattern

Adapters should be registered during app initialization:

import { registerProvider } from '@/lib/payments/registry'
import { StripeAdapter } from '@/lib/payments/adapters/stripe'

registerProvider(new StripeAdapter())

Database Schema

The Payment model (table: payments) supports all three providers with unified and provider-specific fields.

Unified Fields

ColumnTypeDescription
idString (CUID)Primary key
amountIntAmount in cents
currencyStringCurrency code (default: usd)
statusPaymentStatusPENDING, SUCCEEDED, FAILED, REFUNDED, PARTIALLY_REFUNDED, CANCELED
paymentMethodStringMethod descriptor (e.g., card, paypal)
orderIdStringFK to Order
providerPaymentProviderSTRIPE, SQUARE, or PAYPAL (default: STRIPE)
providerPaymentIdStringUnified provider payment reference
channelPaymentChannelONLINE or POS (default: ONLINE)
methodTypeStringCARD, ACH, APPLE_PAY, etc.
metadataJSONArbitrary provider metadata
paidAtDateTimeWhen payment was confirmed

Provider-Specific Fields

ColumnProviderDescription
stripePaymentIntentIdStripePaymentIntent ID (unique)
stripeCheckoutSessionIdStripeCheckout Session ID (unique)
stripeCustomerIdStripeStripe Customer ID
squarePaymentIdSquareSquare Payment ID (unique)
squareTerminalCheckoutIdSquareTerminal Checkout ID (unique)
paypalOrderIdPayPalPayPal Order ID (unique)
paypalCaptureIdPayPalPayPal Capture ID (unique)

Indexes

  • orderId - Fast lookup by order
  • status - Filter by payment status
  • provider - Filter by provider

Provider Configuration

Stripe

VariableRequiredDescription
STRIPE_SECRET_KEYYesServer-side secret key (sk_live_* or sk_test_*)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYYesClient-side publishable key (pk_live_* or pk_test_*)
STRIPE_WEBHOOK_SECRETYesWebhook signing secret (whsec_*)

Alternative: STRIPE_SECRET accepted as fallback for STRIPE_SECRET_KEY.

SDK configuration (from lib/stripe.ts):

  • API version: 2025-10-29.clover
  • Max retries: 2
  • Timeout: 30 seconds

PayPal

VariableRequiredDescription
PAYPAL_CLIENT_IDYesPayPal REST API client ID
PAYPAL_CLIENT_SECRETYesPayPal REST API client secret
PAYPAL_SANDBOXNoSet to false for production; defaults to sandbox
PAYPAL_RETURN_URLNoCustomer return URL after PayPal approval (defaults to {NEXT_PUBLIC_BASE_URL}/checkout/paypal/return)
PAYPAL_CANCEL_URLNoCustomer cancel URL (defaults to {NEXT_PUBLIC_BASE_URL}/checkout/paypal/cancel)
PAYPAL_WEBHOOK_IDYes (for webhooks)PayPal webhook ID for signature verification
NEXT_PUBLIC_PAYPAL_CLIENT_IDYes (for client)Client-side PayPal client ID for the PayPal JS SDK

SDK: @paypal/checkout-server-sdk (server), @paypal/react-paypal-js (client).

Square

VariableRequiredDescription
SQUARE_ACCESS_TOKENYesSquare API access token
SQUARE_LOCATION_IDYesSquare location ID for POS transactions
SQUARE_ENVIRONMENTNosandbox (default) or production
SQUARE_TERMINAL_DEVICE_IDFor POSSquare Terminal device ID
NEXT_PUBLIC_SQUARE_APP_IDYes (for client)Client-side Square application ID
NEXT_PUBLIC_SQUARE_LOCATION_IDYes (for client)Client-side Square location ID

SDK: react-square-web-payments-sdk (client). Server adapter not yet implemented.

Stripe (Online Payments)

Stripe is the fully implemented provider, handling all online payment methods.

Supported Methods

  • Credit/debit cards (via CardElement)
  • Apple Pay (via ExpressCheckoutElement)
  • Google Pay (via ExpressCheckoutElement)
  • ACH bank transfers (type definition ready)

Flow

See docs/payment-integration.md for the complete Stripe payment flow, webhook handling, and refund process.

PayPal (Online Payments)

PayPal uses a redirect/popup-based flow where the customer authorizes payment through PayPal, then the server captures it.

File: lib/payments/providers/paypal.ts

Adapter Implementation

The PayPalAdapter implements the full PaymentProviderAdapter interface:

MethodBehavior
createPaymentCreates a PayPal Order via the Orders API with intent: CAPTURE. Returns approvalUrl for customer redirect. Amounts are converted from cents to dollars (amount / 100).
confirmPaymentChecks the PayPal Order status. If APPROVED, captures the payment via the Orders Capture API.
refundRefunds a captured payment via CapturesRefundRequest. Supports partial refunds by specifying an amount.
createCustomerNo-op (returns empty providerId). PayPal identifies customers via their PayPal account during checkout.
listPaymentMethodsReturns empty array. PayPal Vault API integration is not yet implemented.
detachPaymentMethodNo-op.

Registration

The PayPal adapter is conditionally registered at app startup in lib/payments/index.ts:

if (process.env.PAYPAL_CLIENT_ID && process.env.PAYPAL_CLIENT_SECRET) {
  registerProvider(new PayPalAdapter())
}

Client-Side Flow

  1. PayPalProvider wraps the checkout page with PayPalScriptProvider (from @paypal/react-paypal-js)
  2. Customer selects PayPal or Venmo in the PaymentMethodSelector
  3. PayPalButton or VenmoButton renders the corresponding PayPalButtons component
  4. On click, createOrder calls POST /api/checkout/paypal/create-order to create a PayPal Order
  5. Customer authorizes payment in the PayPal popup
  6. On approval, onApprove calls POST /api/checkout/paypal/capture-order to capture the payment
  7. On success, the client redirects to the order confirmation page

Venmo

Venmo is enabled as a funding source within the PayPal SDK (enableFunding: 'venmo' in PayPalProvider). The VenmoButton component uses the same PayPal order creation and capture endpoints with fundingSource: FUNDING.VENMO.

Webhook Handling

File: app/api/webhooks/paypal/route.ts

PayPal webhook events are received at POST /api/webhooks/paypal.

Signature Verification

  1. Extracts PayPal signature headers (paypal-transmission-id, paypal-transmission-sig, etc.)
  2. Obtains an access token via the PayPal OAuth2 API
  3. Calls PayPal's webhook verification endpoint (/v1/notifications/verify-webhook-signature)
  4. Requires PAYPAL_WEBHOOK_ID environment variable

Handled Events

EventAction
PAYMENT.CAPTURE.COMPLETEDOrder -> CONFIRMED/SUCCEEDED, creates Payment record with paypalOrderId and paypalCaptureId, sends confirmation email
PAYMENT.CAPTURE.REFUNDEDCreates Refund record, updates Payment status. Full refund: Order -> REFUNDED. Partial: Payment -> PARTIALLY_REFUNDED

Idempotency

Uses the same WebhookEvent table as Stripe, with provider: 'PAYPAL' and providerEventId tracking.

Square (POS and Cash App Pay)

Square powers in-person payment processing via Square Terminal and online payments via Cash App Pay.

Cash App Pay (Online)

Files: components/checkout/CashAppButton.tsx, components/checkout/SquareProvider.tsx

Cash App Pay is available when NEXT_PUBLIC_SQUARE_APP_ID and NEXT_PUBLIC_SQUARE_LOCATION_ID are configured.

Client-Side Flow

  1. SquareProvider wraps the Cash App button with Square's PaymentForm from react-square-web-payments-sdk
  2. Customer selects Cash App Pay in the PaymentMethodSelector
  3. CashAppButton renders Square's CashAppPay component
  4. On authorization, the PaymentForm tokenizes the payment and calls POST /api/checkout/square/process-payment
  5. Server processes the payment using the Square Payments API

SquareProvider Configuration

The SquareProvider component initializes the Square Web Payments SDK:

  • applicationId: From NEXT_PUBLIC_SQUARE_APP_ID
  • locationId: From NEXT_PUBLIC_SQUARE_LOCATION_ID
  • Payment request: countryCode: 'US', currencyCode: 'USD'

POS Hardware Setup (Planned)

ComponentPurpose
iPadRuns the POS admin interface (web-based)
Square TerminalAccepts card tap/dip/swipe and displays payment amount
Barcode ScannerScans product barcodes to add items to the POS order
Thermal PrinterPrints receipts after payment completion

Expected POS Flow (When Implemented)

  1. Operator scans product barcodes on iPad to build the order
  2. System creates an order in the database (channel: POS)
  3. Server calls createPayment() on Square adapter with channel: 'POS' and methodType: 'SQUARE_TERMINAL'
  4. Square adapter creates a Terminal Checkout via the Square Terminal API
  5. Square Terminal displays the payment amount to the customer
  6. Customer taps/inserts card on the Square Terminal
  7. Square sends payment confirmation
  8. Server marks order as paid, fires inventory deduction
  9. Thermal printer prints receipt

Square Terminal API Integration Points (Planned)

  • Create Terminal Checkout - Sends payment request to a specific terminal device
  • Get Terminal Checkout - Polls for payment status
  • Cancel Terminal Checkout - Cancels a pending terminal payment
  • Refund - Processes refund through Square

Payment Retry Flow

File: app/api/checkout/retry-payment/route.ts

Allows customers to retry failed payments on existing orders.

Endpoint: POST /api/checkout/retry-payment

Request

{
  "orderId": "clxxx..."
}

Eligibility

  • Payment status must be FAILED or PENDING
  • Order status must not be CANCELLED or REFUNDED
  • Authenticated user must own the order (if userId is set)

Retry Strategy

  1. If existing PaymentIntent is still retryable (requires_payment_method, requires_confirmation, requires_action), reuse its clientSecret
  2. If existing PaymentIntent is in a terminal state (canceled), create a new PaymentIntent
  3. If existing PaymentIntent succeeded, return error (already paid)
  4. Update order's stripePaymentId and reset paymentStatus to PENDING
  5. If order was cancelled due to payment failure, reset status to PENDING

Response

{
  "success": true,
  "clientSecret": "pi_xxx_secret_xxx",
  "orderId": "clxxx...",
  "amount": 42.99,
  "reused": true
}

Unified Payment API

File: app/api/payments/create/route.ts

A provider-agnostic endpoint for creating payments on existing orders.

Endpoint: POST /api/payments/create

Request

{
  "orderId": "clxxx...",
  "provider": "PAYPAL",
  "methodType": "PAYPAL",
  "channel": "ONLINE",
  "guestEmail": "guest@example.com"
}
FieldRequiredDescription
orderIdYesCUID of the order to pay
providerNoExplicit provider override (STRIPE, PAYPAL, SQUARE)
methodTypeNoPayment method type (used for provider routing via METHOD_PROVIDER_MAP)
channelNoONLINE (default) or POS
guestEmailNoRequired for guest orders (ownership verification)

Provider Resolution

The adapter is resolved in priority order:

  1. Explicit provider parameter
  2. methodType lookup via METHOD_PROVIDER_MAP
  3. Default: STRIPE

Ownership Verification

  • Authenticated users: user.id must match order.userId
  • Guest orders: guestEmail must match order.guestEmail (case-insensitive)

Response

{
  "success": true,
  "provider": "PAYPAL",
  "providerPaymentId": "5O190127TN364715T",
  "clientSecret": null,
  "approvalUrl": "https://www.paypal.com/checkoutnow?token=...",
  "status": "REQUIRES_ACTION"
}

Payment Methods API

File: app/api/payments/methods/route.ts

Lists all enabled payment methods and their providers.

Endpoint: GET /api/payments/methods

Response

{
  "success": true,
  "methods": [
    { "method": "CARD", "provider": "STRIPE", "enabled": true },
    { "method": "PAYPAL", "provider": "PAYPAL", "enabled": true }
  ],
  "providers": [
    { "provider": "STRIPE", "isActive": true, "testMode": true },
    { "provider": "PAYPAL", "isActive": true, "testMode": true }
  ]
}

Method availability is determined by:

  1. PaymentProviderConfig records in the database (if any exist)
  2. Fallback: all methods from registered providers (via METHOD_PROVIDER_MAP)

Payment Method Selector

File: components/checkout/PaymentMethodSelector.tsx

Client component that renders available payment methods as selectable radio buttons.

Available Methods

Method IDLabelProviderEnabled When
cardCredit / Debit CardStripeAlways
paypalPayPalPayPalNEXT_PUBLIC_PAYPAL_CLIENT_ID is set
venmoVenmoPayPalNEXT_PUBLIC_PAYPAL_CLIENT_ID is set
cashappCash App PaySquareNEXT_PUBLIC_SQUARE_APP_ID and NEXT_PUBLIC_SQUARE_LOCATION_ID are set
linkLink (by Stripe)StripeDisabled (coming soon)

Disabled methods are shown in a "Coming soon" section. If only one method is enabled, the selector is hidden.

Admin Payment Settings

File: app/admin/settings/payments/page.tsx

Server-rendered admin page showing the connection status of each payment provider.

Provider Status

Each provider shows one of three states:

  • Connected (green) - All required API keys are configured
  • Partial setup (yellow) - Some but not all keys are configured
  • Not configured (gray) - No keys configured; setup instructions shown

Settings API

File: app/api/admin/settings/payments/route.ts

MethodPermissionDescription
GETsettings:readLists all PaymentProviderConfig records (credentials stripped)
PUTsettings:writeUpserts provider config (active state, test mode, supported methods)

Validation Schemas

File: lib/validations/payment.ts

Zod schemas for payment operations (currently Stripe-specific):

SchemaPurpose
PaymentMethodSchemaCard details, billing details. Types: card, us_bank_account, link
CreatePaymentIntentSchemaamount, currency, orderId, customerEmail, shipping
ConfirmPaymentSchemapaymentIntentId (must start with pi_), paymentMethodId, orderId
UpdatePaymentStatusSchemaStatus enum: PENDING, PROCESSING, SUCCEEDED, FAILED, CANCELED
RefundRequestSchemaorderId, paymentIntentId, amount?, reason (duplicate/fraudulent/requested_by_customer)
StripeWebhookEventSchemaEvent ID (must start with evt_), type, data.object, created, livemode
PaymentMetadataSchemaorderId, orderNumber, customerName?, customerId?, notes? (max 500 chars)
PaymentAmountSchemaInteger, positive, max $999,999.99 (99999999 cents)
CreateStripeCustomerSchemaemail, name, phone?, address?, metadata?

Implementation Status

ComponentStatusNotes
Provider abstraction typesCompletelib/payments/types.ts
Provider registryCompletelib/payments/registry.ts
Method-to-provider mappingCompleteAll 6 methods mapped
Database schemaCompleteMulti-provider Payment model with provider-specific columns
Stripe adapterCompletelib/payments/providers/stripe.ts - full adapter pattern
PayPal adapterCompletelib/payments/providers/paypal.ts - Orders API, capture, refund
PayPal client componentsCompletePayPalButton, VenmoButton, PayPalProvider
PayPal webhooksCompleteapp/api/webhooks/paypal/route.ts - capture + refund events
Square adapter (server)Not startedTypes and DB columns ready
Square client (Cash App Pay)CompleteCashAppButton, SquareProvider via react-square-web-payments-sdk
POS interfaceNot startedDB schema supports POS channel
Payment method selectorCompletecomponents/checkout/PaymentMethodSelector.tsx
Unified payment APICompleteapp/api/payments/create/route.ts - provider-agnostic payment creation
Payment methods APICompleteapp/api/payments/methods/route.ts - lists enabled methods/providers
Admin payment settingsCompleteapp/admin/settings/payments/page.tsx - provider status dashboard
Admin settings APICompleteapp/api/admin/settings/payments/route.ts - CRUD for provider configs
Payment retryCompleteapp/api/checkout/retry-payment/route.ts

Key Files

Provider Abstraction

FilePurpose
lib/payments/types.tsProvider-agnostic payment types and adapter interface
lib/payments/registry.tsProvider registry and method-to-provider mapping
lib/payments/index.tsAuto-registration of adapters and public exports
lib/payments/providers/stripe.tsStripe adapter implementation
lib/payments/providers/paypal.tsPayPal adapter implementation

Stripe

FilePurpose
lib/stripe.tsStripe SDK client singleton
lib/stripe/types.tsStripe-specific type definitions
lib/stripe/webhooks.tsStripe webhook handler functions
app/api/webhooks/stripe/route.tsStripe webhook endpoint

PayPal

FilePurpose
app/api/webhooks/paypal/route.tsPayPal webhook endpoint with signature verification
components/checkout/PayPalProvider.tsxClient-side PayPal SDK wrapper
components/checkout/PayPalButton.tsxPayPal payment button component
components/checkout/VenmoButton.tsxVenmo payment button component (via PayPal SDK)

Square

FilePurpose
components/checkout/SquareProvider.tsxClient-side Square Web Payments SDK wrapper
components/checkout/CashAppButton.tsxCash App Pay button component

Unified Payment APIs

FilePurpose
app/api/payments/create/route.tsProvider-agnostic payment creation endpoint
app/api/payments/methods/route.tsLists enabled payment methods and providers
app/api/admin/settings/payments/route.tsAdmin API for provider configuration (GET/PUT)
app/admin/settings/payments/page.tsxAdmin UI for provider connection status

Checkout and Orders

FilePurpose
app/api/checkout/route.tsCheckout API (order + payment creation)
app/api/checkout/complete/route.tsOrder completion after payment
app/api/checkout/retry-payment/route.tsPayment retry for failed orders
app/api/admin/orders/[id]/refund/route.tsAdmin refund processing
components/checkout/PaymentMethodSelector.tsxPayment method selection UI
lib/validations/payment.tsZod validation schemas for payment operations
prisma/schema.prismaPayment model, PaymentProvider and PaymentChannel enums

How is this guide?

Edit on GitHub

Last updated on

On this page