// Pure decision rules for the Payple checkout edge function. No IO lives // here: index.ts owns Supabase/Payple calls and asks these functions what to // do with each result, so the rules are unit-testable without a network. export type CheckoutTier = 'pro' | 'pro_plus' export interface CheckoutInput { payerId: string tier: CheckoutTier idempotencyKey: string | undefined } export function isValidIdempotencyKey(value: unknown): value is string { return typeof value === 'string' && /^[A-Za-z0-9._:-]{12,160}$/.test(value) } /** Returns null when the request body is not a valid checkout request. */ export function parseCheckoutRequest(body: unknown): CheckoutInput | null { if (!body || typeof body !== 'object') return null const record = body as Record const payerId = record.payer_id const tier = record.tier const idempotencyKey = record.idempotency_key if ( typeof payerId !== 'string' || payerId.length < 8 || payerId.length > 255 || (tier !== 'pro' && tier !== 'pro_plus') || (idempotencyKey !== undefined && !isValidIdempotencyKey(idempotencyKey)) ) { return null } return { payerId, tier, idempotencyKey } } export type ReservationDecision = | { kind: 'proceed'; operationId: string } | { kind: 'reject'; status: 409; body: { error: string; state: string } } /** * Interprets the reserve_payment_provider_operation result. Only a freshly * created operation may lead to an external charge; every other outcome * (idempotent replay, in-flight operation, other-provider ownership, or an * already active paid period — 'subscription_already_active') is a 409 with * the database reason passed through unchanged. */ export function decideReservation(data: unknown): ReservationDecision { const reservation = data && typeof data === 'object' ? data as Record : null if (reservation?.created === true && typeof reservation.operation_id === 'string') { return { kind: 'proceed', operationId: reservation.operation_id } } return { kind: 'reject', status: 409, body: { error: typeof reservation?.reason === 'string' ? reservation.reason : 'payment_operation_in_progress', state: typeof reservation?.state === 'string' ? reservation.state : 'rejected', }, } } export interface BillingKeyOwnerEvidence { PCD_PAY_RST: string PCD_PAYER_ID?: string PCD_PAYER_NO?: string } /** The billing key must be live and registered to the authenticated user. */ export function isBillingKeyOwnedBy( billingKey: BillingKeyOwnerEvidence, payerId: string, expectedPayerNumbers: readonly string[], ): boolean { return billingKey.PCD_PAY_RST === 'success' && billingKey.PCD_PAYER_ID === payerId && typeof billingKey.PCD_PAYER_NO === 'string' && billingKey.PCD_PAYER_NO.length > 0 && expectedPayerNumbers.includes(billingKey.PCD_PAYER_NO) } export interface ChargeEvidence { PCD_PAY_OID: string PCD_PAY_TOTAL: string PCD_PAYER_ID?: string } /** The synchronous charge response must describe exactly the reserved order. */ export function chargeMatchesOrder( charge: ChargeEvidence, expected: { orderId: string; amount: number; payerId: string }, ): boolean { return charge.PCD_PAY_OID === expected.orderId && charge.PCD_PAY_TOTAL === String(expected.amount) && (!charge.PCD_PAYER_ID || charge.PCD_PAYER_ID === expected.payerId) } export interface CheckoutFailureFacts { /** A reservation exists for this request. */ hasOperation: boolean /** Payple accepted the charge before the failure. */ externalChargeCompleted: boolean /** The charge call failed in a way that does not prove it was not taken. */ chargeOutcomeUnknown: boolean /** Payple is not configured on this deployment. */ configurationError: boolean } export interface CheckoutFailurePlan { /** How to mark the reserved operation, or null to leave it untouched. */ mark: | { state: 'external_created' } | { state: 'failed'; errorCode: 'payple_not_configured' | 'payple_checkout_failed' } | null /** HTTP status and error code returned to the caller (auth errors excepted). */ status: 409 | 502 | 503 error: string } export function planCheckoutFailure( facts: CheckoutFailureFacts, configurationErrorCode: string, ): CheckoutFailurePlan { let mark: CheckoutFailurePlan['mark'] = null if (facts.hasOperation && facts.chargeOutcomeUnknown) { mark = { state: 'external_created' } } else if (facts.hasOperation && !facts.externalChargeCompleted) { mark = { state: 'failed', errorCode: facts.configurationError ? 'payple_not_configured' : 'payple_checkout_failed', } } if (facts.configurationError) { return { mark, status: 503, error: configurationErrorCode } } const needsReconciliation = facts.externalChargeCompleted || facts.chargeOutcomeUnknown return { mark, status: needsReconciliation ? 409 : 502, error: needsReconciliation ? 'payment_requires_reconciliation' : 'payple_checkout_failed', } }