import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts' import { requireUser, authErrorResponse, type AuthError } from '../_shared/auth.ts' import { createServiceRoleClient } from '../_shared/quota.ts' import { calcSubscriptionPeriod, generateOrderId, getPaypleConfig, parsePaypleTimestamp, paypleAuth, paypleBilling, PaypleBillingError, paypleLookupBillingKey, payplePaymentEventDigest, payplePaymentEventId, payplePayerNumber, PaypleConfigurationError, TIER_GOODS_NAME, TIER_PRICE, } from '../_shared/payple.ts' interface CheckoutRequest { payer_id?: unknown tier?: unknown idempotency_key?: unknown } interface OperationReservation { created?: boolean operation_id?: string state?: string reason?: string } interface ApplyResult { applied?: boolean duplicate?: boolean reason?: string } function jsonResponse(body: Record, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } function validIdempotencyKey(value: unknown): value is string { return typeof value === 'string' && /^[A-Za-z0-9._:-]{12,160}$/.test(value) } Deno.serve(async (req: Request) => { const preflight = handleCorsPreflightRequest(req) if (preflight) return preflight if (req.method !== 'POST') return jsonResponse({ error: 'method_not_allowed' }, 405) let operationId: string | null = null let providerOrderId: string | null = null let externalChargeCompleted = false const serviceClient = createServiceRoleClient() try { const user = await requireUser(req) const body = await req.json() as CheckoutRequest if ( typeof body.payer_id !== 'string' || body.payer_id.length < 8 || body.payer_id.length > 255 || (body.tier !== 'pro' && body.tier !== 'pro_plus') || (body.idempotency_key !== undefined && !validIdempotencyKey(body.idempotency_key)) ) { return jsonResponse({ error: 'invalid_request' }, 400) } // Configuration is validated before reserving state or contacting Payple. // There are deliberately no bundled/test credential fallbacks. const config = getPaypleConfig() const idempotencyKey = body.idempotency_key ?? `payple-checkout:${crypto.randomUUID()}` const orderId = generateOrderId(user.id) providerOrderId = orderId const { data: reservationData, error: reservationError } = await serviceClient.rpc( 'reserve_payment_provider_operation', { p_user_id: user.id, p_provider: 'payple', p_operation_type: 'checkout', p_requested_tier: body.tier, p_idempotency_key: idempotencyKey, p_provider_order_id: orderId, // Bind the reserved order to this billing key before any charge. The // PUSERINFO lookup below still verifies its authenticated user owner. p_provider_resource_id: body.payer_id, }, ) if (reservationError) throw new Error('payment_reservation_failed') const reservation = reservationData as OperationReservation | null if (!reservation?.created || typeof reservation.operation_id !== 'string') { return jsonResponse({ error: reservation?.reason ?? 'payment_operation_in_progress', state: reservation?.state ?? 'rejected', }, 409) } operationId = reservation.operation_id const price = TIER_PRICE[body.tier] const goodsName = TIER_GOODS_NAME[body.tier] const billingKeyAuth = await paypleAuth(config, { payWork: 'PUSERINFO' }) const billingKey = await paypleLookupBillingKey(config, billingKeyAuth, body.payer_id) const expectedPayerNumbers = [user.id, await payplePayerNumber(user.id)] if ( billingKey.PCD_PAY_RST !== 'success' || billingKey.PCD_PAYER_ID !== body.payer_id || !billingKey.PCD_PAYER_NO || !expectedPayerNumbers.includes(billingKey.PCD_PAYER_NO) ) { throw new Error('payple_billing_key_owner_mismatch') } const auth = await paypleAuth(config, { simpleFlag: true }) const billingResult = await paypleBilling(config, auth, { payerId: body.payer_id, amount: price, orderId, goodsName, }) externalChargeCompleted = true const { error: chargedError } = await serviceClient.rpc('mark_payment_provider_operation', { p_operation_id: operationId, p_state: 'charged', p_external_reference: billingResult.PCD_PAY_OID || orderId, p_error_code: null, }) if (chargedError) throw new Error('payment_operation_update_failed') if ( billingResult.PCD_PAY_OID !== orderId || billingResult.PCD_PAY_TOTAL !== String(price) || (billingResult.PCD_PAYER_ID && billingResult.PCD_PAYER_ID !== body.payer_id) ) { throw new Error('payple_charge_response_mismatch') } const eventDate = billingResult.PCD_PAY_TIME ? parsePaypleTimestamp(billingResult.PCD_PAY_TIME) : new Date() const { start, end } = calcSubscriptionPeriod(eventDate) const { data: applyData, error: applyError } = await serviceClient.rpc( 'apply_payment_provider_event', { p_user_id: user.id, p_provider: 'payple', p_event_id: payplePaymentEventId(orderId), p_event_created_at: eventDate.toISOString(), p_event_type: 'payment.completed', p_payload_digest: await payplePaymentEventDigest({ orderId, payerId: body.payer_id, payType: 'card', amount: price, }), p_provider_resource_id: body.payer_id, p_tier: body.tier, p_status: 'active', p_entitled: true, p_current_period_start: start, p_current_period_end: end, p_cancel_at: null, p_auto_renewing: true, p_provider_customer_id: body.payer_id, p_provider_order_id: orderId, p_store_product_id: null, p_store_purchase_id: null, p_operation_id: operationId, }, ) if (applyError) throw new Error('entitlement_persistence_failed') const applied = applyData as ApplyResult | null if (!applied?.applied && !applied?.duplicate) { return jsonResponse({ error: 'payment_requires_reconciliation', reason: applied?.reason ?? 'entitlement_not_applied', order_id: orderId, }, 409) } return jsonResponse({ success: true, tier: body.tier, order_id: orderId, amount: price, }) } catch (error) { const chargeOutcomeUnknown = error instanceof PaypleBillingError && !error.definitive if (operationId && chargeOutcomeUnknown) { await serviceClient.rpc('mark_payment_provider_operation', { p_operation_id: operationId, p_state: 'external_created', p_external_reference: providerOrderId, p_error_code: null, }) } else if (operationId && !externalChargeCompleted) { await serviceClient.rpc('mark_payment_provider_operation', { p_operation_id: operationId, p_state: 'failed', p_external_reference: null, p_error_code: error instanceof PaypleConfigurationError ? 'payple_not_configured' : 'payple_checkout_failed', }) } if (error && typeof error === 'object' && 'status' in error && 'message' in error) { return authErrorResponse(error as AuthError, corsHeaders) } if (error instanceof PaypleConfigurationError) { return jsonResponse({ error: error.code }, 503) } return jsonResponse({ error: externalChargeCompleted || chargeOutcomeUnknown ? 'payment_requires_reconciliation' : 'payple_checkout_failed', }, externalChargeCompleted || chargeOutcomeUnknown ? 409 : 502) } })