// ADR 0009: pago real via ePayco (PSE) -- tercer proveedor evaluado // (PayU LatAm descartado porque Rapyd compró ese negocio en 2023 y el // registro real quedó con credenciales incompatibles; Rapyd descartado // por costo). ePayco: 100% de Davivienda desde 2024, comisión PSE más // baja para el volumen real del usuario, tarifa preferencial con cuenta // Davivienda. Módulo custom del backend // (workloads/commerce-backend/src/modules/epayco-pse). Archivo separado // de medusa-cart.ts por el mismo motivo que ese archivo esta separado de // medusa.ts -- reutiliza su mismo fetch helper (medusaCartFetch), no // duplica la logica de headers/no-store. import { medusaCartFetch } from "./medusa-cart"; export interface PseBank { pseCode: string; description: string; } // "0" persona natural, "1" persona juridica -- valores reales que espera // ePayco (campo type_person), no un codigo propio nuestro. export type PseUserType = "0" | "1"; export type PseDocType = "CC" | "CE" | "NIT" | "TI" | "PP"; export interface PsePaymentInput { bankCode: string; userType: PseUserType; docType: PseDocType; docNumber: string; firstName: string; lastName: string; email: string; phone: string; city: string; } // "pp_{identifier}_{id}" -- convencion de Medusa, confirmada contra el // codigo fuente de @medusajs/payment (identifier "epayco" + id "pse" en // medusa-config.ts). const PSE_PROVIDER_ID = "pp_epayco_pse"; export async function getPseBanks(): Promise { const data = await medusaCartFetch("/store/payments/pse-banks"); const banks = (data.banks as Array<{ pseCode?: string; description?: string }> | undefined) ?? []; return banks .filter((bank): bank is { pseCode: string; description: string } => Boolean(bank.pseCode && bank.description)) .map((bank) => ({ pseCode: bank.pseCode, description: bank.description })); } export async function createPaymentCollection(cartId: string): Promise { const data = await medusaCartFetch("/store/payment-collections", { method: "POST", body: { cart_id: cartId }, }); const collection = data.payment_collection as { id: string }; return collection.id; } export async function createPsePaymentSession(paymentCollectionId: string, input: PsePaymentInput): Promise { const data = await medusaCartFetch(`/store/payment-collections/${paymentCollectionId}/payment-sessions`, { method: "POST", body: { provider_id: PSE_PROVIDER_ID, data: { bank_code: input.bankCode, user_type: input.userType, doc_type: input.docType, doc_number: input.docNumber, first_name: input.firstName, last_name: input.lastName, email: input.email, phone: input.phone, city: input.city, }, }, }); const collection = data.payment_collection as { payment_sessions?: Array<{ provider_id: string; data?: { bank_url?: string } }>; }; const session = collection.payment_sessions?.find((paymentSession) => paymentSession.provider_id === PSE_PROVIDER_ID); const bankUrl = session?.data?.bank_url; if (!bankUrl) { throw new Error("ePayco no devolvió la URL del banco para completar el pago."); } return bankUrl; } // El carrito se completa (orden creada en estado "awaiting") ANTES de // redirigir al banco -- authorizePayment del modulo PSE devuelve // "pending_authorization" a proposito, que es justo el mecanismo de // Medusa para esto (ver AbstractPaymentProvider). La confirmacion real // llega despues via el webhook nativo de Medusa, no por esta llamada. export async function completeCart(cartId: string): Promise { const data = await medusaCartFetch(`/store/carts/${cartId}/complete`, { method: "POST" }); if (data.type === "cart") { const error = (data as { error?: { message?: string } }).error; throw new Error(error?.message || "No se pudo completar el pedido."); } }