@crosspay-lb/js 0.2.0 → 0.4.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/README.md +14 -1
- package/dist/crosspay.min.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +67 -8
- package/dist/index.d.ts +67 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,20 @@ const fields = await cp.card.mount({ container: '#card', clientSecret })
|
|
|
17
17
|
const result = await fields.pay()
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Si la orden se crea recién al pagar, monta el formulario antes con el monto y la
|
|
21
|
+
moneda, y pasa la sesión en `pay` (desde 0.4.0, solo cobro único):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const fields = await cp.card.mountDeferred({ container: '#card', amount: 50000, currency: 'COP' })
|
|
25
|
+
// …el pagador llena la tarjeta y le da a pagar:
|
|
26
|
+
const { clientSecret } = await crearOrdenEnTuServidor()
|
|
27
|
+
const result = await fields.pay({ clientSecret })
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Si la sesión no es de ese monto y moneda, `pay` rechaza con `AMOUNT_MISMATCH`
|
|
31
|
+
sin tocar la tarjeta: cuando el total cambie, `destroy()` y vuelve a montar.
|
|
32
|
+
|
|
33
|
+
O por CDN: `<script src="https://cdn.jsdelivr.net/npm/@crosspay-lb/js@0.4/dist/crosspay.min.js"></script>` → `window.Crosspay`.
|
|
21
34
|
|
|
22
35
|
Requisitos: llave pública del comercio, sitio en sus **dominios autorizados** y
|
|
23
36
|
una sesión de pago creada en el servidor con la llave secreta.
|
package/dist/crosspay.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var Crosspay=(()=>{var v=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var
|
|
1
|
+
"use strict";var Crosspay=(()=>{var v=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var V=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},K=(e,t,n,d)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of H(t))!z.call(e,s)&&s!==n&&v(e,s,{get:()=>t[s],enumerable:!(d=q(t,s))||d.enumerable});return e};var Y=e=>K(v({},"__esModule",{value:!0}),e);var ne={};V(ne,{API_BASE_URLS:()=>S,CrosspayError:()=>i,init:()=>P,version:()=>j});var i=class extends Error{constructor(n,d,s){super(n);this.code=d;this.details=s;this.name="CrosspayError"}};var C="crosspay-fields/2";function R(e,t){return typeof e=="object"&&e!==null&&e.protocol===C&&e.channel===t&&typeof e.kind=="string"}function k(e){return new URLSearchParams({channel:e.channel,origin:e.parentOrigin}).toString()}var Q=3e4,W={position:"fixed",inset:"0",width:"100%",height:"100%",zIndex:"2147483647"};function b(e,t,n){let d=typeof n.container=="string"?document.querySelector(n.container):n.container;if(!d)return Promise.reject(new i(`No existe el contenedor ${String(n.container)}.`,"INVALID_CONTAINER"));let s=new URL(e).origin,a=D(),o=document.createElement("div");o.style.position="relative";let r=document.createElement("iframe");r.src=`${e}#${k({channel:a,parentOrigin:window.location.origin})}`,r.title="Formulario seguro de tarjeta",r.setAttribute("scrolling","no"),r.setAttribute("allow","payment"),Object.assign(r.style,{display:"block",width:"100%",height:"0px",border:"0",background:"transparent",colorScheme:"normal"}),o.appendChild(r),d.replaceChildren(o);let c=0,p=new Map,m=u=>{r.contentWindow?.postMessage({...u,protocol:C,channel:a},s)},f=u=>{u?(o.style.height=`${c}px`,Object.assign(r.style,W)):(o.style.height="",Object.assign(r.style,{position:"",inset:"",width:"100%",height:`${c}px`,zIndex:""}))},h=!1,x=()=>{if(!h){h=!0,window.removeEventListener("message",O);for(let u of p.values())u.reject(new i("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));p.clear(),o.remove()}},y=null;function O(u){if(u.source!==r.contentWindow||u.origin!==s||!R(u.data,a))return;let l=u.data;switch(l.kind){case"ready":m({kind:"init",options:{publishableKey:t,clientSecret:n.clientSecret,amount:n.amount,currency:n.currency,installments:n.installments,labels:n.labels,styles:n.styles}});break;case"mounted":y?.({session:l.session});break;case"mount-error":y?.(new i(l.message,l.code));break;case"resize":c=Math.max(0,Math.ceil(l.height)),r.style.position!=="fixed"?r.style.height=`${c}px`:o.style.height=`${c}px`;break;case"validity":n.onValidityChange?.(l.valid);break;case"overlay":f(l.active);break;case"paid":{let g=p.get(l.requestId);p.delete(l.requestId),g?.resolve(l.result);break}case"pay-error":{let g=p.get(l.requestId);p.delete(l.requestId),g?.reject(new i(l.message,l.code));break}}}return window.addEventListener("message",O),new Promise((u,l)=>{let g=setTimeout(()=>{y?.(new i("No se pudo cargar el formulario de tarjeta.","FIELDS_TIMEOUT"))},Q);y=w=>{if(y=null,clearTimeout(g),w instanceof i){x(),l(w);return}u({session:w.session,pay(F){if(h)return Promise.reject(new i("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));let T=D();return new Promise((N,B)=>{p.set(T,{resolve:N,reject:B}),m({kind:"pay",requestId:T,...F?{clientSecret:F}:{}})})},destroy:x})}})}function D(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`}var S={development:"https://api-dev.crosspaysolutions.financial",production:"https://api.crosspaysolutions.financial"};function U(e){let t=S[e];if(!t)throw new TypeError(`Ambiente de Crosspay no valido: "${String(e)}". Usa "development" o "production".`);return t}var E={development:"https://fields-dev.crosspaysolutions.financial/",production:"https://fields.crosspaysolutions.financial/"};function _(e){let t=(s,a)=>(e.fetch??globalThis.fetch)(s,a);async function n(s,a,o){let r={Accept:"application/json"};o!==void 0&&(r["Content-Type"]="application/json"),e.locale&&(r["Accept-Language"]=e.locale);let c;try{c=await t(`${e.baseUrl}${a}`,{method:s,headers:r,body:o===void 0?void 0:JSON.stringify(o),credentials:"omit"})}catch(m){throw new i("No se pudo conectar con Crosspay.","NETWORK_ERROR",m)}let p=await c.json().catch(()=>null);if(!c.ok||p?.success===!1){let m=p?.message||p?.error||`Crosspay respondio ${c.status}.`;throw new i(m,`HTTP_${c.status}`,p)}return p}async function d(s,a,o){let r=await n(s,a,o);return r&&typeof r=="object"&&"data"in r?r.data:r}return{get:s=>d("GET",s),post:(s,a)=>d("POST",s,a),postRaw:(s,a)=>n("POST",s,a)}}var L="cp_device_id";function G(){try{let e=localStorage.getItem(L);return e||(e=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`,localStorage.setItem(L,e)),e}catch{return""}}function Z(){return typeof navigator>"u"?"":[navigator.userAgent,navigator.language,typeof screen<"u"?`${screen.width}x${screen.height}`:"",I()].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 I(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function M(){let e=G(),t=J(Z());return e?`${e}.${t}`:t}function X(){let e=typeof navigator<"u"?navigator.language?.split("-")[0]:void 0;return{fingerprint:M(),timezone:I(),...e?{language:e}:{}}}function A(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(s=>{let a=String(s.code??"").trim(),o=String(s.name??"").normalize("NFD").replace(/\p{Diacritic}/gu,"").toLowerCase();return a!==""&&a!=="0"&&!o.includes("seleccione su banco")})},async pay(n){let{clientSecret:d,...s}=n,a=await e.postRaw("/payments/sessions/pay",{publishableKey:t,clientSecret:d,parentOrigin:window.location.origin,method:"pse",pse:s,meta:X()}),{data:o,...r}=a??{},c=o?.sessionId;return{...r,...c?{sessionId:c}:{}}}}}var ee={development:"pk_test_",production:"pk_live_"};function P(e){let t=U(e.environment),n=String(e.publishableKey??"").trim(),d=ee[e.environment];if(!n.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??E[e.environment],a=_({baseUrl:t,locale:e.locale,fetch:e.fetch});return{environment:e.environment,card:{async mount(o){if(!String(o.clientSecret??"").trim())throw new i("Falta `clientSecret`: crea la sesion de pago en tu servidor con @crosspay-lb/node.","INVALID_REQUEST");let r=await b(s,n,o);return{session:r.session,pay:()=>r.pay(),destroy:r.destroy}},async mountDeferred(o){let{amount:r,currency:c}=te(o),p=await b(s,n,{...o,amount:r,currency:c});return{amount:r,currency:c,pay(m){let f=String(m?.clientSecret??"").trim();return f?p.pay(f):Promise.reject(new i("Falta `clientSecret`: crea la sesion de pago en tu servidor antes de pagar.","INVALID_REQUEST"))},destroy:p.destroy}}},payments:A(a),pse:$(a,n)}}function te(e){if("clientSecret"in e)throw new i("`mountDeferred` no recibe `clientSecret`: pasalo en `pay()`, o usa `mount` si ya tienes la sesion.","INVALID_REQUEST");let t=Number(e.amount);if(!Number.isFinite(t)||t<=0)throw new i("`amount` debe ser un numero mayor que cero.","INVALID_REQUEST");let n=String(e.currency??"").trim().toUpperCase();if(!/^[A-Z]{3}$/.test(n))throw new i('`currency` debe ser un codigo ISO 4217, p. ej. "COP".',"INVALID_REQUEST");return{amount:t,currency:n}}var j="0.4.0";return Y(ne);})();
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var v=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var z=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},V=(e,t,n,d)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of q(t))!H.call(e,s)&&s!==n&&v(e,s,{get:()=>t[s],enumerable:!(d=B(t,s))||d.enumerable});return e};var K=e=>V(v({},"__esModule",{value:!0}),e);var ne={};z(ne,{API_BASE_URLS:()=>E,CrosspayError:()=>a,FIELDS_URLS:()=>h,init:()=>$,version:()=>te});module.exports=K(ne);var a=class extends Error{constructor(n,d,s){super(n);this.code=d;this.details=s;this.name="CrosspayError"}};var C="crosspay-fields/2";function T(e,t){return typeof e=="object"&&e!==null&&e.protocol===C&&e.channel===t&&typeof e.kind=="string"}function k(e){return new URLSearchParams({channel:e.channel,origin:e.parentOrigin}).toString()}var Y=3e4,Q={position:"fixed",inset:"0",width:"100%",height:"100%",zIndex:"2147483647"};function b(e,t,n){let d=typeof n.container=="string"?document.querySelector(n.container):n.container;if(!d)return Promise.reject(new a(`No existe el contenedor ${String(n.container)}.`,"INVALID_CONTAINER"));let s=new URL(e).origin,i=R(),o=document.createElement("div");o.style.position="relative";let r=document.createElement("iframe");r.src=`${e}#${k({channel:i,parentOrigin:window.location.origin})}`,r.title="Formulario seguro de tarjeta",r.setAttribute("scrolling","no"),r.setAttribute("allow","payment"),Object.assign(r.style,{display:"block",width:"100%",height:"0px",border:"0",background:"transparent",colorScheme:"normal"}),o.appendChild(r),d.replaceChildren(o);let c=0,p=new Map,m=u=>{r.contentWindow?.postMessage({...u,protocol:C,channel:i},s)},f=u=>{u?(o.style.height=`${c}px`,Object.assign(r.style,Q)):(o.style.height="",Object.assign(r.style,{position:"",inset:"",width:"100%",height:`${c}px`,zIndex:""}))},S=!1,P=()=>{if(!S){S=!0,window.removeEventListener("message",x);for(let u of p.values())u.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));p.clear(),o.remove()}},y=null;function x(u){if(u.source!==r.contentWindow||u.origin!==s||!T(u.data,i))return;let l=u.data;switch(l.kind){case"ready":m({kind:"init",options:{publishableKey:t,clientSecret:n.clientSecret,amount:n.amount,currency:n.currency,installments:n.installments,labels:n.labels,styles:n.styles}});break;case"mounted":y?.({session:l.session});break;case"mount-error":y?.(new a(l.message,l.code));break;case"resize":c=Math.max(0,Math.ceil(l.height)),r.style.position!=="fixed"?r.style.height=`${c}px`:o.style.height=`${c}px`;break;case"validity":n.onValidityChange?.(l.valid);break;case"overlay":f(l.active);break;case"paid":{let g=p.get(l.requestId);p.delete(l.requestId),g?.resolve(l.result);break}case"pay-error":{let g=p.get(l.requestId);p.delete(l.requestId),g?.reject(new a(l.message,l.code));break}}}return window.addEventListener("message",x),new Promise((u,l)=>{let g=setTimeout(()=>{y?.(new a("No se pudo cargar el formulario de tarjeta.","FIELDS_TIMEOUT"))},Y);y=w=>{if(y=null,clearTimeout(g),w instanceof a){P(),l(w);return}u({session:w.session,pay(O){if(S)return Promise.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));let F=R();return new Promise((j,N)=>{p.set(F,{resolve:j,reject:N}),m({kind:"pay",requestId:F,...O?{clientSecret:O}:{}})})},destroy:P})}})}function R(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`}var E={development:"https://api-dev.crosspaysolutions.financial",production:"https://api.crosspaysolutions.financial"};function D(e){let t=E[e];if(!t)throw new TypeError(`Ambiente de Crosspay no valido: "${String(e)}". Usa "development" o "production".`);return t}var h={development:"https://fields-dev.crosspaysolutions.financial/",production:"https://fields.crosspaysolutions.financial/"};function U(e){let t=(s,i)=>(e.fetch??globalThis.fetch)(s,i);async function n(s,i,o){let r={Accept:"application/json"};o!==void 0&&(r["Content-Type"]="application/json"),e.locale&&(r["Accept-Language"]=e.locale);let c;try{c=await t(`${e.baseUrl}${i}`,{method:s,headers:r,body:o===void 0?void 0:JSON.stringify(o),credentials:"omit"})}catch(m){throw new a("No se pudo conectar con Crosspay.","NETWORK_ERROR",m)}let p=await c.json().catch(()=>null);if(!c.ok||p?.success===!1){let m=p?.message||p?.error||`Crosspay respondio ${c.status}.`;throw new a(m,`HTTP_${c.status}`,p)}return p}async function d(s,i,o){let r=await n(s,i,o);return r&&typeof r=="object"&&"data"in r?r.data:r}return{get:s=>d("GET",s),post:(s,i)=>d("POST",s,i),postRaw:(s,i)=>n("POST",s,i)}}var L="cp_device_id";function W(){try{let e=localStorage.getItem(L);return e||(e=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`,localStorage.setItem(L,e)),e}catch{return""}}function G(){return typeof navigator>"u"?"":[navigator.userAgent,navigator.language,typeof screen<"u"?`${screen.width}x${screen.height}`:"",I()].join("|")}function Z(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 I(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function _(){let e=W(),t=Z(G());return e?`${e}.${t}`:t}function J(){let e=typeof navigator<"u"?navigator.language?.split("-")[0]:void 0;return{fingerprint:_(),timezone:I(),...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(s=>{let i=String(s.code??"").trim(),o=String(s.name??"").normalize("NFD").replace(/\p{Diacritic}/gu,"").toLowerCase();return i!==""&&i!=="0"&&!o.includes("seleccione su banco")})},async pay(n){let{clientSecret:d,...s}=n,i=await e.postRaw("/payments/sessions/pay",{publishableKey:t,clientSecret:d,parentOrigin:window.location.origin,method:"pse",pse:s,meta:J()}),{data:o,...r}=i??{},c=o?.sessionId;return{...r,...c?{sessionId:c}:{}}}}}var X={development:"pk_test_",production:"pk_live_"};function $(e){let t=D(e.environment),n=String(e.publishableKey??"").trim(),d=X[e.environment];if(!n.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??h[e.environment],i=U({baseUrl:t,locale:e.locale,fetch:e.fetch});return{environment:e.environment,card:{async mount(o){if(!String(o.clientSecret??"").trim())throw new a("Falta `clientSecret`: crea la sesion de pago en tu servidor con @crosspay-lb/node.","INVALID_REQUEST");let r=await b(s,n,o);return{session:r.session,pay:()=>r.pay(),destroy:r.destroy}},async mountDeferred(o){let{amount:r,currency:c}=ee(o),p=await b(s,n,{...o,amount:r,currency:c});return{amount:r,currency:c,pay(m){let f=String(m?.clientSecret??"").trim();return f?p.pay(f):Promise.reject(new a("Falta `clientSecret`: crea la sesion de pago en tu servidor antes de pagar.","INVALID_REQUEST"))},destroy:p.destroy}}},payments:M(i),pse:A(i,n)}}function ee(e){if("clientSecret"in e)throw new a("`mountDeferred` no recibe `clientSecret`: pasalo en `pay()`, o usa `mount` si ya tienes la sesion.","INVALID_REQUEST");let t=Number(e.amount);if(!Number.isFinite(t)||t<=0)throw new a("`amount` debe ser un numero mayor que cero.","INVALID_REQUEST");let n=String(e.currency??"").trim().toUpperCase();if(!/^[A-Z]{3}$/.test(n))throw new a('`currency` debe ser un codigo ISO 4217, p. ej. "COP".',"INVALID_REQUEST");return{amount:t,currency:n}}var te="0.4.0";0&&(module.exports={API_BASE_URLS,CrosspayError,FIELDS_URLS,init,version});
|
package/dist/index.d.cts
CHANGED
|
@@ -36,6 +36,10 @@ interface PaymentResult {
|
|
|
36
36
|
/** PSE: URL del banco a la que hay que llevar al pagador. */
|
|
37
37
|
redirectUrl?: string;
|
|
38
38
|
declineReason?: string | null;
|
|
39
|
+
/** Sesion de suscripcion: id de la suscripcion creada. */
|
|
40
|
+
subscriptionId?: string;
|
|
41
|
+
/** Sesion de suscripcion: transaccion del primer cobro, si ya se confirmo. */
|
|
42
|
+
firstTransactionId?: string;
|
|
39
43
|
amount?: number;
|
|
40
44
|
currency?: string;
|
|
41
45
|
reference?: string;
|
|
@@ -71,12 +75,24 @@ declare function createPseApi(http: HttpClient, publishableKey: string): {
|
|
|
71
75
|
pay(input: PsePayInput): Promise<PaymentResult>;
|
|
72
76
|
};
|
|
73
77
|
|
|
78
|
+
/** Plan de una sesion de suscripcion. Lo fija el servidor del comercio. */
|
|
79
|
+
interface SubscriptionPlan {
|
|
80
|
+
/** `daily`, `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannual` o `annual`. */
|
|
81
|
+
periodicity: string;
|
|
82
|
+
planName: string;
|
|
83
|
+
}
|
|
74
84
|
/** Lo que la pagina de campos confirma de la sesion al montar. */
|
|
75
85
|
interface SessionSummary {
|
|
76
86
|
sessionId: string;
|
|
77
87
|
amount: number;
|
|
78
88
|
currency: string;
|
|
79
89
|
description: string;
|
|
90
|
+
/**
|
|
91
|
+
* `payment` (cobro unico) o `subscription` (el pago crea una suscripcion y
|
|
92
|
+
* cobra el primer ciclo). Desde 0.3.0; una pagina de campos anterior no lo envia.
|
|
93
|
+
*/
|
|
94
|
+
mode?: 'payment' | 'subscription';
|
|
95
|
+
subscription?: SubscriptionPlan;
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
interface CardFieldLabels {
|
|
@@ -86,15 +102,10 @@ interface CardFieldLabels {
|
|
|
86
102
|
cvv?: string;
|
|
87
103
|
installments?: string;
|
|
88
104
|
}
|
|
89
|
-
|
|
105
|
+
/** Opciones comunes a los dos modos de montaje. */
|
|
106
|
+
interface BaseCardFieldsOptions {
|
|
90
107
|
/** Donde se inserta el formulario: un selector CSS o el elemento. */
|
|
91
108
|
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
109
|
/** Muestra el selector de cuotas. */
|
|
99
110
|
installments?: boolean;
|
|
100
111
|
labels?: CardFieldLabels;
|
|
@@ -107,6 +118,48 @@ interface CardFieldsOptions {
|
|
|
107
118
|
/** Se dispara cada vez que cambia la validez del formulario. */
|
|
108
119
|
onValidityChange?: (isValid: boolean) => void;
|
|
109
120
|
}
|
|
121
|
+
interface CardFieldsOptions extends BaseCardFieldsOptions {
|
|
122
|
+
/**
|
|
123
|
+
* `clientSecret` de la sesion de pago que creo el SERVIDOR del comercio con
|
|
124
|
+
* su llave secreta (`sessions.create` de @crosspay-lb/node). Monto, moneda y
|
|
125
|
+
* cliente salen de la sesion: el navegador no puede cambiarlos.
|
|
126
|
+
*/
|
|
127
|
+
clientSecret: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Montaje diferido (0.4.0): el formulario aparece antes de que exista la
|
|
131
|
+
* sesion, y la sesion se pasa en `pay()`. Sirve para crear la orden recien
|
|
132
|
+
* cuando el pagador le da a pagar.
|
|
133
|
+
*
|
|
134
|
+
* `amount` y `currency` son los que vera el banco en la verificacion 3DS. NO
|
|
135
|
+
* deciden el cobro: al pagar, la pagina de campos compara con la sesion y, si
|
|
136
|
+
* no coinciden, rechaza con `AMOUNT_MISMATCH` sin tocar la tarjeta. Si el total
|
|
137
|
+
* cambia (cupon, envio…), retira el formulario y vuelve a montarlo.
|
|
138
|
+
*
|
|
139
|
+
* Solo cobro unico: una sesion de suscripcion se monta con `clientSecret`.
|
|
140
|
+
*/
|
|
141
|
+
interface DeferredCardFieldsOptions extends BaseCardFieldsOptions {
|
|
142
|
+
amount: number;
|
|
143
|
+
/** ISO 4217, p. ej. `COP` o `USD`. */
|
|
144
|
+
currency: string;
|
|
145
|
+
}
|
|
146
|
+
/** Lo que `pay()` de un montaje diferido necesita. */
|
|
147
|
+
interface DeferredPayInput {
|
|
148
|
+
/** `clientSecret` de la sesion que el servidor acaba de crear para este cobro. */
|
|
149
|
+
clientSecret: string;
|
|
150
|
+
}
|
|
151
|
+
interface DeferredCardFields {
|
|
152
|
+
/** Monto y moneda con que se monto, en mayusculas. */
|
|
153
|
+
readonly amount: number;
|
|
154
|
+
readonly currency: string;
|
|
155
|
+
/**
|
|
156
|
+
* Valida la sesion, tokeniza y cobra. Lanza `CrosspayError` con
|
|
157
|
+
* `AMOUNT_MISMATCH` si la sesion no es del monto y moneda montados.
|
|
158
|
+
*/
|
|
159
|
+
pay(input: DeferredPayInput): Promise<PaymentResult>;
|
|
160
|
+
/** Retira el formulario. */
|
|
161
|
+
destroy(): void;
|
|
162
|
+
}
|
|
110
163
|
interface MountedCardFields {
|
|
111
164
|
/** Monto, moneda y descripcion de la sesion que se va a pagar. */
|
|
112
165
|
readonly session: SessionSummary;
|
|
@@ -167,6 +220,12 @@ interface Crosspay {
|
|
|
167
220
|
* la sesion o el dominio no son validos.
|
|
168
221
|
*/
|
|
169
222
|
mount(options: CardFieldsOptions): Promise<MountedCardFields>;
|
|
223
|
+
/**
|
|
224
|
+
* Monta el formulario ANTES de que exista la sesion, con el monto y la
|
|
225
|
+
* moneda que se van a cobrar; la sesion se pasa en `pay({ clientSecret })`.
|
|
226
|
+
* Rechaza con `CrosspayError` si la llave o el dominio no son validos.
|
|
227
|
+
*/
|
|
228
|
+
mountDeferred(options: DeferredCardFieldsOptions): Promise<DeferredCardFields>;
|
|
170
229
|
};
|
|
171
230
|
payments: ReturnType<typeof createPaymentsApi>;
|
|
172
231
|
pse: ReturnType<typeof createPseApi>;
|
|
@@ -189,4 +248,4 @@ declare class CrosspayError extends Error {
|
|
|
189
248
|
/** Version de la libreria, inyectada en el build. */
|
|
190
249
|
declare const version: string;
|
|
191
250
|
|
|
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 };
|
|
251
|
+
export { API_BASE_URLS, type Bank, type CardFieldLabels, type CardFieldsOptions, type Crosspay, type CrosspayEnvironment, CrosspayError, type CrosspayOptions, type DeferredCardFields, type DeferredCardFieldsOptions, type DeferredPayInput, FIELDS_URLS, type MountedCardFields, type PaymentResult, type PaymentStatus, type PsePayInput, type SessionSummary, init, version };
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ interface PaymentResult {
|
|
|
36
36
|
/** PSE: URL del banco a la que hay que llevar al pagador. */
|
|
37
37
|
redirectUrl?: string;
|
|
38
38
|
declineReason?: string | null;
|
|
39
|
+
/** Sesion de suscripcion: id de la suscripcion creada. */
|
|
40
|
+
subscriptionId?: string;
|
|
41
|
+
/** Sesion de suscripcion: transaccion del primer cobro, si ya se confirmo. */
|
|
42
|
+
firstTransactionId?: string;
|
|
39
43
|
amount?: number;
|
|
40
44
|
currency?: string;
|
|
41
45
|
reference?: string;
|
|
@@ -71,12 +75,24 @@ declare function createPseApi(http: HttpClient, publishableKey: string): {
|
|
|
71
75
|
pay(input: PsePayInput): Promise<PaymentResult>;
|
|
72
76
|
};
|
|
73
77
|
|
|
78
|
+
/** Plan de una sesion de suscripcion. Lo fija el servidor del comercio. */
|
|
79
|
+
interface SubscriptionPlan {
|
|
80
|
+
/** `daily`, `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannual` o `annual`. */
|
|
81
|
+
periodicity: string;
|
|
82
|
+
planName: string;
|
|
83
|
+
}
|
|
74
84
|
/** Lo que la pagina de campos confirma de la sesion al montar. */
|
|
75
85
|
interface SessionSummary {
|
|
76
86
|
sessionId: string;
|
|
77
87
|
amount: number;
|
|
78
88
|
currency: string;
|
|
79
89
|
description: string;
|
|
90
|
+
/**
|
|
91
|
+
* `payment` (cobro unico) o `subscription` (el pago crea una suscripcion y
|
|
92
|
+
* cobra el primer ciclo). Desde 0.3.0; una pagina de campos anterior no lo envia.
|
|
93
|
+
*/
|
|
94
|
+
mode?: 'payment' | 'subscription';
|
|
95
|
+
subscription?: SubscriptionPlan;
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
interface CardFieldLabels {
|
|
@@ -86,15 +102,10 @@ interface CardFieldLabels {
|
|
|
86
102
|
cvv?: string;
|
|
87
103
|
installments?: string;
|
|
88
104
|
}
|
|
89
|
-
|
|
105
|
+
/** Opciones comunes a los dos modos de montaje. */
|
|
106
|
+
interface BaseCardFieldsOptions {
|
|
90
107
|
/** Donde se inserta el formulario: un selector CSS o el elemento. */
|
|
91
108
|
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
109
|
/** Muestra el selector de cuotas. */
|
|
99
110
|
installments?: boolean;
|
|
100
111
|
labels?: CardFieldLabels;
|
|
@@ -107,6 +118,48 @@ interface CardFieldsOptions {
|
|
|
107
118
|
/** Se dispara cada vez que cambia la validez del formulario. */
|
|
108
119
|
onValidityChange?: (isValid: boolean) => void;
|
|
109
120
|
}
|
|
121
|
+
interface CardFieldsOptions extends BaseCardFieldsOptions {
|
|
122
|
+
/**
|
|
123
|
+
* `clientSecret` de la sesion de pago que creo el SERVIDOR del comercio con
|
|
124
|
+
* su llave secreta (`sessions.create` de @crosspay-lb/node). Monto, moneda y
|
|
125
|
+
* cliente salen de la sesion: el navegador no puede cambiarlos.
|
|
126
|
+
*/
|
|
127
|
+
clientSecret: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Montaje diferido (0.4.0): el formulario aparece antes de que exista la
|
|
131
|
+
* sesion, y la sesion se pasa en `pay()`. Sirve para crear la orden recien
|
|
132
|
+
* cuando el pagador le da a pagar.
|
|
133
|
+
*
|
|
134
|
+
* `amount` y `currency` son los que vera el banco en la verificacion 3DS. NO
|
|
135
|
+
* deciden el cobro: al pagar, la pagina de campos compara con la sesion y, si
|
|
136
|
+
* no coinciden, rechaza con `AMOUNT_MISMATCH` sin tocar la tarjeta. Si el total
|
|
137
|
+
* cambia (cupon, envio…), retira el formulario y vuelve a montarlo.
|
|
138
|
+
*
|
|
139
|
+
* Solo cobro unico: una sesion de suscripcion se monta con `clientSecret`.
|
|
140
|
+
*/
|
|
141
|
+
interface DeferredCardFieldsOptions extends BaseCardFieldsOptions {
|
|
142
|
+
amount: number;
|
|
143
|
+
/** ISO 4217, p. ej. `COP` o `USD`. */
|
|
144
|
+
currency: string;
|
|
145
|
+
}
|
|
146
|
+
/** Lo que `pay()` de un montaje diferido necesita. */
|
|
147
|
+
interface DeferredPayInput {
|
|
148
|
+
/** `clientSecret` de la sesion que el servidor acaba de crear para este cobro. */
|
|
149
|
+
clientSecret: string;
|
|
150
|
+
}
|
|
151
|
+
interface DeferredCardFields {
|
|
152
|
+
/** Monto y moneda con que se monto, en mayusculas. */
|
|
153
|
+
readonly amount: number;
|
|
154
|
+
readonly currency: string;
|
|
155
|
+
/**
|
|
156
|
+
* Valida la sesion, tokeniza y cobra. Lanza `CrosspayError` con
|
|
157
|
+
* `AMOUNT_MISMATCH` si la sesion no es del monto y moneda montados.
|
|
158
|
+
*/
|
|
159
|
+
pay(input: DeferredPayInput): Promise<PaymentResult>;
|
|
160
|
+
/** Retira el formulario. */
|
|
161
|
+
destroy(): void;
|
|
162
|
+
}
|
|
110
163
|
interface MountedCardFields {
|
|
111
164
|
/** Monto, moneda y descripcion de la sesion que se va a pagar. */
|
|
112
165
|
readonly session: SessionSummary;
|
|
@@ -167,6 +220,12 @@ interface Crosspay {
|
|
|
167
220
|
* la sesion o el dominio no son validos.
|
|
168
221
|
*/
|
|
169
222
|
mount(options: CardFieldsOptions): Promise<MountedCardFields>;
|
|
223
|
+
/**
|
|
224
|
+
* Monta el formulario ANTES de que exista la sesion, con el monto y la
|
|
225
|
+
* moneda que se van a cobrar; la sesion se pasa en `pay({ clientSecret })`.
|
|
226
|
+
* Rechaza con `CrosspayError` si la llave o el dominio no son validos.
|
|
227
|
+
*/
|
|
228
|
+
mountDeferred(options: DeferredCardFieldsOptions): Promise<DeferredCardFields>;
|
|
170
229
|
};
|
|
171
230
|
payments: ReturnType<typeof createPaymentsApi>;
|
|
172
231
|
pse: ReturnType<typeof createPseApi>;
|
|
@@ -189,4 +248,4 @@ declare class CrosspayError extends Error {
|
|
|
189
248
|
/** Version de la libreria, inyectada en el build. */
|
|
190
249
|
declare const version: string;
|
|
191
250
|
|
|
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 };
|
|
251
|
+
export { API_BASE_URLS, type Bank, type CardFieldLabels, type CardFieldsOptions, type Crosspay, type CrosspayEnvironment, CrosspayError, type CrosspayOptions, type DeferredCardFields, type DeferredCardFieldsOptions, type DeferredPayInput, FIELDS_URLS, type MountedCardFields, type PaymentResult, type PaymentStatus, type PsePayInput, type SessionSummary, init, version };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var a=class extends Error{constructor(t,d,s){super(t);this.code=d;this.details=s;this.name="CrosspayError"}};var w="crosspay-fields/2";function O(e,r){return typeof e=="object"&&e!==null&&e.protocol===w&&e.channel===r&&typeof e.kind=="string"}function F(e){return new URLSearchParams({channel:e.channel,origin:e.parentOrigin}).toString()}var j=3e4,N={position:"fixed",inset:"0",width:"100%",height:"100%",zIndex:"2147483647"};function v(e,r,t){let d=typeof t.container=="string"?document.querySelector(t.container):t.container;if(!d)return Promise.reject(new a(`No existe el contenedor ${String(t.container)}.`,"INVALID_CONTAINER"));let s=new URL(e).origin,i=T(),o=document.createElement("div");o.style.position="relative";let n=document.createElement("iframe");n.src=`${e}#${F({channel:i,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"}),o.appendChild(n),d.replaceChildren(o);let c=0,p=new Map,m=u=>{n.contentWindow?.postMessage({...u,protocol:w,channel:i},s)},f=u=>{u?(o.style.height=`${c}px`,Object.assign(n.style,N)):(o.style.height="",Object.assign(n.style,{position:"",inset:"",width:"100%",height:`${c}px`,zIndex:""}))},h=!1,E=()=>{if(!h){h=!0,window.removeEventListener("message",I);for(let u of p.values())u.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));p.clear(),o.remove()}},y=null;function I(u){if(u.source!==n.contentWindow||u.origin!==s||!O(u.data,i))return;let l=u.data;switch(l.kind){case"ready":m({kind:"init",options:{publishableKey:r,clientSecret:t.clientSecret,amount:t.amount,currency:t.currency,installments:t.installments,labels:t.labels,styles:t.styles}});break;case"mounted":y?.({session:l.session});break;case"mount-error":y?.(new a(l.message,l.code));break;case"resize":c=Math.max(0,Math.ceil(l.height)),n.style.position!=="fixed"?n.style.height=`${c}px`:o.style.height=`${c}px`;break;case"validity":t.onValidityChange?.(l.valid);break;case"overlay":f(l.active);break;case"paid":{let g=p.get(l.requestId);p.delete(l.requestId),g?.resolve(l.result);break}case"pay-error":{let g=p.get(l.requestId);p.delete(l.requestId),g?.reject(new a(l.message,l.code));break}}}return window.addEventListener("message",I),new Promise((u,l)=>{let g=setTimeout(()=>{y?.(new a("No se pudo cargar el formulario de tarjeta.","FIELDS_TIMEOUT"))},j);y=S=>{if(y=null,clearTimeout(g),S instanceof a){E(),l(S);return}u({session:S.session,pay(P){if(h)return Promise.reject(new a("El formulario de tarjeta se retiro.","FIELDS_DESTROYED"));let x=T();return new Promise((A,$)=>{p.set(x,{resolve:A,reject:$}),m({kind:"pay",requestId:x,...P?{clientSecret:P}:{}})})},destroy:E})}})}function T(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`}var k={development:"https://api-dev.crosspaysolutions.financial",production:"https://api.crosspaysolutions.financial"};function R(e){let r=k[e];if(!r)throw new TypeError(`Ambiente de Crosspay no valido: "${String(e)}". Usa "development" o "production".`);return r}var C={development:"https://fields-dev.crosspaysolutions.financial/",production:"https://fields.crosspaysolutions.financial/"};function D(e){let r=(s,i)=>(e.fetch??globalThis.fetch)(s,i);async function t(s,i,o){let n={Accept:"application/json"};o!==void 0&&(n["Content-Type"]="application/json"),e.locale&&(n["Accept-Language"]=e.locale);let c;try{c=await r(`${e.baseUrl}${i}`,{method:s,headers:n,body:o===void 0?void 0:JSON.stringify(o),credentials:"omit"})}catch(m){throw new a("No se pudo conectar con Crosspay.","NETWORK_ERROR",m)}let p=await c.json().catch(()=>null);if(!c.ok||p?.success===!1){let m=p?.message||p?.error||`Crosspay respondio ${c.status}.`;throw new a(m,`HTTP_${c.status}`,p)}return p}async function d(s,i,o){let n=await t(s,i,o);return n&&typeof n=="object"&&"data"in n?n.data:n}return{get:s=>d("GET",s),post:(s,i)=>d("POST",s,i),postRaw:(s,i)=>t("POST",s,i)}}var U="cp_device_id";function B(){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 q(){return typeof navigator>"u"?"":[navigator.userAgent,navigator.language,typeof screen<"u"?`${screen.width}x${screen.height}`:"",b()].join("|")}function H(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 b(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}function L(){let e=B(),r=H(q());return e?`${e}.${r}`:r}function z(){let e=typeof navigator<"u"?navigator.language?.split("-")[0]:void 0;return{fingerprint:L(),timezone:b(),...e?{language:e}:{}}}function _(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 i=String(s.code??"").trim(),o=String(s.name??"").normalize("NFD").replace(/\p{Diacritic}/gu,"").toLowerCase();return i!==""&&i!=="0"&&!o.includes("seleccione su banco")})},async pay(t){let{clientSecret:d,...s}=t,i=await e.postRaw("/payments/sessions/pay",{publishableKey:r,clientSecret:d,parentOrigin:window.location.origin,method:"pse",pse:s,meta:z()}),{data:o,...n}=i??{},c=o?.sessionId;return{...n,...c?{sessionId:c}:{}}}}}var V={development:"pk_test_",production:"pk_live_"};function K(e){let r=R(e.environment),t=String(e.publishableKey??"").trim(),d=V[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??C[e.environment],i=D({baseUrl:r,locale:e.locale,fetch:e.fetch});return{environment:e.environment,card:{async mount(o){if(!String(o.clientSecret??"").trim())throw new a("Falta `clientSecret`: crea la sesion de pago en tu servidor con @crosspay-lb/node.","INVALID_REQUEST");let n=await v(s,t,o);return{session:n.session,pay:()=>n.pay(),destroy:n.destroy}},async mountDeferred(o){let{amount:n,currency:c}=Y(o),p=await v(s,t,{...o,amount:n,currency:c});return{amount:n,currency:c,pay(m){let f=String(m?.clientSecret??"").trim();return f?p.pay(f):Promise.reject(new a("Falta `clientSecret`: crea la sesion de pago en tu servidor antes de pagar.","INVALID_REQUEST"))},destroy:p.destroy}}},payments:_(i),pse:M(i,t)}}function Y(e){if("clientSecret"in e)throw new a("`mountDeferred` no recibe `clientSecret`: pasalo en `pay()`, o usa `mount` si ya tienes la sesion.","INVALID_REQUEST");let r=Number(e.amount);if(!Number.isFinite(r)||r<=0)throw new a("`amount` debe ser un numero mayor que cero.","INVALID_REQUEST");let t=String(e.currency??"").trim().toUpperCase();if(!/^[A-Z]{3}$/.test(t))throw new a('`currency` debe ser un codigo ISO 4217, p. ej. "COP".',"INVALID_REQUEST");return{amount:r,currency:t}}var de="0.4.0";export{k as API_BASE_URLS,a as CrosspayError,C as FIELDS_URLS,K as init,de as version};
|
package/package.json
CHANGED