@pandait.tech/payment-deuna 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ // src/client.ts
4
+ function createDeUnaClient(config) {
5
+ const base = config.baseUrl ?? "https://api.deuna.io/v1";
6
+ const headers = {
7
+ "X-Client-Id": config.clientId,
8
+ "X-Client-Secret": config.clientSecret,
9
+ "Content-Type": "application/json"
10
+ };
11
+ return {
12
+ async prepare(params, mode = "qr") {
13
+ const res = await fetch(`${base}/payments/prepare`, {
14
+ method: "POST",
15
+ headers,
16
+ body: JSON.stringify({
17
+ orderRef: params.orderRef,
18
+ amount: params.amount,
19
+ currency: "USD",
20
+ description: params.description ?? params.orderRef,
21
+ mode,
22
+ webhookUrl: config.webhookUrl,
23
+ returnUrl: config.returnUrl
24
+ })
25
+ });
26
+ if (!res.ok) {
27
+ const text = await res.text().catch(() => "");
28
+ throw new Error(`DeUna prepare ${res.status}: ${text}`);
29
+ }
30
+ return res.json();
31
+ },
32
+ async getStatus(orderRef) {
33
+ const url = `${base}/payments/status?orderRef=${encodeURIComponent(orderRef)}`;
34
+ const res = await fetch(url, { method: "GET", headers });
35
+ if (!res.ok) {
36
+ const text = await res.text().catch(() => "");
37
+ throw new Error(`DeUna getStatus ${res.status}: ${text}`);
38
+ }
39
+ return res.json();
40
+ }
41
+ };
42
+ }
43
+
44
+ // src/adapter.ts
45
+ var PAID_STATUS = "PAID";
46
+ function toConfirmResult(s) {
47
+ if (s.status === PAID_STATUS) return { status: "paid", transactionId: s.transactionId };
48
+ if (s.status === "FAILED" || s.status === "EXPIRED")
49
+ return { status: "failed", error: s.message ?? s.status };
50
+ return { status: "pending" };
51
+ }
52
+ function toDeUnaPaymentMethod(client, opts) {
53
+ const processed = opts?.processedRefs ?? /* @__PURE__ */ new Set();
54
+ return {
55
+ id: "deuna",
56
+ label: "DeUna (wallet QR \xB7 Banco Pichincha)",
57
+ async prepare(order) {
58
+ const result = await client.prepare({
59
+ orderRef: order.id,
60
+ amount: order.total,
61
+ description: `Orden ${order.id}`
62
+ });
63
+ return {
64
+ qrPayload: result.qrPayload,
65
+ paymentUrl: result.paymentUrl,
66
+ paymentCode: result.paymentCode,
67
+ transactionId: result.transactionId,
68
+ orderId: order.id
69
+ };
70
+ },
71
+ async confirm(ref) {
72
+ if (opts?.onConfirm) return opts.onConfirm(ref);
73
+ return toConfirmResult(await client.getStatus(ref));
74
+ },
75
+ async handleWebhook(payload) {
76
+ const p = payload;
77
+ const orderId = p?.orderRef;
78
+ if (!orderId) return { handled: false };
79
+ if (processed.has(orderId)) return { handled: true, orderId };
80
+ const status = p?.status ?? (await client.getStatus(orderId)).status;
81
+ if (status !== PAID_STATUS) return { handled: false };
82
+ processed.add(orderId);
83
+ return { handled: true, orderId };
84
+ }
85
+ };
86
+ }
87
+
88
+ exports.createDeUnaClient = createDeUnaClient;
89
+ exports.toDeUnaPaymentMethod = toDeUnaPaymentMethod;
90
+ //# sourceMappingURL=index.cjs.map
91
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/adapter.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// DeUna (Banco Pichincha) — cliente HTTP mínimo\n// Wallet QR: genera QR dinámico o link de pago y consulta estado del cobro.\n\nexport interface DeUnaConfig {\n /** Client ID del comercio (DEUNA_CLIENT_ID). */\n clientId: string;\n /** Client Secret del comercio (DEUNA_CLIENT_SECRET). */\n clientSecret: string;\n /** URL a la que DeUna notificará el resultado del pago (webhook). */\n webhookUrl: string;\n /** URL de retorno después del pago (para link/código). */\n returnUrl: string;\n /** ponytail: base URL sobreescribible para tests/staging. */\n baseUrl?: string;\n}\n\nexport interface PrepareParams {\n /** Referencia única de la orden. */\n orderRef: string;\n /** Monto total en la moneda del comercio (USD). */\n amount: number;\n /** Descripción del pedido (aparece en la pantalla del wallet). */\n description?: string;\n}\n\n/** Modos de cobro que DeUna soporta. */\nexport type DeUnaPrepareMode = \"qr\" | \"link\" | \"code\";\n\nexport interface PrepareResponse {\n /** Payload del QR dinámico (string para que la UI genere la imagen). */\n qrPayload?: string;\n /** Link de pago (alternativa al QR). */\n paymentUrl?: string;\n /** Código único de 6 dígitos. */\n paymentCode?: string;\n /** ID interno de la transacción en DeUna. */\n transactionId: string;\n}\n\nexport interface TransactionStatus {\n transactionId: string;\n orderRef: string;\n /** \"PAID\" | \"PENDING\" | \"FAILED\" | \"EXPIRED\" */\n status: string;\n authorizationCode?: string;\n message?: string;\n}\n\nexport interface DeUnaClient {\n /** Genera un QR dinámico (y opcionalmente link/código) para cobrar la orden. */\n prepare(params: PrepareParams, mode?: DeUnaPrepareMode): Promise<PrepareResponse>;\n /** Consulta el estado actual de la transacción por orderRef. */\n getStatus(orderRef: string): Promise<TransactionStatus>;\n}\n\nexport function createDeUnaClient(config: DeUnaConfig): DeUnaClient {\n const base = config.baseUrl ?? \"https://api.deuna.io/v1\";\n const headers = {\n \"X-Client-Id\": config.clientId,\n \"X-Client-Secret\": config.clientSecret,\n \"Content-Type\": \"application/json\",\n };\n\n return {\n async prepare(params, mode = \"qr\") {\n const res = await fetch(`${base}/payments/prepare`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n orderRef: params.orderRef,\n amount: params.amount,\n currency: \"USD\",\n description: params.description ?? params.orderRef,\n mode,\n webhookUrl: config.webhookUrl,\n returnUrl: config.returnUrl,\n }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`DeUna prepare ${res.status}: ${text}`);\n }\n return res.json() as Promise<PrepareResponse>;\n },\n\n async getStatus(orderRef) {\n const url = `${base}/payments/status?orderRef=${encodeURIComponent(orderRef)}`;\n const res = await fetch(url, { method: \"GET\", headers });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`DeUna getStatus ${res.status}: ${text}`);\n }\n return res.json() as Promise<TransactionStatus>;\n },\n };\n}\n","// Tipos estructuralmente compatibles con payment-core (sin importarlo — evita ciclo).\nimport type { Order } from \"@pandait.tech/commerce-core\";\nimport type { DeUnaClient, TransactionStatus } from \"./client.js\";\n\ninterface ConfirmResult {\n status: \"paid\" | \"pending\" | \"failed\" | \"challenge\";\n transactionId?: string;\n redirectUrl?: string;\n error?: string;\n}\ninterface WebhookResult {\n handled: boolean;\n orderId?: string;\n}\ninterface PaymentMethod {\n id: string;\n label: string;\n prepare(order: Order): Promise<Record<string, unknown>>;\n confirm(ref: string): Promise<ConfirmResult>;\n handleWebhook(payload: unknown): Promise<WebhookResult>;\n}\n\nconst PAID_STATUS = \"PAID\";\n\nfunction toConfirmResult(s: TransactionStatus): ConfirmResult {\n if (s.status === PAID_STATUS) return { status: \"paid\", transactionId: s.transactionId };\n if (s.status === \"FAILED\" || s.status === \"EXPIRED\")\n return { status: \"failed\", error: s.message ?? s.status };\n return { status: \"pending\" };\n}\n\nexport interface DeUnaWebhookPayload {\n orderRef?: string;\n transactionId?: string;\n status?: string;\n authorizationCode?: string;\n}\n\nexport interface DeUnaPaymentMethodOpts {\n /**\n * Set inyectable para idempotencia de webhooks.\n * ponytail: en memoria por defecto; reemplaza con persistencia en DB para multi-instancia.\n */\n processedRefs?: Set<string>;\n /** Override de confirm para entornos sin acceso directo a la API (tests, staging). */\n onConfirm?: (ref: string) => Promise<ConfirmResult>;\n}\n\nexport function toDeUnaPaymentMethod(\n client: DeUnaClient,\n opts?: DeUnaPaymentMethodOpts,\n): PaymentMethod {\n // ponytail: in-memory; reemplazar con Set persistido en DB en prod multi-instancia\n const processed = opts?.processedRefs ?? new Set<string>();\n\n return {\n id: \"deuna\",\n label: \"DeUna (wallet QR · Banco Pichincha)\",\n\n async prepare(order: Order): Promise<Record<string, unknown>> {\n const result = await client.prepare({\n orderRef: order.id,\n amount: order.total,\n description: `Orden ${order.id}`,\n });\n // Devuelve el payload/string del QR — la UI genera la imagen (ponytail: no generar aquí).\n return {\n qrPayload: result.qrPayload,\n paymentUrl: result.paymentUrl,\n paymentCode: result.paymentCode,\n transactionId: result.transactionId,\n orderId: order.id,\n };\n },\n\n async confirm(ref: string): Promise<ConfirmResult> {\n if (opts?.onConfirm) return opts.onConfirm(ref);\n return toConfirmResult(await client.getStatus(ref));\n },\n\n async handleWebhook(payload: unknown): Promise<WebhookResult> {\n const p = payload as DeUnaWebhookPayload | null;\n const orderId = p?.orderRef;\n if (!orderId) return { handled: false };\n\n // Idempotencia: misma referencia no se procesa dos veces\n if (processed.has(orderId)) return { handled: true, orderId };\n\n // Si el webhook trae el status directamente lo usamos; si no, consultamos\n const status = p?.status ?? (await client.getStatus(orderId)).status;\n\n if (status !== PAID_STATUS) return { handled: false };\n\n processed.add(orderId);\n return { handled: true, orderId };\n },\n };\n}\n"]}
@@ -0,0 +1,85 @@
1
+ import { Order } from '@pandait.tech/commerce-core';
2
+
3
+ interface DeUnaConfig {
4
+ /** Client ID del comercio (DEUNA_CLIENT_ID). */
5
+ clientId: string;
6
+ /** Client Secret del comercio (DEUNA_CLIENT_SECRET). */
7
+ clientSecret: string;
8
+ /** URL a la que DeUna notificará el resultado del pago (webhook). */
9
+ webhookUrl: string;
10
+ /** URL de retorno después del pago (para link/código). */
11
+ returnUrl: string;
12
+ /** ponytail: base URL sobreescribible para tests/staging. */
13
+ baseUrl?: string;
14
+ }
15
+ interface PrepareParams {
16
+ /** Referencia única de la orden. */
17
+ orderRef: string;
18
+ /** Monto total en la moneda del comercio (USD). */
19
+ amount: number;
20
+ /** Descripción del pedido (aparece en la pantalla del wallet). */
21
+ description?: string;
22
+ }
23
+ /** Modos de cobro que DeUna soporta. */
24
+ type DeUnaPrepareMode = "qr" | "link" | "code";
25
+ interface PrepareResponse {
26
+ /** Payload del QR dinámico (string para que la UI genere la imagen). */
27
+ qrPayload?: string;
28
+ /** Link de pago (alternativa al QR). */
29
+ paymentUrl?: string;
30
+ /** Código único de 6 dígitos. */
31
+ paymentCode?: string;
32
+ /** ID interno de la transacción en DeUna. */
33
+ transactionId: string;
34
+ }
35
+ interface TransactionStatus {
36
+ transactionId: string;
37
+ orderRef: string;
38
+ /** "PAID" | "PENDING" | "FAILED" | "EXPIRED" */
39
+ status: string;
40
+ authorizationCode?: string;
41
+ message?: string;
42
+ }
43
+ interface DeUnaClient {
44
+ /** Genera un QR dinámico (y opcionalmente link/código) para cobrar la orden. */
45
+ prepare(params: PrepareParams, mode?: DeUnaPrepareMode): Promise<PrepareResponse>;
46
+ /** Consulta el estado actual de la transacción por orderRef. */
47
+ getStatus(orderRef: string): Promise<TransactionStatus>;
48
+ }
49
+ declare function createDeUnaClient(config: DeUnaConfig): DeUnaClient;
50
+
51
+ interface ConfirmResult {
52
+ status: "paid" | "pending" | "failed" | "challenge";
53
+ transactionId?: string;
54
+ redirectUrl?: string;
55
+ error?: string;
56
+ }
57
+ interface WebhookResult {
58
+ handled: boolean;
59
+ orderId?: string;
60
+ }
61
+ interface PaymentMethod {
62
+ id: string;
63
+ label: string;
64
+ prepare(order: Order): Promise<Record<string, unknown>>;
65
+ confirm(ref: string): Promise<ConfirmResult>;
66
+ handleWebhook(payload: unknown): Promise<WebhookResult>;
67
+ }
68
+ interface DeUnaWebhookPayload {
69
+ orderRef?: string;
70
+ transactionId?: string;
71
+ status?: string;
72
+ authorizationCode?: string;
73
+ }
74
+ interface DeUnaPaymentMethodOpts {
75
+ /**
76
+ * Set inyectable para idempotencia de webhooks.
77
+ * ponytail: en memoria por defecto; reemplaza con persistencia en DB para multi-instancia.
78
+ */
79
+ processedRefs?: Set<string>;
80
+ /** Override de confirm para entornos sin acceso directo a la API (tests, staging). */
81
+ onConfirm?: (ref: string) => Promise<ConfirmResult>;
82
+ }
83
+ declare function toDeUnaPaymentMethod(client: DeUnaClient, opts?: DeUnaPaymentMethodOpts): PaymentMethod;
84
+
85
+ export { type DeUnaClient, type DeUnaConfig, type DeUnaPaymentMethodOpts, type DeUnaPrepareMode, type DeUnaWebhookPayload, type PrepareParams, type PrepareResponse, type TransactionStatus, createDeUnaClient, toDeUnaPaymentMethod };
@@ -0,0 +1,85 @@
1
+ import { Order } from '@pandait.tech/commerce-core';
2
+
3
+ interface DeUnaConfig {
4
+ /** Client ID del comercio (DEUNA_CLIENT_ID). */
5
+ clientId: string;
6
+ /** Client Secret del comercio (DEUNA_CLIENT_SECRET). */
7
+ clientSecret: string;
8
+ /** URL a la que DeUna notificará el resultado del pago (webhook). */
9
+ webhookUrl: string;
10
+ /** URL de retorno después del pago (para link/código). */
11
+ returnUrl: string;
12
+ /** ponytail: base URL sobreescribible para tests/staging. */
13
+ baseUrl?: string;
14
+ }
15
+ interface PrepareParams {
16
+ /** Referencia única de la orden. */
17
+ orderRef: string;
18
+ /** Monto total en la moneda del comercio (USD). */
19
+ amount: number;
20
+ /** Descripción del pedido (aparece en la pantalla del wallet). */
21
+ description?: string;
22
+ }
23
+ /** Modos de cobro que DeUna soporta. */
24
+ type DeUnaPrepareMode = "qr" | "link" | "code";
25
+ interface PrepareResponse {
26
+ /** Payload del QR dinámico (string para que la UI genere la imagen). */
27
+ qrPayload?: string;
28
+ /** Link de pago (alternativa al QR). */
29
+ paymentUrl?: string;
30
+ /** Código único de 6 dígitos. */
31
+ paymentCode?: string;
32
+ /** ID interno de la transacción en DeUna. */
33
+ transactionId: string;
34
+ }
35
+ interface TransactionStatus {
36
+ transactionId: string;
37
+ orderRef: string;
38
+ /** "PAID" | "PENDING" | "FAILED" | "EXPIRED" */
39
+ status: string;
40
+ authorizationCode?: string;
41
+ message?: string;
42
+ }
43
+ interface DeUnaClient {
44
+ /** Genera un QR dinámico (y opcionalmente link/código) para cobrar la orden. */
45
+ prepare(params: PrepareParams, mode?: DeUnaPrepareMode): Promise<PrepareResponse>;
46
+ /** Consulta el estado actual de la transacción por orderRef. */
47
+ getStatus(orderRef: string): Promise<TransactionStatus>;
48
+ }
49
+ declare function createDeUnaClient(config: DeUnaConfig): DeUnaClient;
50
+
51
+ interface ConfirmResult {
52
+ status: "paid" | "pending" | "failed" | "challenge";
53
+ transactionId?: string;
54
+ redirectUrl?: string;
55
+ error?: string;
56
+ }
57
+ interface WebhookResult {
58
+ handled: boolean;
59
+ orderId?: string;
60
+ }
61
+ interface PaymentMethod {
62
+ id: string;
63
+ label: string;
64
+ prepare(order: Order): Promise<Record<string, unknown>>;
65
+ confirm(ref: string): Promise<ConfirmResult>;
66
+ handleWebhook(payload: unknown): Promise<WebhookResult>;
67
+ }
68
+ interface DeUnaWebhookPayload {
69
+ orderRef?: string;
70
+ transactionId?: string;
71
+ status?: string;
72
+ authorizationCode?: string;
73
+ }
74
+ interface DeUnaPaymentMethodOpts {
75
+ /**
76
+ * Set inyectable para idempotencia de webhooks.
77
+ * ponytail: en memoria por defecto; reemplaza con persistencia en DB para multi-instancia.
78
+ */
79
+ processedRefs?: Set<string>;
80
+ /** Override de confirm para entornos sin acceso directo a la API (tests, staging). */
81
+ onConfirm?: (ref: string) => Promise<ConfirmResult>;
82
+ }
83
+ declare function toDeUnaPaymentMethod(client: DeUnaClient, opts?: DeUnaPaymentMethodOpts): PaymentMethod;
84
+
85
+ export { type DeUnaClient, type DeUnaConfig, type DeUnaPaymentMethodOpts, type DeUnaPrepareMode, type DeUnaWebhookPayload, type PrepareParams, type PrepareResponse, type TransactionStatus, createDeUnaClient, toDeUnaPaymentMethod };
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ // src/client.ts
2
+ function createDeUnaClient(config) {
3
+ const base = config.baseUrl ?? "https://api.deuna.io/v1";
4
+ const headers = {
5
+ "X-Client-Id": config.clientId,
6
+ "X-Client-Secret": config.clientSecret,
7
+ "Content-Type": "application/json"
8
+ };
9
+ return {
10
+ async prepare(params, mode = "qr") {
11
+ const res = await fetch(`${base}/payments/prepare`, {
12
+ method: "POST",
13
+ headers,
14
+ body: JSON.stringify({
15
+ orderRef: params.orderRef,
16
+ amount: params.amount,
17
+ currency: "USD",
18
+ description: params.description ?? params.orderRef,
19
+ mode,
20
+ webhookUrl: config.webhookUrl,
21
+ returnUrl: config.returnUrl
22
+ })
23
+ });
24
+ if (!res.ok) {
25
+ const text = await res.text().catch(() => "");
26
+ throw new Error(`DeUna prepare ${res.status}: ${text}`);
27
+ }
28
+ return res.json();
29
+ },
30
+ async getStatus(orderRef) {
31
+ const url = `${base}/payments/status?orderRef=${encodeURIComponent(orderRef)}`;
32
+ const res = await fetch(url, { method: "GET", headers });
33
+ if (!res.ok) {
34
+ const text = await res.text().catch(() => "");
35
+ throw new Error(`DeUna getStatus ${res.status}: ${text}`);
36
+ }
37
+ return res.json();
38
+ }
39
+ };
40
+ }
41
+
42
+ // src/adapter.ts
43
+ var PAID_STATUS = "PAID";
44
+ function toConfirmResult(s) {
45
+ if (s.status === PAID_STATUS) return { status: "paid", transactionId: s.transactionId };
46
+ if (s.status === "FAILED" || s.status === "EXPIRED")
47
+ return { status: "failed", error: s.message ?? s.status };
48
+ return { status: "pending" };
49
+ }
50
+ function toDeUnaPaymentMethod(client, opts) {
51
+ const processed = opts?.processedRefs ?? /* @__PURE__ */ new Set();
52
+ return {
53
+ id: "deuna",
54
+ label: "DeUna (wallet QR \xB7 Banco Pichincha)",
55
+ async prepare(order) {
56
+ const result = await client.prepare({
57
+ orderRef: order.id,
58
+ amount: order.total,
59
+ description: `Orden ${order.id}`
60
+ });
61
+ return {
62
+ qrPayload: result.qrPayload,
63
+ paymentUrl: result.paymentUrl,
64
+ paymentCode: result.paymentCode,
65
+ transactionId: result.transactionId,
66
+ orderId: order.id
67
+ };
68
+ },
69
+ async confirm(ref) {
70
+ if (opts?.onConfirm) return opts.onConfirm(ref);
71
+ return toConfirmResult(await client.getStatus(ref));
72
+ },
73
+ async handleWebhook(payload) {
74
+ const p = payload;
75
+ const orderId = p?.orderRef;
76
+ if (!orderId) return { handled: false };
77
+ if (processed.has(orderId)) return { handled: true, orderId };
78
+ const status = p?.status ?? (await client.getStatus(orderId)).status;
79
+ if (status !== PAID_STATUS) return { handled: false };
80
+ processed.add(orderId);
81
+ return { handled: true, orderId };
82
+ }
83
+ };
84
+ }
85
+
86
+ export { createDeUnaClient, toDeUnaPaymentMethod };
87
+ //# sourceMappingURL=index.js.map
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/adapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// DeUna (Banco Pichincha) — cliente HTTP mínimo\n// Wallet QR: genera QR dinámico o link de pago y consulta estado del cobro.\n\nexport interface DeUnaConfig {\n /** Client ID del comercio (DEUNA_CLIENT_ID). */\n clientId: string;\n /** Client Secret del comercio (DEUNA_CLIENT_SECRET). */\n clientSecret: string;\n /** URL a la que DeUna notificará el resultado del pago (webhook). */\n webhookUrl: string;\n /** URL de retorno después del pago (para link/código). */\n returnUrl: string;\n /** ponytail: base URL sobreescribible para tests/staging. */\n baseUrl?: string;\n}\n\nexport interface PrepareParams {\n /** Referencia única de la orden. */\n orderRef: string;\n /** Monto total en la moneda del comercio (USD). */\n amount: number;\n /** Descripción del pedido (aparece en la pantalla del wallet). */\n description?: string;\n}\n\n/** Modos de cobro que DeUna soporta. */\nexport type DeUnaPrepareMode = \"qr\" | \"link\" | \"code\";\n\nexport interface PrepareResponse {\n /** Payload del QR dinámico (string para que la UI genere la imagen). */\n qrPayload?: string;\n /** Link de pago (alternativa al QR). */\n paymentUrl?: string;\n /** Código único de 6 dígitos. */\n paymentCode?: string;\n /** ID interno de la transacción en DeUna. */\n transactionId: string;\n}\n\nexport interface TransactionStatus {\n transactionId: string;\n orderRef: string;\n /** \"PAID\" | \"PENDING\" | \"FAILED\" | \"EXPIRED\" */\n status: string;\n authorizationCode?: string;\n message?: string;\n}\n\nexport interface DeUnaClient {\n /** Genera un QR dinámico (y opcionalmente link/código) para cobrar la orden. */\n prepare(params: PrepareParams, mode?: DeUnaPrepareMode): Promise<PrepareResponse>;\n /** Consulta el estado actual de la transacción por orderRef. */\n getStatus(orderRef: string): Promise<TransactionStatus>;\n}\n\nexport function createDeUnaClient(config: DeUnaConfig): DeUnaClient {\n const base = config.baseUrl ?? \"https://api.deuna.io/v1\";\n const headers = {\n \"X-Client-Id\": config.clientId,\n \"X-Client-Secret\": config.clientSecret,\n \"Content-Type\": \"application/json\",\n };\n\n return {\n async prepare(params, mode = \"qr\") {\n const res = await fetch(`${base}/payments/prepare`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n orderRef: params.orderRef,\n amount: params.amount,\n currency: \"USD\",\n description: params.description ?? params.orderRef,\n mode,\n webhookUrl: config.webhookUrl,\n returnUrl: config.returnUrl,\n }),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`DeUna prepare ${res.status}: ${text}`);\n }\n return res.json() as Promise<PrepareResponse>;\n },\n\n async getStatus(orderRef) {\n const url = `${base}/payments/status?orderRef=${encodeURIComponent(orderRef)}`;\n const res = await fetch(url, { method: \"GET\", headers });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`DeUna getStatus ${res.status}: ${text}`);\n }\n return res.json() as Promise<TransactionStatus>;\n },\n };\n}\n","// Tipos estructuralmente compatibles con payment-core (sin importarlo — evita ciclo).\nimport type { Order } from \"@pandait.tech/commerce-core\";\nimport type { DeUnaClient, TransactionStatus } from \"./client.js\";\n\ninterface ConfirmResult {\n status: \"paid\" | \"pending\" | \"failed\" | \"challenge\";\n transactionId?: string;\n redirectUrl?: string;\n error?: string;\n}\ninterface WebhookResult {\n handled: boolean;\n orderId?: string;\n}\ninterface PaymentMethod {\n id: string;\n label: string;\n prepare(order: Order): Promise<Record<string, unknown>>;\n confirm(ref: string): Promise<ConfirmResult>;\n handleWebhook(payload: unknown): Promise<WebhookResult>;\n}\n\nconst PAID_STATUS = \"PAID\";\n\nfunction toConfirmResult(s: TransactionStatus): ConfirmResult {\n if (s.status === PAID_STATUS) return { status: \"paid\", transactionId: s.transactionId };\n if (s.status === \"FAILED\" || s.status === \"EXPIRED\")\n return { status: \"failed\", error: s.message ?? s.status };\n return { status: \"pending\" };\n}\n\nexport interface DeUnaWebhookPayload {\n orderRef?: string;\n transactionId?: string;\n status?: string;\n authorizationCode?: string;\n}\n\nexport interface DeUnaPaymentMethodOpts {\n /**\n * Set inyectable para idempotencia de webhooks.\n * ponytail: en memoria por defecto; reemplaza con persistencia en DB para multi-instancia.\n */\n processedRefs?: Set<string>;\n /** Override de confirm para entornos sin acceso directo a la API (tests, staging). */\n onConfirm?: (ref: string) => Promise<ConfirmResult>;\n}\n\nexport function toDeUnaPaymentMethod(\n client: DeUnaClient,\n opts?: DeUnaPaymentMethodOpts,\n): PaymentMethod {\n // ponytail: in-memory; reemplazar con Set persistido en DB en prod multi-instancia\n const processed = opts?.processedRefs ?? new Set<string>();\n\n return {\n id: \"deuna\",\n label: \"DeUna (wallet QR · Banco Pichincha)\",\n\n async prepare(order: Order): Promise<Record<string, unknown>> {\n const result = await client.prepare({\n orderRef: order.id,\n amount: order.total,\n description: `Orden ${order.id}`,\n });\n // Devuelve el payload/string del QR — la UI genera la imagen (ponytail: no generar aquí).\n return {\n qrPayload: result.qrPayload,\n paymentUrl: result.paymentUrl,\n paymentCode: result.paymentCode,\n transactionId: result.transactionId,\n orderId: order.id,\n };\n },\n\n async confirm(ref: string): Promise<ConfirmResult> {\n if (opts?.onConfirm) return opts.onConfirm(ref);\n return toConfirmResult(await client.getStatus(ref));\n },\n\n async handleWebhook(payload: unknown): Promise<WebhookResult> {\n const p = payload as DeUnaWebhookPayload | null;\n const orderId = p?.orderRef;\n if (!orderId) return { handled: false };\n\n // Idempotencia: misma referencia no se procesa dos veces\n if (processed.has(orderId)) return { handled: true, orderId };\n\n // Si el webhook trae el status directamente lo usamos; si no, consultamos\n const status = p?.status ?? (await client.getStatus(orderId)).status;\n\n if (status !== PAID_STATUS) return { handled: false };\n\n processed.add(orderId);\n return { handled: true, orderId };\n },\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@pandait.tech/payment-deuna",
3
+ "version": "1.0.0",
4
+ "description": "DeUna (Banco Pichincha) wallet QR payment adapter for panda-commerce-kit. Implements PaymentMethod via QR dinámico, link de pago y código único.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/PandaITorg/panda-commerce-kit.git",
16
+ "directory": "packages/payment-deuna"
17
+ },
18
+ "keywords": [
19
+ "deuna",
20
+ "banco-pichincha",
21
+ "wallet",
22
+ "qr",
23
+ "payment-gateway",
24
+ "ecuador",
25
+ "panda-commerce-kit"
26
+ ],
27
+ "type": "module",
28
+ "main": "./dist/index.cjs",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js",
35
+ "require": "./dist/index.cjs"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "dependencies": {},
42
+ "devDependencies": {
43
+ "@types/node": "^22.7.5",
44
+ "@vitest/coverage-v8": "^4.1.7",
45
+ "tsup": "^8.3.0",
46
+ "typescript": "^5.6.3",
47
+ "vitest": "^4.1.7",
48
+ "@pandait.tech/commerce-core": "0.0.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "dev": "tsup --watch",
53
+ "type-check": "tsc --noEmit",
54
+ "test": "vitest run --coverage",
55
+ "test:watch": "vitest",
56
+ "lint": "eslint src",
57
+ "clean": "rm -rf dist .turbo"
58
+ }
59
+ }