@crosspay-lb/js 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ Copyright (c) 2026 Crosspay Solutions S.A.S. Todos los derechos reservados.
2
+
3
+ Este software y su documentación (el "Software") son propiedad de
4
+ Crosspay Solutions S.A.S. ("Crosspay") y están protegidos por las leyes de
5
+ derechos de autor y los tratados internacionales aplicables.
6
+
7
+ 1. Uso permitido. Crosspay concede a sus clientes con una cuenta activa una
8
+ licencia limitada, no exclusiva, intransferible y revocable para usar el
9
+ Software, sin modificarlo, con el único fin de integrarse con los servicios
10
+ de Crosspay y mientras dure su relación comercial con Crosspay.
11
+
12
+ 2. Restricciones. Salvo autorización previa y por escrito de Crosspay, no se
13
+ permite: (a) copiar, modificar, traducir o crear obras derivadas del
14
+ Software; (b) distribuirlo, publicarlo, sublicenciarlo, venderlo o
15
+ ponerlo a disposición de terceros, salvo su carga en el sitio del cliente
16
+ como parte de la integración permitida; (c) aplicar ingeniería inversa,
17
+ descompilarlo o intentar obtener su código fuente, salvo en la medida en
18
+ que la ley aplicable lo permita expresamente; (d) usarlo con servicios de
19
+ pago distintos de los de Crosspay; ni (e) eliminar o alterar este aviso.
20
+
21
+ 3. Sin garantías. El Software se entrega "tal cual", sin garantías de ningún
22
+ tipo, expresas o implícitas, incluidas las de comerciabilidad, idoneidad
23
+ para un fin particular y no infracción.
24
+
25
+ 4. Limitación de responsabilidad. En ningún caso Crosspay será responsable de
26
+ daños directos, indirectos, incidentales o consecuentes derivados del uso
27
+ o de la imposibilidad de usar el Software, salvo lo que disponga la ley
28
+ aplicable de forma imperativa.
29
+
30
+ 5. Terminación. Esta licencia termina automáticamente si se incumple
31
+ cualquiera de sus condiciones o al terminar la relación comercial con
32
+ Crosspay. Al terminar, debe cesar todo uso del Software.
33
+
34
+ Contacto: willy@crosspaysolutions.com
package/README.md CHANGED
@@ -1,3 +1,27 @@
1
- # Temporary Holding Version
1
+ # @crosspay-lb/js
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Librería de navegador de Crosspay: formulario seguro de tarjeta (el número
4
+ nunca toca tu sitio), PSE y estado del pago. Sin dependencias.
5
+
6
+ ```bash
7
+ npm install @crosspay-lb/js
8
+ ```
9
+
10
+ ```ts
11
+ import { init } from '@crosspay-lb/js'
12
+
13
+ const cp = init({ environment: 'production', publishableKey: 'pk_live_…' })
14
+
15
+ // clientSecret: lo crea TU servidor con @crosspay-lb/node (sessions.create)
16
+ const fields = await cp.card.mount({ container: '#card', clientSecret })
17
+ const result = await fields.pay()
18
+ ```
19
+
20
+ O por CDN: `<script src="https://cdn.jsdelivr.net/npm/@crosspay-lb/js@0.2/dist/crosspay.min.js"></script>` → `window.Crosspay`.
21
+
22
+ Requisitos: llave pública del comercio, sitio en sus **dominios autorizados** y
23
+ una sesión de pago creada en el servidor con la llave secreta.
24
+
25
+ Documentación completa: panel de Crosspay → **Documentación de la API** → *crosspay.js*.
26
+
27
+ © Crosspay Solutions S.A.S. Todos los derechos reservados. Uso sujeto a [LICENSE](LICENSE).
@@ -0,0 +1 @@
1
+ "use strict";var Crosspay=(()=>{var v=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},K=(e,t,n,p)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of N(t))!H.call(e,r)&&r!==n&&v(e,r,{get:()=>t[r],enumerable:!(p=q(t,r))||p.enumerable});return e};var Y=e=>K(v({},"__esModule",{value:!0}),e);var ee={};z(ee,{API_BASE_URLS:()=>f,CrosspayError:()=>a,init:()=>b,version:()=>$});var a=class extends Error{constructor(n,p,r){super(n);this.code=p;this.details=r;this.name="CrosspayError"}};var w="crosspay-fields/2";function I(e,t){return typeof e=="object"&&e!==null&&e.protocol===w&&e.channel===t&&typeof e.kind=="string"}function T(e){return new URLSearchParams({channel:e.channel,origin:e.parentOrigin}).toString()}var V=3e4,W={position:"fixed",inset:"0",width:"100%",height:"100%",zIndex:"2147483647"};function O(e,t,n){let p=typeof n.container=="string"?document.querySelector(n.container):n.container;if(!p)return Promise.reject(new a(`No existe el contenedor ${String(n.container)}.`,"INVALID_CONTAINER"));let r=new URL(e).origin,o=R(),i=document.createElement("div");i.style.position="relative";let s=document.createElement("iframe");s.src=`${e}#${T({channel:o,parentOrigin:window.location.origin})}`,s.title="Formulario seguro de tarjeta",s.setAttribute("scrolling","no"),s.setAttribute("allow","payment"),Object.assign(s.style,{display:"block",width:"100%",height:"0px",border:"0",background:"transparent",colorScheme:"normal"}),i.appendChild(s),p.replaceChildren(i);let l=0,d=new Map,u=m=>{s.contentWindow?.postMessage({...m,protocol:w,channel:o},r)},D=m=>{m?(i.style.height=`${l}px`,Object.assign(s.style,W)):(i.style.height="",Object.assign(s.style,{position:"",inset:"",width:"100%",height:`${l}px`,zIndex:""}))},h=!1,x=()=>{if(!h){h=!0,window.removeEventListener("message",k);for(let m of d.values())m.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));d.clear(),i.remove()}},y=null;function k(m){if(m.source!==s.contentWindow||m.origin!==r||!I(m.data,o))return;let c=m.data;switch(c.kind){case"ready":u({kind:"init",options:{publishableKey:t,clientSecret:n.clientSecret,installments:n.installments,labels:n.labels,styles:n.styles}});break;case"mounted":y?.(c.session);break;case"mount-error":y?.(new a(c.message,c.code));break;case"resize":l=Math.max(0,Math.ceil(c.height)),s.style.position!=="fixed"?s.style.height=`${l}px`:i.style.height=`${l}px`;break;case"validity":n.onValidityChange?.(c.valid);break;case"overlay":D(c.active);break;case"paid":{let g=d.get(c.requestId);d.delete(c.requestId),g?.resolve(c.result);break}case"pay-error":{let g=d.get(c.requestId);d.delete(c.requestId),g?.reject(new a(c.message,c.code));break}}}return window.addEventListener("message",k),new Promise((m,c)=>{let g=setTimeout(()=>{y?.(new a("No se pudo cargar el formulario de tarjeta.","FIELDS_TIMEOUT"))},V);y=S=>{if(y=null,clearTimeout(g),S instanceof a){x(),c(S);return}m({session:S,pay(){if(h)return Promise.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));let P=R();return new Promise((j,B)=>{d.set(P,{resolve:j,reject:B}),u({kind:"pay",requestId:P})})},destroy:x})}})}function R(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`}var f={development:"https://api-dev.crosspaysolutions.financial",production:"https://api.crosspaysolutions.financial"};function F(e){let t=f[e];if(!t)throw new TypeError(`Ambiente de Crosspay no valido: "${String(e)}". Usa "development" o "production".`);return t}var C={development:"https://fields-dev.crosspaysolutions.financial/",production:"https://fields.crosspaysolutions.financial/"};function U(e){let t=(r,o)=>(e.fetch??globalThis.fetch)(r,o);async function n(r,o,i){let s={Accept:"application/json"};i!==void 0&&(s["Content-Type"]="application/json"),e.locale&&(s["Accept-Language"]=e.locale);let l;try{l=await t(`${e.baseUrl}${o}`,{method:r,headers:s,body:i===void 0?void 0:JSON.stringify(i),credentials:"omit"})}catch(u){throw new a("No se pudo conectar con Crosspay.","NETWORK_ERROR",u)}let d=await l.json().catch(()=>null);if(!l.ok||d?.success===!1){let u=d?.message||d?.error||`Crosspay respondio ${l.status}.`;throw new a(u,`HTTP_${l.status}`,d)}return d}async function p(r,o,i){let s=await n(r,o,i);return s&&typeof s=="object"&&"data"in s?s.data:s}return{get:r=>p("GET",r),post:(r,o)=>p("POST",r,o),postRaw:(r,o)=>n("POST",r,o)}}var _="cp_device_id";function G(){try{let e=localStorage.getItem(_);return e||(e=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`,localStorage.setItem(_,e)),e}catch{return""}}function J(){return typeof navigator>"u"?"":[navigator.userAgent,navigator.language,typeof screen<"u"?`${screen.width}x${screen.height}`:"",E()].join("|")}function Q(e){let t=5381;for(let n=0;n<e.length;n+=1)t=(t<<5)+t+e.charCodeAt(n)|0;return(t>>>0).toString(16)}function E(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function L(){let e=G(),t=Q(J());return e?`${e}.${t}`:t}function X(){let e=typeof navigator<"u"?navigator.language?.split("-")[0]:void 0;return{fingerprint:L(),timezone:E(),...e?{language:e}:{}}}function M(e){return{status(t){return e.get(`/payments/status/${encodeURIComponent(t)}`)}}}function A(e,t){return{async listBanks(n="COP"){return(await e.get(`/payments/pse/banks?currency=${encodeURIComponent(n)}`)??[]).filter(r=>{let o=String(r.code??"").trim(),i=String(r.name??"").normalize("NFD").replace(/\p{Diacritic}/gu,"").toLowerCase();return o!==""&&o!=="0"&&!i.includes("seleccione su banco")})},async pay(n){let{clientSecret:p,...r}=n,o=await e.postRaw("/payments/sessions/pay",{publishableKey:t,clientSecret:p,parentOrigin:window.location.origin,method:"pse",pse:r,meta:X()}),{data:i,...s}=o??{},l=i?.sessionId;return{...s,...l?{sessionId:l}:{}}}}}var Z={development:"pk_test_",production:"pk_live_"};function b(e){let t=F(e.environment),n=String(e.publishableKey??"").trim(),p=Z[e.environment];if(!n.startsWith(p))throw new TypeError(`\`publishableKey\` debe empezar por "${p}" en el ambiente "${e.environment}".`);if(e.fieldsUrl&&e.environment!=="development")throw new TypeError('`fieldsUrl` solo se admite en el ambiente "development".');let r=e.fieldsUrl??C[e.environment],o=U({baseUrl:t,locale:e.locale,fetch:e.fetch});return{environment:e.environment,card:{async mount(i){if(!String(i.clientSecret??"").trim())throw new a("Falta `clientSecret`: crea la sesion de pago en tu servidor con @crosspay-lb/node.","INVALID_REQUEST");return O(r,n,i)}},payments:M(o),pse:A(o,n)}}var $="0.2.0";return Y(ee);})();
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var S=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var H=(e,t)=>{for(var n in t)S(e,n,{get:t[n],enumerable:!0})},z=(e,t,n,p)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of q(t))!N.call(e,r)&&r!==n&&S(e,r,{get:()=>t[r],enumerable:!(p=B(t,r))||p.enumerable});return e};var K=e=>z(S({},"__esModule",{value:!0}),e);var ee={};H(ee,{API_BASE_URLS:()=>C,CrosspayError:()=>a,FIELDS_URLS:()=>f,init:()=>$,version:()=>Z});module.exports=K(ee);var a=class extends Error{constructor(n,p,r){super(n);this.code=p;this.details=r;this.name="CrosspayError"}};var w="crosspay-fields/2";function P(e,t){return typeof e=="object"&&e!==null&&e.protocol===w&&e.channel===t&&typeof e.kind=="string"}function T(e){return new URLSearchParams({channel:e.channel,origin:e.parentOrigin}).toString()}var Y=3e4,V={position:"fixed",inset:"0",width:"100%",height:"100%",zIndex:"2147483647"};function R(e,t,n){let p=typeof n.container=="string"?document.querySelector(n.container):n.container;if(!p)return Promise.reject(new a(`No existe el contenedor ${String(n.container)}.`,"INVALID_CONTAINER"));let r=new URL(e).origin,o=I(),i=document.createElement("div");i.style.position="relative";let s=document.createElement("iframe");s.src=`${e}#${T({channel:o,parentOrigin:window.location.origin})}`,s.title="Formulario seguro de tarjeta",s.setAttribute("scrolling","no"),s.setAttribute("allow","payment"),Object.assign(s.style,{display:"block",width:"100%",height:"0px",border:"0",background:"transparent",colorScheme:"normal"}),i.appendChild(s),p.replaceChildren(i);let l=0,d=new Map,u=m=>{s.contentWindow?.postMessage({...m,protocol:w,channel:o},r)},A=m=>{m?(i.style.height=`${l}px`,Object.assign(s.style,V)):(i.style.height="",Object.assign(s.style,{position:"",inset:"",width:"100%",height:`${l}px`,zIndex:""}))},h=!1,b=()=>{if(!h){h=!0,window.removeEventListener("message",x);for(let m of d.values())m.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));d.clear(),i.remove()}},y=null;function x(m){if(m.source!==s.contentWindow||m.origin!==r||!P(m.data,o))return;let c=m.data;switch(c.kind){case"ready":u({kind:"init",options:{publishableKey:t,clientSecret:n.clientSecret,installments:n.installments,labels:n.labels,styles:n.styles}});break;case"mounted":y?.(c.session);break;case"mount-error":y?.(new a(c.message,c.code));break;case"resize":l=Math.max(0,Math.ceil(c.height)),s.style.position!=="fixed"?s.style.height=`${l}px`:i.style.height=`${l}px`;break;case"validity":n.onValidityChange?.(c.valid);break;case"overlay":A(c.active);break;case"paid":{let g=d.get(c.requestId);d.delete(c.requestId),g?.resolve(c.result);break}case"pay-error":{let g=d.get(c.requestId);d.delete(c.requestId),g?.reject(new a(c.message,c.code));break}}}return window.addEventListener("message",x),new Promise((m,c)=>{let g=setTimeout(()=>{y?.(new a("No se pudo cargar el formulario de tarjeta.","FIELDS_TIMEOUT"))},Y);y=v=>{if(y=null,clearTimeout(g),v instanceof a){b(),c(v);return}m({session:v,pay(){if(h)return Promise.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));let k=I();return new Promise((D,j)=>{d.set(k,{resolve:D,reject:j}),u({kind:"pay",requestId:k})})},destroy:b})}})}function I(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`}var C={development:"https://api-dev.crosspaysolutions.financial",production:"https://api.crosspaysolutions.financial"};function O(e){let t=C[e];if(!t)throw new TypeError(`Ambiente de Crosspay no valido: "${String(e)}". Usa "development" o "production".`);return t}var f={development:"https://fields-dev.crosspaysolutions.financial/",production:"https://fields.crosspaysolutions.financial/"};function F(e){let t=(r,o)=>(e.fetch??globalThis.fetch)(r,o);async function n(r,o,i){let s={Accept:"application/json"};i!==void 0&&(s["Content-Type"]="application/json"),e.locale&&(s["Accept-Language"]=e.locale);let l;try{l=await t(`${e.baseUrl}${o}`,{method:r,headers:s,body:i===void 0?void 0:JSON.stringify(i),credentials:"omit"})}catch(u){throw new a("No se pudo conectar con Crosspay.","NETWORK_ERROR",u)}let d=await l.json().catch(()=>null);if(!l.ok||d?.success===!1){let u=d?.message||d?.error||`Crosspay respondio ${l.status}.`;throw new a(u,`HTTP_${l.status}`,d)}return d}async function p(r,o,i){let s=await n(r,o,i);return s&&typeof s=="object"&&"data"in s?s.data:s}return{get:r=>p("GET",r),post:(r,o)=>p("POST",r,o),postRaw:(r,o)=>n("POST",r,o)}}var U="cp_device_id";function W(){try{let e=localStorage.getItem(U);return e||(e=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`,localStorage.setItem(U,e)),e}catch{return""}}function G(){return typeof navigator>"u"?"":[navigator.userAgent,navigator.language,typeof screen<"u"?`${screen.width}x${screen.height}`:"",E()].join("|")}function J(e){let t=5381;for(let n=0;n<e.length;n+=1)t=(t<<5)+t+e.charCodeAt(n)|0;return(t>>>0).toString(16)}function E(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function L(){let e=W(),t=J(G());return e?`${e}.${t}`:t}function Q(){let e=typeof navigator<"u"?navigator.language?.split("-")[0]:void 0;return{fingerprint:L(),timezone:E(),...e?{language:e}:{}}}function M(e){return{status(t){return e.get(`/payments/status/${encodeURIComponent(t)}`)}}}function _(e,t){return{async listBanks(n="COP"){return(await e.get(`/payments/pse/banks?currency=${encodeURIComponent(n)}`)??[]).filter(r=>{let o=String(r.code??"").trim(),i=String(r.name??"").normalize("NFD").replace(/\p{Diacritic}/gu,"").toLowerCase();return o!==""&&o!=="0"&&!i.includes("seleccione su banco")})},async pay(n){let{clientSecret:p,...r}=n,o=await e.postRaw("/payments/sessions/pay",{publishableKey:t,clientSecret:p,parentOrigin:window.location.origin,method:"pse",pse:r,meta:Q()}),{data:i,...s}=o??{},l=i?.sessionId;return{...s,...l?{sessionId:l}:{}}}}}var X={development:"pk_test_",production:"pk_live_"};function $(e){let t=O(e.environment),n=String(e.publishableKey??"").trim(),p=X[e.environment];if(!n.startsWith(p))throw new TypeError(`\`publishableKey\` debe empezar por "${p}" en el ambiente "${e.environment}".`);if(e.fieldsUrl&&e.environment!=="development")throw new TypeError('`fieldsUrl` solo se admite en el ambiente "development".');let r=e.fieldsUrl??f[e.environment],o=F({baseUrl:t,locale:e.locale,fetch:e.fetch});return{environment:e.environment,card:{async mount(i){if(!String(i.clientSecret??"").trim())throw new a("Falta `clientSecret`: crea la sesion de pago en tu servidor con @crosspay-lb/node.","INVALID_REQUEST");return R(r,n,i)}},payments:M(o),pse:_(o,n)}}var Z="0.2.0";0&&(module.exports={API_BASE_URLS,CrosspayError,FIELDS_URLS,init,version});
@@ -0,0 +1,192 @@
1
+ interface HttpClientOptions {
2
+ baseUrl: string;
3
+ locale?: string;
4
+ fetch?: typeof fetch;
5
+ }
6
+ /**
7
+ * Cliente HTTP minimo sobre `fetch`.
8
+ *
9
+ * Desde el navegador NO se manda la llave secreta: el backend reconoce al
10
+ * comercio por su llave publica, el `Origin` de su dominio autorizado y la
11
+ * sesion que creo su servidor. La llave secreta solo va en `@crosspay-lb/node`.
12
+ *
13
+ * El backend responde `{ success, data }`; aqui se desenvuelve `data` y un
14
+ * `success: false` se convierte en `CrosspayError`.
15
+ */
16
+ declare function createHttpClient(options: HttpClientOptions): {
17
+ get: <T>(path: string) => Promise<T>;
18
+ post: <T>(path: string, body: unknown) => Promise<T>;
19
+ /** Sin desenvolver: para respuestas planas como la del cobro. */
20
+ postRaw: (path: string, body: unknown) => Promise<{
21
+ success?: boolean;
22
+ data?: unknown;
23
+ message?: string;
24
+ error?: string;
25
+ } | null>;
26
+ };
27
+ type HttpClient = ReturnType<typeof createHttpClient>;
28
+
29
+ type PaymentStatus = 'approved' | 'pending' | 'declined' | 'failed' | 'cancelled' | 'refunded' | (string & {});
30
+ interface PaymentResult {
31
+ success: boolean;
32
+ status: PaymentStatus;
33
+ message?: string;
34
+ transactionId: string | null;
35
+ sessionId?: string;
36
+ /** PSE: URL del banco a la que hay que llevar al pagador. */
37
+ redirectUrl?: string;
38
+ declineReason?: string | null;
39
+ amount?: number;
40
+ currency?: string;
41
+ reference?: string;
42
+ [key: string]: unknown;
43
+ }
44
+ interface Bank {
45
+ code: string;
46
+ name: string;
47
+ }
48
+ interface PsePayInput {
49
+ /** `clientSecret` de la sesion creada por el servidor del comercio. */
50
+ clientSecret: string;
51
+ /** Codigo del banco, de `pse.listBanks()`. */
52
+ bank: string;
53
+ documentType: string;
54
+ documentNumber: string;
55
+ /** `natural` o `juridica`. */
56
+ personType: string;
57
+ /** Direccion del pagador. Obligatoria para PSE. */
58
+ address?: string;
59
+ }
60
+ declare function createPaymentsApi(http: HttpClient): {
61
+ /** Estado publico de una transaccion (el mismo recurso del comprobante). */
62
+ status(transactionId: string): Promise<PaymentResult>;
63
+ };
64
+ declare function createPseApi(http: HttpClient, publishableKey: string): {
65
+ /** Bancos PSE (Colombia). Se quita la entrada guia "seleccione su banco". */
66
+ listBanks(currency?: string): Promise<Bank[]>;
67
+ /**
68
+ * Inicia el pago PSE de la sesion. Devuelve `redirectUrl`: hay que llevar
69
+ * al pagador al banco. Monto y cliente salen de la sesion.
70
+ */
71
+ pay(input: PsePayInput): Promise<PaymentResult>;
72
+ };
73
+
74
+ /** Lo que la pagina de campos confirma de la sesion al montar. */
75
+ interface SessionSummary {
76
+ sessionId: string;
77
+ amount: number;
78
+ currency: string;
79
+ description: string;
80
+ }
81
+
82
+ interface CardFieldLabels {
83
+ cardholderName?: string;
84
+ cardNumber?: string;
85
+ expirationDate?: string;
86
+ cvv?: string;
87
+ installments?: string;
88
+ }
89
+ interface CardFieldsOptions {
90
+ /** Donde se inserta el formulario: un selector CSS o el elemento. */
91
+ container: string | HTMLElement;
92
+ /**
93
+ * `clientSecret` de la sesion de pago que creo el SERVIDOR del comercio con
94
+ * su llave secreta (`sessions.create` de @crosspay-lb/node). Monto, moneda y
95
+ * cliente salen de la sesion: el navegador no puede cambiarlos.
96
+ */
97
+ clientSecret: string;
98
+ /** Muestra el selector de cuotas. */
99
+ installments?: boolean;
100
+ labels?: CardFieldLabels;
101
+ /**
102
+ * Estilos de los campos: `input`, `focus`, `invalid`, `label`, `container`…
103
+ * con propiedades CSS en kebab-case. El formulario vive en un iframe y no
104
+ * hereda el CSS de la pagina, asi que la marca del comercio se pasa aqui.
105
+ */
106
+ styles?: Record<string, unknown>;
107
+ /** Se dispara cada vez que cambia la validez del formulario. */
108
+ onValidityChange?: (isValid: boolean) => void;
109
+ }
110
+ interface MountedCardFields {
111
+ /** Monto, moneda y descripcion de la sesion que se va a pagar. */
112
+ readonly session: SessionSummary;
113
+ /**
114
+ * Tokeniza y cobra la sesion. Puede abrir la verificacion 3DS del banco a
115
+ * pantalla completa, asi que puede tardar. Lanza `CrosspayError` si la
116
+ * tarjeta se rechaza o la sesion ya no se puede pagar.
117
+ */
118
+ pay(): Promise<PaymentResult>;
119
+ /** Retira el formulario. */
120
+ destroy(): void;
121
+ }
122
+
123
+ /**
124
+ * Ambientes soportados.
125
+ *
126
+ * crosspay.js solo habla con la plataforma `.financial`: desarrollo y
127
+ * produccion. No se acepta una URL libre a proposito — una libreria de pagos
128
+ * que deja apuntar a cualquier host es una forma comoda de mandar datos del
129
+ * pagador a donde no toca.
130
+ */
131
+ type CrosspayEnvironment = 'development' | 'production';
132
+ declare const API_BASE_URLS: Record<CrosspayEnvironment, string>;
133
+ /**
134
+ * Pagina de campos de tarjeta de cada ambiente. Se carga en un iframe dentro
135
+ * del sitio del comercio; la tarjeta se escribe ahi, nunca en la pagina del
136
+ * comercio.
137
+ */
138
+ declare const FIELDS_URLS: Record<CrosspayEnvironment, string>;
139
+
140
+ interface CrosspayOptions {
141
+ /** `development` → api-dev.crosspaysolutions.financial · `production` → api.crosspaysolutions.financial */
142
+ environment: CrosspayEnvironment;
143
+ /**
144
+ * Llave publica del comercio (panel → Integracion). `pk_test_…` en
145
+ * development, `pk_live_…` en production. Es publica por diseno: solo
146
+ * funciona desde los dominios autorizados del comercio y con una sesion
147
+ * creada por su servidor.
148
+ */
149
+ publishableKey: string;
150
+ /** Idioma de los correos al pagador (`es`, `en`…). */
151
+ locale?: string;
152
+ /** Para pruebas. Por defecto, el `fetch` del navegador. */
153
+ fetch?: typeof fetch;
154
+ /**
155
+ * Solo `development`: URL de una pagina de campos local, para probar sin
156
+ * desplegar. En `production` se rechaza: el formulario de tarjeta siempre
157
+ * se carga desde el dominio de Crosspay.
158
+ */
159
+ fieldsUrl?: string;
160
+ }
161
+ interface Crosspay {
162
+ readonly environment: CrosspayEnvironment;
163
+ card: {
164
+ /**
165
+ * Monta el formulario seguro de tarjeta en `options.container` para la
166
+ * sesion `options.clientSecret`. Rechaza con `CrosspayError` si la llave,
167
+ * la sesion o el dominio no son validos.
168
+ */
169
+ mount(options: CardFieldsOptions): Promise<MountedCardFields>;
170
+ };
171
+ payments: ReturnType<typeof createPaymentsApi>;
172
+ pse: ReturnType<typeof createPseApi>;
173
+ }
174
+ declare function init(options: CrosspayOptions): Crosspay;
175
+
176
+ /**
177
+ * Error normalizado de crosspay.js.
178
+ *
179
+ * `code` es el codigo del rechazo (p. ej. `E005` en 3DS, `017` en un rechazo) o
180
+ * uno propio de la libreria (`CARD_UNAVAILABLE`, `HTTP_401`…). `message` ya
181
+ * viene en lenguaje del pagador: se puede mostrar tal cual.
182
+ */
183
+ declare class CrosspayError extends Error {
184
+ readonly code?: string | undefined;
185
+ readonly details?: unknown | undefined;
186
+ constructor(message: string, code?: string | undefined, details?: unknown | undefined);
187
+ }
188
+
189
+ /** Version de la libreria, inyectada en el build. */
190
+ declare const version: string;
191
+
192
+ export { API_BASE_URLS, type Bank, type CardFieldLabels, type CardFieldsOptions, type Crosspay, type CrosspayEnvironment, CrosspayError, type CrosspayOptions, FIELDS_URLS, type MountedCardFields, type PaymentResult, type PaymentStatus, type PsePayInput, type SessionSummary, init, version };
@@ -0,0 +1,192 @@
1
+ interface HttpClientOptions {
2
+ baseUrl: string;
3
+ locale?: string;
4
+ fetch?: typeof fetch;
5
+ }
6
+ /**
7
+ * Cliente HTTP minimo sobre `fetch`.
8
+ *
9
+ * Desde el navegador NO se manda la llave secreta: el backend reconoce al
10
+ * comercio por su llave publica, el `Origin` de su dominio autorizado y la
11
+ * sesion que creo su servidor. La llave secreta solo va en `@crosspay-lb/node`.
12
+ *
13
+ * El backend responde `{ success, data }`; aqui se desenvuelve `data` y un
14
+ * `success: false` se convierte en `CrosspayError`.
15
+ */
16
+ declare function createHttpClient(options: HttpClientOptions): {
17
+ get: <T>(path: string) => Promise<T>;
18
+ post: <T>(path: string, body: unknown) => Promise<T>;
19
+ /** Sin desenvolver: para respuestas planas como la del cobro. */
20
+ postRaw: (path: string, body: unknown) => Promise<{
21
+ success?: boolean;
22
+ data?: unknown;
23
+ message?: string;
24
+ error?: string;
25
+ } | null>;
26
+ };
27
+ type HttpClient = ReturnType<typeof createHttpClient>;
28
+
29
+ type PaymentStatus = 'approved' | 'pending' | 'declined' | 'failed' | 'cancelled' | 'refunded' | (string & {});
30
+ interface PaymentResult {
31
+ success: boolean;
32
+ status: PaymentStatus;
33
+ message?: string;
34
+ transactionId: string | null;
35
+ sessionId?: string;
36
+ /** PSE: URL del banco a la que hay que llevar al pagador. */
37
+ redirectUrl?: string;
38
+ declineReason?: string | null;
39
+ amount?: number;
40
+ currency?: string;
41
+ reference?: string;
42
+ [key: string]: unknown;
43
+ }
44
+ interface Bank {
45
+ code: string;
46
+ name: string;
47
+ }
48
+ interface PsePayInput {
49
+ /** `clientSecret` de la sesion creada por el servidor del comercio. */
50
+ clientSecret: string;
51
+ /** Codigo del banco, de `pse.listBanks()`. */
52
+ bank: string;
53
+ documentType: string;
54
+ documentNumber: string;
55
+ /** `natural` o `juridica`. */
56
+ personType: string;
57
+ /** Direccion del pagador. Obligatoria para PSE. */
58
+ address?: string;
59
+ }
60
+ declare function createPaymentsApi(http: HttpClient): {
61
+ /** Estado publico de una transaccion (el mismo recurso del comprobante). */
62
+ status(transactionId: string): Promise<PaymentResult>;
63
+ };
64
+ declare function createPseApi(http: HttpClient, publishableKey: string): {
65
+ /** Bancos PSE (Colombia). Se quita la entrada guia "seleccione su banco". */
66
+ listBanks(currency?: string): Promise<Bank[]>;
67
+ /**
68
+ * Inicia el pago PSE de la sesion. Devuelve `redirectUrl`: hay que llevar
69
+ * al pagador al banco. Monto y cliente salen de la sesion.
70
+ */
71
+ pay(input: PsePayInput): Promise<PaymentResult>;
72
+ };
73
+
74
+ /** Lo que la pagina de campos confirma de la sesion al montar. */
75
+ interface SessionSummary {
76
+ sessionId: string;
77
+ amount: number;
78
+ currency: string;
79
+ description: string;
80
+ }
81
+
82
+ interface CardFieldLabels {
83
+ cardholderName?: string;
84
+ cardNumber?: string;
85
+ expirationDate?: string;
86
+ cvv?: string;
87
+ installments?: string;
88
+ }
89
+ interface CardFieldsOptions {
90
+ /** Donde se inserta el formulario: un selector CSS o el elemento. */
91
+ container: string | HTMLElement;
92
+ /**
93
+ * `clientSecret` de la sesion de pago que creo el SERVIDOR del comercio con
94
+ * su llave secreta (`sessions.create` de @crosspay-lb/node). Monto, moneda y
95
+ * cliente salen de la sesion: el navegador no puede cambiarlos.
96
+ */
97
+ clientSecret: string;
98
+ /** Muestra el selector de cuotas. */
99
+ installments?: boolean;
100
+ labels?: CardFieldLabels;
101
+ /**
102
+ * Estilos de los campos: `input`, `focus`, `invalid`, `label`, `container`…
103
+ * con propiedades CSS en kebab-case. El formulario vive en un iframe y no
104
+ * hereda el CSS de la pagina, asi que la marca del comercio se pasa aqui.
105
+ */
106
+ styles?: Record<string, unknown>;
107
+ /** Se dispara cada vez que cambia la validez del formulario. */
108
+ onValidityChange?: (isValid: boolean) => void;
109
+ }
110
+ interface MountedCardFields {
111
+ /** Monto, moneda y descripcion de la sesion que se va a pagar. */
112
+ readonly session: SessionSummary;
113
+ /**
114
+ * Tokeniza y cobra la sesion. Puede abrir la verificacion 3DS del banco a
115
+ * pantalla completa, asi que puede tardar. Lanza `CrosspayError` si la
116
+ * tarjeta se rechaza o la sesion ya no se puede pagar.
117
+ */
118
+ pay(): Promise<PaymentResult>;
119
+ /** Retira el formulario. */
120
+ destroy(): void;
121
+ }
122
+
123
+ /**
124
+ * Ambientes soportados.
125
+ *
126
+ * crosspay.js solo habla con la plataforma `.financial`: desarrollo y
127
+ * produccion. No se acepta una URL libre a proposito — una libreria de pagos
128
+ * que deja apuntar a cualquier host es una forma comoda de mandar datos del
129
+ * pagador a donde no toca.
130
+ */
131
+ type CrosspayEnvironment = 'development' | 'production';
132
+ declare const API_BASE_URLS: Record<CrosspayEnvironment, string>;
133
+ /**
134
+ * Pagina de campos de tarjeta de cada ambiente. Se carga en un iframe dentro
135
+ * del sitio del comercio; la tarjeta se escribe ahi, nunca en la pagina del
136
+ * comercio.
137
+ */
138
+ declare const FIELDS_URLS: Record<CrosspayEnvironment, string>;
139
+
140
+ interface CrosspayOptions {
141
+ /** `development` → api-dev.crosspaysolutions.financial · `production` → api.crosspaysolutions.financial */
142
+ environment: CrosspayEnvironment;
143
+ /**
144
+ * Llave publica del comercio (panel → Integracion). `pk_test_…` en
145
+ * development, `pk_live_…` en production. Es publica por diseno: solo
146
+ * funciona desde los dominios autorizados del comercio y con una sesion
147
+ * creada por su servidor.
148
+ */
149
+ publishableKey: string;
150
+ /** Idioma de los correos al pagador (`es`, `en`…). */
151
+ locale?: string;
152
+ /** Para pruebas. Por defecto, el `fetch` del navegador. */
153
+ fetch?: typeof fetch;
154
+ /**
155
+ * Solo `development`: URL de una pagina de campos local, para probar sin
156
+ * desplegar. En `production` se rechaza: el formulario de tarjeta siempre
157
+ * se carga desde el dominio de Crosspay.
158
+ */
159
+ fieldsUrl?: string;
160
+ }
161
+ interface Crosspay {
162
+ readonly environment: CrosspayEnvironment;
163
+ card: {
164
+ /**
165
+ * Monta el formulario seguro de tarjeta en `options.container` para la
166
+ * sesion `options.clientSecret`. Rechaza con `CrosspayError` si la llave,
167
+ * la sesion o el dominio no son validos.
168
+ */
169
+ mount(options: CardFieldsOptions): Promise<MountedCardFields>;
170
+ };
171
+ payments: ReturnType<typeof createPaymentsApi>;
172
+ pse: ReturnType<typeof createPseApi>;
173
+ }
174
+ declare function init(options: CrosspayOptions): Crosspay;
175
+
176
+ /**
177
+ * Error normalizado de crosspay.js.
178
+ *
179
+ * `code` es el codigo del rechazo (p. ej. `E005` en 3DS, `017` en un rechazo) o
180
+ * uno propio de la libreria (`CARD_UNAVAILABLE`, `HTTP_401`…). `message` ya
181
+ * viene en lenguaje del pagador: se puede mostrar tal cual.
182
+ */
183
+ declare class CrosspayError extends Error {
184
+ readonly code?: string | undefined;
185
+ readonly details?: unknown | undefined;
186
+ constructor(message: string, code?: string | undefined, details?: unknown | undefined);
187
+ }
188
+
189
+ /** Version de la libreria, inyectada en el build. */
190
+ declare const version: string;
191
+
192
+ export { API_BASE_URLS, type Bank, type CardFieldLabels, type CardFieldsOptions, type Crosspay, type CrosspayEnvironment, CrosspayError, type CrosspayOptions, FIELDS_URLS, type MountedCardFields, type PaymentResult, type PaymentStatus, type PsePayInput, type SessionSummary, init, version };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var c=class extends Error{constructor(t,d,s){super(t);this.code=d;this.details=s;this.name="CrosspayError"}};var v="crosspay-fields/2";function x(e,r){return typeof e=="object"&&e!==null&&e.protocol===v&&e.channel===r&&typeof e.kind=="string"}function k(e){return new URLSearchParams({channel:e.channel,origin:e.parentOrigin}).toString()}var D=3e4,j={position:"fixed",inset:"0",width:"100%",height:"100%",zIndex:"2147483647"};function T(e,r,t){let d=typeof t.container=="string"?document.querySelector(t.container):t.container;if(!d)return Promise.reject(new c(`No existe el contenedor ${String(t.container)}.`,"INVALID_CONTAINER"));let s=new URL(e).origin,o=P(),i=document.createElement("div");i.style.position="relative";let n=document.createElement("iframe");n.src=`${e}#${k({channel:o,parentOrigin:window.location.origin})}`,n.title="Formulario seguro de tarjeta",n.setAttribute("scrolling","no"),n.setAttribute("allow","payment"),Object.assign(n.style,{display:"block",width:"100%",height:"0px",border:"0",background:"transparent",colorScheme:"normal"}),i.appendChild(n),d.replaceChildren(i);let p=0,l=new Map,u=m=>{n.contentWindow?.postMessage({...m,protocol:v,channel:o},s)},_=m=>{m?(i.style.height=`${p}px`,Object.assign(n.style,j)):(i.style.height="",Object.assign(n.style,{position:"",inset:"",width:"100%",height:`${p}px`,zIndex:""}))},f=!1,C=()=>{if(!f){f=!0,window.removeEventListener("message",E);for(let m of l.values())m.reject(new c("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));l.clear(),i.remove()}},y=null;function E(m){if(m.source!==n.contentWindow||m.origin!==s||!x(m.data,o))return;let a=m.data;switch(a.kind){case"ready":u({kind:"init",options:{publishableKey:r,clientSecret:t.clientSecret,installments:t.installments,labels:t.labels,styles:t.styles}});break;case"mounted":y?.(a.session);break;case"mount-error":y?.(new c(a.message,a.code));break;case"resize":p=Math.max(0,Math.ceil(a.height)),n.style.position!=="fixed"?n.style.height=`${p}px`:i.style.height=`${p}px`;break;case"validity":t.onValidityChange?.(a.valid);break;case"overlay":_(a.active);break;case"paid":{let g=l.get(a.requestId);l.delete(a.requestId),g?.resolve(a.result);break}case"pay-error":{let g=l.get(a.requestId);l.delete(a.requestId),g?.reject(new c(a.message,a.code));break}}}return window.addEventListener("message",E),new Promise((m,a)=>{let g=setTimeout(()=>{y?.(new c("No se pudo cargar el formulario de tarjeta.","FIELDS_TIMEOUT"))},D);y=h=>{if(y=null,clearTimeout(g),h instanceof c){C(),a(h);return}m({session:h,pay(){if(f)return Promise.reject(new c("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));let b=P();return new Promise(($,A)=>{l.set(b,{resolve:$,reject:A}),u({kind:"pay",requestId:b})})},destroy:C})}})}function P(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`}var I={development:"https://api-dev.crosspaysolutions.financial",production:"https://api.crosspaysolutions.financial"};function R(e){let r=I[e];if(!r)throw new TypeError(`Ambiente de Crosspay no valido: "${String(e)}". Usa "development" o "production".`);return r}var S={development:"https://fields-dev.crosspaysolutions.financial/",production:"https://fields.crosspaysolutions.financial/"};function O(e){let r=(s,o)=>(e.fetch??globalThis.fetch)(s,o);async function t(s,o,i){let n={Accept:"application/json"};i!==void 0&&(n["Content-Type"]="application/json"),e.locale&&(n["Accept-Language"]=e.locale);let p;try{p=await r(`${e.baseUrl}${o}`,{method:s,headers:n,body:i===void 0?void 0:JSON.stringify(i),credentials:"omit"})}catch(u){throw new c("No se pudo conectar con Crosspay.","NETWORK_ERROR",u)}let l=await p.json().catch(()=>null);if(!p.ok||l?.success===!1){let u=l?.message||l?.error||`Crosspay respondio ${p.status}.`;throw new c(u,`HTTP_${p.status}`,l)}return l}async function d(s,o,i){let n=await t(s,o,i);return n&&typeof n=="object"&&"data"in n?n.data:n}return{get:s=>d("GET",s),post:(s,o)=>d("POST",s,o),postRaw:(s,o)=>t("POST",s,o)}}var F="cp_device_id";function B(){try{let e=localStorage.getItem(F);return e||(e=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`,localStorage.setItem(F,e)),e}catch{return""}}function q(){return typeof navigator>"u"?"":[navigator.userAgent,navigator.language,typeof screen<"u"?`${screen.width}x${screen.height}`:"",w()].join("|")}function N(e){let r=5381;for(let t=0;t<e.length;t+=1)r=(r<<5)+r+e.charCodeAt(t)|0;return(r>>>0).toString(16)}function w(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function U(){let e=B(),r=N(q());return e?`${e}.${r}`:r}function H(){let e=typeof navigator<"u"?navigator.language?.split("-")[0]:void 0;return{fingerprint:U(),timezone:w(),...e?{language:e}:{}}}function L(e){return{status(r){return e.get(`/payments/status/${encodeURIComponent(r)}`)}}}function M(e,r){return{async listBanks(t="COP"){return(await e.get(`/payments/pse/banks?currency=${encodeURIComponent(t)}`)??[]).filter(s=>{let o=String(s.code??"").trim(),i=String(s.name??"").normalize("NFD").replace(/\p{Diacritic}/gu,"").toLowerCase();return o!==""&&o!=="0"&&!i.includes("seleccione su banco")})},async pay(t){let{clientSecret:d,...s}=t,o=await e.postRaw("/payments/sessions/pay",{publishableKey:r,clientSecret:d,parentOrigin:window.location.origin,method:"pse",pse:s,meta:H()}),{data:i,...n}=o??{},p=i?.sessionId;return{...n,...p?{sessionId:p}:{}}}}}var z={development:"pk_test_",production:"pk_live_"};function K(e){let r=R(e.environment),t=String(e.publishableKey??"").trim(),d=z[e.environment];if(!t.startsWith(d))throw new TypeError(`\`publishableKey\` debe empezar por "${d}" en el ambiente "${e.environment}".`);if(e.fieldsUrl&&e.environment!=="development")throw new TypeError('`fieldsUrl` solo se admite en el ambiente "development".');let s=e.fieldsUrl??S[e.environment],o=O({baseUrl:r,locale:e.locale,fetch:e.fetch});return{environment:e.environment,card:{async mount(i){if(!String(i.clientSecret??"").trim())throw new c("Falta `clientSecret`: crea la sesion de pago en tu servidor con @crosspay-lb/node.","INVALID_REQUEST");return T(s,t,i)}},payments:L(o),pse:M(o,t)}}var pe="0.2.0";export{I as API_BASE_URLS,c as CrosspayError,S as FIELDS_URLS,K as init,pe as version};
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "@crosspay-lb/js",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Librería de navegador de Crosspay: formulario seguro de tarjeta, PSE y estado de pagos.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "unpkg": "./dist/crosspay.min.js",
18
+ "jsdelivr": "./dist/crosspay.min.js",
19
+ "files": [
20
+ "dist",
21
+ "LICENSE"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "rm -rf dist && tsup",
26
+ "test": "vitest run",
27
+ "typecheck": "tsc -p tsconfig.json",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }