Traducción de
docs/en/providers/adding-a-provider.md, que es el original. Si discrepan, manda el inglés.
Lee Contratos de proveedor, la especificación oficial del proveedor y las reglas de arquitectura antes de crear ficheros. El plugin de otro proveedor es un ejemplo de implementación, nunca la fuente de la verdad para los nombres de campo o el comportamiento de uno nuevo.
Un proveedor es un directorio bajo apps/api/src/providers/<id>/ y una fila en
apps/api/src/core/providers/plugin-registry.ts. Nada más en el código aprende
su nombre — ni un switch, ni un if (provider === …). Si te encuentras
editando el núcleo para añadir un proveedor, la costura está en el sitio
equivocado, y el arreglo es moverla, no añadir la rama.
Un plugin es un objeto plano sin dependencias inyectadas. No puede llegar a la
base de datos, al libro mayor ni a la cola, y ésa es la forma que demuestra que la
frontera aguanta: un plugin que necesitara el EntityManager estaría haciendo el
trabajo del núcleo.
#Las dos mitades
Las declaraciones son datos. Lo que un proveedor es: sus rutas, las palabras que usa para cada estado, cómo firma un webhook, qué cobra. Eso se puede describir.
El comportamiento es código. validate, toCanonical, toWire. Traducen
entre el cable del proveedor y el modelo canónico, y no pueden ser configuración
— un toWire editable es un generador de mentiras, y la fidelidad es el producto.
#Qué declara un plugin
| Campo | Qué es |
|---|---|
id, displayName |
El prefijo de URL, y lo que enseña la consola |
routes |
Método y patrón de ruta → operación canónica, literal de la documentación del proveedor |
resource |
Cómo se llama su recurso y cómo se acuñan sus identificadores |
idempotency |
La cabecera que lee, y qué operaciones la exigen |
credentials |
Cómo acuña y cómo reconoce una credencial |
statuses |
Su palabra propia para cada estado canónico |
errors |
Su forma propia para cada negativa canónica — ver abajo |
webhooks |
Sus asuntos, la forma del cuerpo, y cómo firma y verifica |
checkout |
La forma de URL de cara al comprador, y los métodos que ofrece |
ledger |
Comisiones, periodo de retención, monedas, y si una devolución devuelve la comisión |
successStatus |
Códigos HTTP opcionales por operación canónica |
requestPatch, validateResource |
Mapeo puro de datos persistibles y validación del recurso existente; escribe el núcleo |
publicDocuments |
Recursos públicos estáticos opcionales, como el certificado local de PayPal |
credentials.exchange declara opcionalmente OAuth de credenciales de cliente.
El checkout puede separar aprobación y pago y declarar enlaces de retorno y
cancelación. El contrato actual está en ProviderPlugin, en
apps/api/src/core/providers/provider-plugin.ts, e incluye payment.update
y el estado canónico approved.
statuses y errors son totales, y la suite de conformidad lo comprueba. Un
estado o una negativa que el plugin no sepa expresar es uno que llega a la
integración con las palabras del emulador en vez de las del proveedor — y una
integración ramifica sobre las dos.
#Errores
El núcleo lanza un ProviderError con uno de los CANONICAL_ERRORS; tu
errors.shape() lo convierte en el estado y el cuerpo de tu proveedor. La
pasarela hace eso una vez, a la salida.
export const yourErrors: ErrorVocabulary = {
shape(detail) {
const mapped = CODES[detail.code];
return { statusCode: mapped.status, body: { /* la forma de tu proveedor */ } };
},
};
Tu plugin decide cómo se llama una negativa. Nunca puede decidir si la hay: un plugin que pudiera sería capaz de hacer que el emulador acepte algo que el proveedor real rechaza.
detail.param nombra el campo culpable cuando lo hay. Úsalo — es como una
integración resalta la entrada que estaba mal.
#Los pasos
- Lee la documentación oficial y guarda lo que leas en
contracts/<id>/. Rutas, cabeceras, nombres de campo y vocabularios de estado salen de la especificación del proveedor, nunca de otro plugin. - Crea
apps/api/src/providers/<id>/con el plugin y sus declaraciones. Separa los vocabularios en sus propios ficheros —<id>.errors.ts,<id>.webhooks.ts,<id>.wire.ts— para que el fichero del plugin siga siendo legible. - Regístralo: añádelo a
PLUGINSenplugin-registry.ts. Ése es todo el registro. - Corre la suite de conformidad. Está parametrizada por plugin, así que a un proveedor nuevo se le exige todo lo que a los que ya están, sin escribir una prueba por proveedor.
- Añade pruebas con el SDK oficial donde el SDK admita una URL base propia. El de Stripe acepta host y puerto; el de Mercado Pago fija el suyo y no se puede redirigir, cosa que se documenta en vez de rodearse.
- Añade la mitad de cara al comprador en
apps/checkout/providers/<id>/cuando el proveedor tenga superficie de checkout. El flujo y la identidad visual se quedan ahí; enapps/checkout/app/components/sólo va lo neutral. Añade la forma de URL real a la resolución de rutas y de kits del checkout, sin importar código del backend. - Escribe
docs/en/fidelity/<id>.mdy di claramente qué no se emula. Una carencia conocida es una decisión de diseño; una carencia desconocida es una trampa. La consola lee ese documento y le enseña esas carencias a quien opera, así que no es papeleo. - Corre
npm run typecheck,npm run lint,npm test,npm run buildynpm run smoke. Si el proveedor cruza la frontera del checkout o del worker, levanta también la pila de Compose y correnpm run smoke:stack.
#Una frontera declarada está permitida
Un proveedor sin rutas es una frontera declarada, y se le exige todo menos tener implementación: sigue declarando sus estados, sus errores, sus webhooks y su contabilidad. Un marcador de posición que no pueda hacerlo es un marcador escondiendo trabajo que pagará el tercer proveedor.
implemented no es algo que se escriba. Lo concede la suite de conformidad al
pasar contra un plugin que enruta; la tabla gateways no puede ascender a nadie.
#Iconos de las pasarelas
Reutiliza los SVG junto a los llms. Para nuevas pasarelas, sigue la búsqueda por keyword de Iconos de las pasarelas. Mantén el nombre visible junto al icono en la consola y el checkout.