@dryinov8/zumbopay-ts 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/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/chunk-YULBG3E6.mjs +338 -0
- package/dist/chunk-YULBG3E6.mjs.map +1 -0
- package/dist/client-CO8dBkq7.d.mts +188 -0
- package/dist/client-CO8dBkq7.d.ts +188 -0
- package/dist/index.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +400 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +36 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +46 -0
- package/dist/react/index.d.ts +46 -0
- package/dist/react/index.js +755 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +393 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/phone.ts
|
|
2
|
+
function normalizePhone(phone) {
|
|
3
|
+
let cleaned = phone.replace(/\D/g, "");
|
|
4
|
+
if (cleaned.startsWith("0")) {
|
|
5
|
+
cleaned = cleaned.slice(1);
|
|
6
|
+
}
|
|
7
|
+
if (cleaned.length === 9) {
|
|
8
|
+
cleaned = "258" + cleaned;
|
|
9
|
+
}
|
|
10
|
+
return cleaned;
|
|
11
|
+
}
|
|
12
|
+
function detectOperator(phone) {
|
|
13
|
+
const normalized = normalizePhone(phone);
|
|
14
|
+
if (normalized.length !== 12 || !normalized.startsWith("258")) {
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
|
17
|
+
const prefix = normalized.slice(3, 5);
|
|
18
|
+
if (prefix === "84" || prefix === "85") {
|
|
19
|
+
return "mpesa";
|
|
20
|
+
}
|
|
21
|
+
if (prefix === "86" || prefix === "87") {
|
|
22
|
+
return "emola";
|
|
23
|
+
}
|
|
24
|
+
if (prefix === "82" || prefix === "83") {
|
|
25
|
+
return "mkesh";
|
|
26
|
+
}
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
function getOperatorLabel(operator) {
|
|
30
|
+
switch (operator) {
|
|
31
|
+
case "mpesa":
|
|
32
|
+
return "Vodacom M-Pesa";
|
|
33
|
+
case "emola":
|
|
34
|
+
return "Movitel e-Mola";
|
|
35
|
+
case "mkesh":
|
|
36
|
+
return "Tmcel mKesh";
|
|
37
|
+
default:
|
|
38
|
+
return "Desconhecida";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isValidMozPhone(phone) {
|
|
42
|
+
return detectOperator(phone) !== "unknown";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/client.ts
|
|
46
|
+
var ZumboPayClient = class {
|
|
47
|
+
config;
|
|
48
|
+
cachedWallets = null;
|
|
49
|
+
walletsCacheExpiresAt = 0;
|
|
50
|
+
constructor(config) {
|
|
51
|
+
const envWallets = {};
|
|
52
|
+
let defaultEnabled = true;
|
|
53
|
+
if (typeof process !== "undefined" && process?.env) {
|
|
54
|
+
if (process.env.ZUMBOPAY_ENABLED !== void 0) {
|
|
55
|
+
defaultEnabled = process.env.ZUMBOPAY_ENABLED === "true" || process.env.ZUMBOPAY_ENABLED === "1";
|
|
56
|
+
}
|
|
57
|
+
if (process.env.ZUMBOPAY_WALLET_MPESA) {
|
|
58
|
+
envWallets.mpesa = process.env.ZUMBOPAY_WALLET_MPESA;
|
|
59
|
+
}
|
|
60
|
+
if (process.env.ZUMBOPAY_WALLET_EMOLA) {
|
|
61
|
+
envWallets.emola = process.env.ZUMBOPAY_WALLET_EMOLA;
|
|
62
|
+
}
|
|
63
|
+
if (process.env.ZUMBOPAY_WALLET_MKESH) {
|
|
64
|
+
envWallets.mkesh = process.env.ZUMBOPAY_WALLET_MKESH;
|
|
65
|
+
}
|
|
66
|
+
if (process.env.ZUMBOPAY_WALLET_CARD) {
|
|
67
|
+
envWallets.card = process.env.ZUMBOPAY_WALLET_CARD;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.config = {
|
|
71
|
+
apiKey: config.apiKey,
|
|
72
|
+
merchantId: config.merchantId,
|
|
73
|
+
baseUrl: config.baseUrl?.replace(/\/+$/, "") || "https://zumbopay.com/api/public/v1",
|
|
74
|
+
webhookSecret: config.webhookSecret,
|
|
75
|
+
wallets: {
|
|
76
|
+
...envWallets,
|
|
77
|
+
...config.wallets || {}
|
|
78
|
+
},
|
|
79
|
+
enabled: config.enabled !== void 0 ? config.enabled : defaultEnabled,
|
|
80
|
+
timeout: config.timeout || 15e3
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns whether the ZumboPay client is currently active.
|
|
85
|
+
* If false, all mutations (STK push, checkouts) are gracefully silenced.
|
|
86
|
+
*/
|
|
87
|
+
isEnabled() {
|
|
88
|
+
return Boolean(this.config.enabled);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Programmatically enable or disable the gateway (kill-switch toggle).
|
|
92
|
+
*/
|
|
93
|
+
setEnabled(enabled) {
|
|
94
|
+
this.config.enabled = enabled;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Returns configured base URL.
|
|
98
|
+
*/
|
|
99
|
+
getBaseUrl() {
|
|
100
|
+
return this.config.baseUrl;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolves a valid Wallet UUID for a given operator or channel.
|
|
104
|
+
*/
|
|
105
|
+
async resolveWalletId(channel) {
|
|
106
|
+
if (this.config.wallets[channel]) {
|
|
107
|
+
return this.config.wallets[channel];
|
|
108
|
+
}
|
|
109
|
+
const wallets = await this.listWallets();
|
|
110
|
+
const match = wallets.find(
|
|
111
|
+
(w) => w.is_active !== false && (w.method?.toLowerCase() === channel || w.wallet_code?.toLowerCase().includes(channel) || w.name?.toLowerCase().includes(channel))
|
|
112
|
+
);
|
|
113
|
+
if (match) {
|
|
114
|
+
return match.id;
|
|
115
|
+
}
|
|
116
|
+
const active = wallets.find((w) => w.is_active !== false);
|
|
117
|
+
return active?.id;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolves the proper wallet ID for a Mozambican phone number.
|
|
121
|
+
*/
|
|
122
|
+
async resolveWalletIdForPhone(phone) {
|
|
123
|
+
const operator = detectOperator(phone);
|
|
124
|
+
if (operator === "unknown") {
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
return this.resolveWalletId(operator);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Initiates a direct STK Push prompt to a mobile phone (M-Pesa, e-Mola, mKesh).
|
|
131
|
+
*/
|
|
132
|
+
async stkPush(request) {
|
|
133
|
+
if (!this.isEnabled()) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
status: "disabled",
|
|
137
|
+
reference: null,
|
|
138
|
+
message: "O gateway de pagamento ZumboPay est\xE1 temporariamente desativado."
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const normalizedPhone = normalizePhone(request.phone);
|
|
142
|
+
const walletId = request.walletId || await this.resolveWalletIdForPhone(normalizedPhone);
|
|
143
|
+
const sourceId = request.reference || `stk-${Date.now()}`;
|
|
144
|
+
const payload = {
|
|
145
|
+
phone: normalizedPhone,
|
|
146
|
+
amount: request.amount,
|
|
147
|
+
wallet_id: walletId,
|
|
148
|
+
reference: sourceId,
|
|
149
|
+
customer_name: request.customerName,
|
|
150
|
+
description: request.description
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
const res = await this.fetchWithTimeout("/charges", {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: this.getHeaders(),
|
|
156
|
+
body: JSON.stringify(payload)
|
|
157
|
+
});
|
|
158
|
+
const data = await res.json().catch(() => ({}));
|
|
159
|
+
if (res.ok) {
|
|
160
|
+
const status = (data.data?.status || data.status || "pending").toLowerCase();
|
|
161
|
+
const code = data.code || data.data?.code;
|
|
162
|
+
const isSuccess = status === "success" || status === "succeeded" || status === "completed" || code === "INS-0";
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
status: isSuccess ? "success" : status,
|
|
166
|
+
reference: data.data?.reference || sourceId,
|
|
167
|
+
message: isSuccess ? "Pagamento efetuado com sucesso!" : "Pedido de pagamento enviado para o seu telem\xF3vel. Por favor confirme com o seu PIN.",
|
|
168
|
+
raw: data
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
status: "declined",
|
|
174
|
+
reference: null,
|
|
175
|
+
message: data.error?.message || data.message || "O pagamento foi recusado ou expirou no telem\xF3vel.",
|
|
176
|
+
raw: data
|
|
177
|
+
};
|
|
178
|
+
} catch (err) {
|
|
179
|
+
const error = err;
|
|
180
|
+
if (error.name === "AbortError" || error.message.includes("timeout")) {
|
|
181
|
+
return {
|
|
182
|
+
success: true,
|
|
183
|
+
status: "pending",
|
|
184
|
+
reference: sourceId,
|
|
185
|
+
message: "O pedido foi enviado. Por favor verifique o telem\xF3vel e confirme com o PIN.",
|
|
186
|
+
raw: { timeout: true }
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
status: "failed",
|
|
192
|
+
reference: null,
|
|
193
|
+
message: error.message || "Falha na comunica\xE7\xE3o com o ZumboPay."
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Creates a Hosted Checkout URL for credit/debit card & multicanal payments.
|
|
199
|
+
*/
|
|
200
|
+
async createCheckout(request) {
|
|
201
|
+
if (!this.isEnabled()) {
|
|
202
|
+
return {
|
|
203
|
+
success: false,
|
|
204
|
+
checkoutUrl: null,
|
|
205
|
+
reference: null,
|
|
206
|
+
message: "O gateway de pagamento ZumboPay est\xE1 temporariamente desativado."
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const walletId = request.walletId || await this.resolveWalletId("card") || await this.resolveWalletId("mpesa");
|
|
210
|
+
const sourceId = request.reference || `chk-${Date.now()}`;
|
|
211
|
+
const payload = {
|
|
212
|
+
title: request.title,
|
|
213
|
+
amount: request.amount,
|
|
214
|
+
currency: request.currency || "MZN",
|
|
215
|
+
channels: request.channels || ["card", "mpesa", "emola", "mkesh"],
|
|
216
|
+
wallet_id: walletId,
|
|
217
|
+
reference: sourceId,
|
|
218
|
+
return_url: request.returnUrl,
|
|
219
|
+
redirect_url: request.returnUrl,
|
|
220
|
+
cancel_url: request.cancelUrl,
|
|
221
|
+
metadata: request.metadata
|
|
222
|
+
};
|
|
223
|
+
try {
|
|
224
|
+
const res = await this.fetchWithTimeout("/checkouts", {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: this.getHeaders(),
|
|
227
|
+
body: JSON.stringify(payload)
|
|
228
|
+
});
|
|
229
|
+
const data = await res.json().catch(() => ({}));
|
|
230
|
+
if (res.ok) {
|
|
231
|
+
const checkoutUrl = data.data?.checkout_url || data.checkout_url || data.data?.url || data.url || null;
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
checkoutUrl,
|
|
235
|
+
reference: data.data?.reference || sourceId,
|
|
236
|
+
message: "Sess\xE3o de checkout criada com sucesso.",
|
|
237
|
+
raw: data
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
checkoutUrl: null,
|
|
243
|
+
reference: null,
|
|
244
|
+
message: data.error?.message || data.message || "N\xE3o foi poss\xEDvel gerar a p\xE1gina de checkout.",
|
|
245
|
+
raw: data
|
|
246
|
+
};
|
|
247
|
+
} catch (err) {
|
|
248
|
+
const error = err;
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
checkoutUrl: null,
|
|
252
|
+
reference: null,
|
|
253
|
+
message: error.message || "Erro de conex\xE3o ao criar checkout."
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Queries status of an existing charge/checkout transaction.
|
|
259
|
+
*/
|
|
260
|
+
async getStatus(referenceOrId) {
|
|
261
|
+
const res = await this.fetchWithTimeout(
|
|
262
|
+
`/charges/${encodeURIComponent(referenceOrId)}`,
|
|
263
|
+
{
|
|
264
|
+
method: "GET",
|
|
265
|
+
headers: this.getHeaders()
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
const data = await res.json().catch(() => ({}));
|
|
269
|
+
const payload = data.data || data;
|
|
270
|
+
const status = (payload.status || "pending").toLowerCase();
|
|
271
|
+
const isPaid = status === "success" || status === "succeeded" || status === "completed" || payload.is_paid === true;
|
|
272
|
+
return {
|
|
273
|
+
success: res.ok,
|
|
274
|
+
status,
|
|
275
|
+
paid: isPaid,
|
|
276
|
+
reference: payload.reference || referenceOrId,
|
|
277
|
+
amount: payload.amount,
|
|
278
|
+
currency: payload.currency || "MZN",
|
|
279
|
+
channel: payload.channel,
|
|
280
|
+
paidAt: payload.paid_at || payload.updated_at || null,
|
|
281
|
+
raw: data
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Lists all wallets associated with the merchant account (cached for 10 minutes).
|
|
286
|
+
*/
|
|
287
|
+
async listWallets() {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
if (this.cachedWallets && now < this.walletsCacheExpiresAt) {
|
|
290
|
+
return this.cachedWallets;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const res = await this.fetchWithTimeout("/wallets", {
|
|
294
|
+
method: "GET",
|
|
295
|
+
headers: this.getHeaders()
|
|
296
|
+
});
|
|
297
|
+
if (res.ok) {
|
|
298
|
+
const data = await res.json();
|
|
299
|
+
this.cachedWallets = data.data || data || [];
|
|
300
|
+
this.walletsCacheExpiresAt = now + 10 * 60 * 1e3;
|
|
301
|
+
return this.cachedWallets;
|
|
302
|
+
}
|
|
303
|
+
return [];
|
|
304
|
+
} catch {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
getHeaders() {
|
|
309
|
+
return {
|
|
310
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
311
|
+
"X-Merchant-Id": this.config.merchantId,
|
|
312
|
+
"Content-Type": "application/json",
|
|
313
|
+
Accept: "application/json"
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
async fetchWithTimeout(endpoint, options) {
|
|
317
|
+
const controller = new AbortController();
|
|
318
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeout);
|
|
319
|
+
const url = `${this.config.baseUrl}${endpoint}`;
|
|
320
|
+
try {
|
|
321
|
+
return await fetch(url, {
|
|
322
|
+
...options,
|
|
323
|
+
signal: controller.signal
|
|
324
|
+
});
|
|
325
|
+
} finally {
|
|
326
|
+
clearTimeout(timer);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export {
|
|
332
|
+
normalizePhone,
|
|
333
|
+
detectOperator,
|
|
334
|
+
getOperatorLabel,
|
|
335
|
+
isValidMozPhone,
|
|
336
|
+
ZumboPayClient
|
|
337
|
+
};
|
|
338
|
+
//# sourceMappingURL=chunk-YULBG3E6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/phone.ts","../src/client.ts"],"sourcesContent":["import type { MobileOperator } from './types.js';\n\n/**\n * Normalizes any Mozambican telephone string into the standard 12-digit format `258XXXXXXXXX`.\n * Removes spaces, hyphens, parentheses, and prefixes (+258, 258, 0).\n */\nexport function normalizePhone(phone: string): string {\n let cleaned = phone.replace(/\\D/g, '');\n\n // Remove leading 0 (e.g. 0841234567 -> 841234567)\n if (cleaned.startsWith('0')) {\n cleaned = cleaned.slice(1);\n }\n\n // Prepend 258 if 9-digit national number\n if (cleaned.length === 9) {\n cleaned = '258' + cleaned;\n }\n\n return cleaned;\n}\n\n/**\n * Detects mobile network operator from phone number:\n * - Vodacom (M-Pesa): 84, 85\n * - Movitel (e-Mola): 86, 87\n * - Tmcel (mKesh): 82, 83\n */\nexport function detectOperator(phone: string): MobileOperator {\n const normalized = normalizePhone(phone);\n\n if (normalized.length !== 12 || !normalized.startsWith('258')) {\n return 'unknown';\n }\n\n const prefix = normalized.slice(3, 5);\n\n if (prefix === '84' || prefix === '85') {\n return 'mpesa';\n }\n if (prefix === '86' || prefix === '87') {\n return 'emola';\n }\n if (prefix === '82' || prefix === '83') {\n return 'mkesh';\n }\n\n return 'unknown';\n}\n\n/**\n * Returns human-readable label for a mobile operator.\n */\nexport function getOperatorLabel(operator: MobileOperator): string {\n switch (operator) {\n case 'mpesa':\n return 'Vodacom M-Pesa';\n case 'emola':\n return 'Movitel e-Mola';\n case 'mkesh':\n return 'Tmcel mKesh';\n default:\n return 'Desconhecida';\n }\n}\n\n/**\n * Validates whether the number is a valid 9-digit Mozambican mobile number (with or without 258).\n */\nexport function isValidMozPhone(phone: string): boolean {\n return detectOperator(phone) !== 'unknown';\n}\n","import { detectOperator, normalizePhone } from './phone.js';\nimport type {\n CheckoutRequest,\n CheckoutResponse,\n StkPushRequest,\n StkPushResponse,\n TransactionStatusResponse,\n WalletItem,\n ZumboPayConfig,\n ZumboPayWallets,\n} from './types.js';\n\nexport class ZumboPayClient {\n private readonly config: Required<\n Omit<ZumboPayConfig, 'webhookSecret' | 'wallets'>\n > & {\n webhookSecret?: string;\n wallets: ZumboPayWallets;\n };\n\n private cachedWallets: WalletItem[] | null = null;\n private walletsCacheExpiresAt = 0;\n\n constructor(config: ZumboPayConfig) {\n const envWallets: ZumboPayWallets = {};\n let defaultEnabled = true;\n\n // Read environment variables if available in Node.js / server environment\n if (typeof process !== 'undefined' && process?.env) {\n if (process.env.ZUMBOPAY_ENABLED !== undefined) {\n defaultEnabled =\n process.env.ZUMBOPAY_ENABLED === 'true' ||\n process.env.ZUMBOPAY_ENABLED === '1';\n }\n\n if (process.env.ZUMBOPAY_WALLET_MPESA) {\n envWallets.mpesa = process.env.ZUMBOPAY_WALLET_MPESA;\n }\n if (process.env.ZUMBOPAY_WALLET_EMOLA) {\n envWallets.emola = process.env.ZUMBOPAY_WALLET_EMOLA;\n }\n if (process.env.ZUMBOPAY_WALLET_MKESH) {\n envWallets.mkesh = process.env.ZUMBOPAY_WALLET_MKESH;\n }\n if (process.env.ZUMBOPAY_WALLET_CARD) {\n envWallets.card = process.env.ZUMBOPAY_WALLET_CARD;\n }\n }\n\n this.config = {\n apiKey: config.apiKey,\n merchantId: config.merchantId,\n baseUrl:\n config.baseUrl?.replace(/\\/+$/, '') ||\n 'https://zumbopay.com/api/public/v1',\n webhookSecret: config.webhookSecret,\n wallets: {\n ...envWallets,\n ...(config.wallets || {}),\n },\n enabled: config.enabled !== undefined ? config.enabled : defaultEnabled,\n timeout: config.timeout || 15000,\n };\n }\n\n /**\n * Returns whether the ZumboPay client is currently active.\n * If false, all mutations (STK push, checkouts) are gracefully silenced.\n */\n public isEnabled(): boolean {\n return Boolean(this.config.enabled);\n }\n\n /**\n * Programmatically enable or disable the gateway (kill-switch toggle).\n */\n public setEnabled(enabled: boolean): void {\n this.config.enabled = enabled;\n }\n\n /**\n * Returns configured base URL.\n */\n public getBaseUrl(): string {\n return this.config.baseUrl;\n }\n\n /**\n * Resolves a valid Wallet UUID for a given operator or channel.\n */\n public async resolveWalletId(\n channel: 'mpesa' | 'emola' | 'mkesh' | 'card'\n ): Promise<string | undefined> {\n // 1. Check explicitly configured wallet\n if (this.config.wallets[channel]) {\n return this.config.wallets[channel];\n }\n\n // 2. Discover dynamically from list of wallets\n const wallets = await this.listWallets();\n const match = wallets.find(\n (w) =>\n w.is_active !== false &&\n (w.method?.toLowerCase() === channel ||\n w.wallet_code?.toLowerCase().includes(channel) ||\n w.name?.toLowerCase().includes(channel))\n );\n\n if (match) {\n return match.id;\n }\n\n // 3. Fallback to any active wallet\n const active = wallets.find((w) => w.is_active !== false);\n return active?.id;\n }\n\n /**\n * Resolves the proper wallet ID for a Mozambican phone number.\n */\n public async resolveWalletIdForPhone(\n phone: string\n ): Promise<string | undefined> {\n const operator = detectOperator(phone);\n if (operator === 'unknown') {\n return undefined;\n }\n return this.resolveWalletId(operator);\n }\n\n /**\n * Initiates a direct STK Push prompt to a mobile phone (M-Pesa, e-Mola, mKesh).\n */\n public async stkPush(request: StkPushRequest): Promise<StkPushResponse> {\n if (!this.isEnabled()) {\n return {\n success: false,\n status: 'disabled',\n reference: null,\n message:\n 'O gateway de pagamento ZumboPay está temporariamente desativado.',\n };\n }\n\n const normalizedPhone = normalizePhone(request.phone);\n const walletId =\n request.walletId ||\n (await this.resolveWalletIdForPhone(normalizedPhone));\n\n const sourceId = request.reference || `stk-${Date.now()}`;\n\n const payload = {\n phone: normalizedPhone,\n amount: request.amount,\n wallet_id: walletId,\n reference: sourceId,\n customer_name: request.customerName,\n description: request.description,\n };\n\n try {\n const res = await this.fetchWithTimeout('/charges', {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(payload),\n });\n\n const data = await res.json().catch(() => ({}));\n\n if (res.ok) {\n const status = (\n data.data?.status ||\n data.status ||\n 'pending'\n ).toLowerCase();\n const code = data.code || data.data?.code;\n const isSuccess =\n status === 'success' ||\n status === 'succeeded' ||\n status === 'completed' ||\n code === 'INS-0';\n\n return {\n success: true,\n status: isSuccess ? 'success' : status,\n reference: data.data?.reference || sourceId,\n message: isSuccess\n ? 'Pagamento efetuado com sucesso!'\n : 'Pedido de pagamento enviado para o seu telemóvel. Por favor confirme com o seu PIN.',\n raw: data,\n };\n }\n\n return {\n success: false,\n status: 'declined',\n reference: null,\n message:\n data.error?.message ||\n data.message ||\n 'O pagamento foi recusado ou expirou no telemóvel.',\n raw: data,\n };\n } catch (err: unknown) {\n const error = err as Error;\n\n // Handle USSD network timeouts gracefully as pending\n if (error.name === 'AbortError' || error.message.includes('timeout')) {\n return {\n success: true,\n status: 'pending',\n reference: sourceId,\n message:\n 'O pedido foi enviado. Por favor verifique o telemóvel e confirme com o PIN.',\n raw: { timeout: true },\n };\n }\n\n return {\n success: false,\n status: 'failed',\n reference: null,\n message: error.message || 'Falha na comunicação com o ZumboPay.',\n };\n }\n }\n\n /**\n * Creates a Hosted Checkout URL for credit/debit card & multicanal payments.\n */\n public async createCheckout(\n request: CheckoutRequest\n ): Promise<CheckoutResponse> {\n if (!this.isEnabled()) {\n return {\n success: false,\n checkoutUrl: null,\n reference: null,\n message:\n 'O gateway de pagamento ZumboPay está temporariamente desativado.',\n };\n }\n\n const walletId =\n request.walletId ||\n (await this.resolveWalletId('card')) ||\n (await this.resolveWalletId('mpesa'));\n\n const sourceId = request.reference || `chk-${Date.now()}`;\n\n const payload = {\n title: request.title,\n amount: request.amount,\n currency: request.currency || 'MZN',\n channels: request.channels || ['card', 'mpesa', 'emola', 'mkesh'],\n wallet_id: walletId,\n reference: sourceId,\n return_url: request.returnUrl,\n redirect_url: request.returnUrl,\n cancel_url: request.cancelUrl,\n metadata: request.metadata,\n };\n\n try {\n const res = await this.fetchWithTimeout('/checkouts', {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(payload),\n });\n\n const data = await res.json().catch(() => ({}));\n\n if (res.ok) {\n const checkoutUrl =\n data.data?.checkout_url ||\n data.checkout_url ||\n data.data?.url ||\n data.url ||\n null;\n\n return {\n success: true,\n checkoutUrl,\n reference: data.data?.reference || sourceId,\n message: 'Sessão de checkout criada com sucesso.',\n raw: data,\n };\n }\n\n return {\n success: false,\n checkoutUrl: null,\n reference: null,\n message:\n data.error?.message ||\n data.message ||\n 'Não foi possível gerar a página de checkout.',\n raw: data,\n };\n } catch (err: unknown) {\n const error = err as Error;\n return {\n success: false,\n checkoutUrl: null,\n reference: null,\n message: error.message || 'Erro de conexão ao criar checkout.',\n };\n }\n }\n\n /**\n * Queries status of an existing charge/checkout transaction.\n */\n public async getStatus(\n referenceOrId: string\n ): Promise<TransactionStatusResponse> {\n const res = await this.fetchWithTimeout(\n `/charges/${encodeURIComponent(referenceOrId)}`,\n {\n method: 'GET',\n headers: this.getHeaders(),\n }\n );\n\n const data = await res.json().catch(() => ({}));\n const payload = data.data || data;\n const status = (payload.status || 'pending').toLowerCase();\n const isPaid =\n status === 'success' ||\n status === 'succeeded' ||\n status === 'completed' ||\n payload.is_paid === true;\n\n return {\n success: res.ok,\n status,\n paid: isPaid,\n reference: payload.reference || referenceOrId,\n amount: payload.amount,\n currency: payload.currency || 'MZN',\n channel: payload.channel,\n paidAt: payload.paid_at || payload.updated_at || null,\n raw: data,\n };\n }\n\n /**\n * Lists all wallets associated with the merchant account (cached for 10 minutes).\n */\n public async listWallets(): Promise<WalletItem[]> {\n const now = Date.now();\n if (this.cachedWallets && now < this.walletsCacheExpiresAt) {\n return this.cachedWallets;\n }\n\n try {\n const res = await this.fetchWithTimeout('/wallets', {\n method: 'GET',\n headers: this.getHeaders(),\n });\n\n if (res.ok) {\n const data = await res.json();\n this.cachedWallets = (data.data || data || []) as WalletItem[];\n this.walletsCacheExpiresAt = now + 10 * 60 * 1000;\n return this.cachedWallets;\n }\n return [];\n } catch {\n return [];\n }\n }\n\n private getHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.config.apiKey}`,\n 'X-Merchant-Id': this.config.merchantId,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n\n private async fetchWithTimeout(\n endpoint: string,\n options: RequestInit\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.config.timeout);\n\n const url = `${this.config.baseUrl}${endpoint}`;\n\n try {\n return await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
type MobileOperator = 'mpesa' | 'emola' | 'mkesh' | 'unknown';
|
|
2
|
+
type PaymentStatus = 'pending' | 'processing' | 'success' | 'succeeded' | 'completed' | 'failed' | 'declined' | 'cancelled' | 'expired' | 'disabled';
|
|
3
|
+
interface ZumboPayWallets {
|
|
4
|
+
mpesa?: string;
|
|
5
|
+
emola?: string;
|
|
6
|
+
mkesh?: string;
|
|
7
|
+
card?: string;
|
|
8
|
+
[key: string]: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
interface ZumboPayConfig {
|
|
11
|
+
/**
|
|
12
|
+
* ZumboPay Secret API Key
|
|
13
|
+
*/
|
|
14
|
+
apiKey: string;
|
|
15
|
+
/**
|
|
16
|
+
* ZumboPay Merchant ID
|
|
17
|
+
*/
|
|
18
|
+
merchantId: string;
|
|
19
|
+
/**
|
|
20
|
+
* Base API URL (defaults to https://zumbopay.com/api/public/v1)
|
|
21
|
+
*/
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Webhook signing secret for HMAC verification
|
|
25
|
+
*/
|
|
26
|
+
webhookSecret?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Pre-configured Wallet UUIDs for specific channels
|
|
29
|
+
*/
|
|
30
|
+
wallets?: ZumboPayWallets;
|
|
31
|
+
/**
|
|
32
|
+
* Global kill-switch / activation toggle.
|
|
33
|
+
* If false, all checkout and STK push operations are gracefully silenced
|
|
34
|
+
* and return a disabled status without making external network calls.
|
|
35
|
+
* Defaults to true (or checks process.env.ZUMBOPAY_ENABLED when available).
|
|
36
|
+
*/
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Request timeout in milliseconds (defaults to 15000)
|
|
40
|
+
*/
|
|
41
|
+
timeout?: number;
|
|
42
|
+
}
|
|
43
|
+
interface StkPushRequest {
|
|
44
|
+
/**
|
|
45
|
+
* Amount in Principal currency units (e.g. 150.00 MZN)
|
|
46
|
+
*/
|
|
47
|
+
amount: number;
|
|
48
|
+
/**
|
|
49
|
+
* Mozambican phone number (Vodacom, Movitel, or Tmcel)
|
|
50
|
+
*/
|
|
51
|
+
phone: string;
|
|
52
|
+
/**
|
|
53
|
+
* Commercial transaction reference / order ID
|
|
54
|
+
*/
|
|
55
|
+
reference?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Customer full name
|
|
58
|
+
*/
|
|
59
|
+
customerName?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Specific Wallet UUID (optional, will be auto-resolved from phone if omitted)
|
|
62
|
+
*/
|
|
63
|
+
walletId?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Description / note
|
|
66
|
+
*/
|
|
67
|
+
description?: string;
|
|
68
|
+
}
|
|
69
|
+
interface StkPushResponse {
|
|
70
|
+
success: boolean;
|
|
71
|
+
status: PaymentStatus;
|
|
72
|
+
reference: string | null;
|
|
73
|
+
message: string;
|
|
74
|
+
raw?: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
interface CheckoutRequest {
|
|
77
|
+
/**
|
|
78
|
+
* Amount in Principal currency units (e.g. 500.00 MZN)
|
|
79
|
+
*/
|
|
80
|
+
amount: number;
|
|
81
|
+
/**
|
|
82
|
+
* Payment title / product name
|
|
83
|
+
*/
|
|
84
|
+
title: string;
|
|
85
|
+
/**
|
|
86
|
+
* Currency code (defaults to MZN)
|
|
87
|
+
*/
|
|
88
|
+
currency?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Commercial transaction reference / order ID
|
|
91
|
+
*/
|
|
92
|
+
reference?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Return URL after successful payment
|
|
95
|
+
*/
|
|
96
|
+
returnUrl?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Cancel URL if user aborts
|
|
99
|
+
*/
|
|
100
|
+
cancelUrl?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Allowed payment channels (defaults to ['card', 'mpesa', 'emola', 'mkesh'])
|
|
103
|
+
*/
|
|
104
|
+
channels?: string[];
|
|
105
|
+
/**
|
|
106
|
+
* Specific Wallet UUID
|
|
107
|
+
*/
|
|
108
|
+
walletId?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Additional metadata
|
|
111
|
+
*/
|
|
112
|
+
metadata?: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
interface CheckoutResponse {
|
|
115
|
+
success: boolean;
|
|
116
|
+
checkoutUrl: string | null;
|
|
117
|
+
reference: string | null;
|
|
118
|
+
message: string;
|
|
119
|
+
raw?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
interface TransactionStatusResponse {
|
|
122
|
+
success: boolean;
|
|
123
|
+
status: PaymentStatus;
|
|
124
|
+
paid: boolean;
|
|
125
|
+
reference: string;
|
|
126
|
+
amount?: number;
|
|
127
|
+
currency?: string;
|
|
128
|
+
channel?: string;
|
|
129
|
+
paidAt?: string | null;
|
|
130
|
+
raw?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
interface WalletItem {
|
|
133
|
+
id: string;
|
|
134
|
+
wallet_code?: string;
|
|
135
|
+
name?: string;
|
|
136
|
+
method?: string;
|
|
137
|
+
currency?: string;
|
|
138
|
+
balance?: number;
|
|
139
|
+
is_active?: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
declare class ZumboPayClient {
|
|
143
|
+
private readonly config;
|
|
144
|
+
private cachedWallets;
|
|
145
|
+
private walletsCacheExpiresAt;
|
|
146
|
+
constructor(config: ZumboPayConfig);
|
|
147
|
+
/**
|
|
148
|
+
* Returns whether the ZumboPay client is currently active.
|
|
149
|
+
* If false, all mutations (STK push, checkouts) are gracefully silenced.
|
|
150
|
+
*/
|
|
151
|
+
isEnabled(): boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Programmatically enable or disable the gateway (kill-switch toggle).
|
|
154
|
+
*/
|
|
155
|
+
setEnabled(enabled: boolean): void;
|
|
156
|
+
/**
|
|
157
|
+
* Returns configured base URL.
|
|
158
|
+
*/
|
|
159
|
+
getBaseUrl(): string;
|
|
160
|
+
/**
|
|
161
|
+
* Resolves a valid Wallet UUID for a given operator or channel.
|
|
162
|
+
*/
|
|
163
|
+
resolveWalletId(channel: 'mpesa' | 'emola' | 'mkesh' | 'card'): Promise<string | undefined>;
|
|
164
|
+
/**
|
|
165
|
+
* Resolves the proper wallet ID for a Mozambican phone number.
|
|
166
|
+
*/
|
|
167
|
+
resolveWalletIdForPhone(phone: string): Promise<string | undefined>;
|
|
168
|
+
/**
|
|
169
|
+
* Initiates a direct STK Push prompt to a mobile phone (M-Pesa, e-Mola, mKesh).
|
|
170
|
+
*/
|
|
171
|
+
stkPush(request: StkPushRequest): Promise<StkPushResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* Creates a Hosted Checkout URL for credit/debit card & multicanal payments.
|
|
174
|
+
*/
|
|
175
|
+
createCheckout(request: CheckoutRequest): Promise<CheckoutResponse>;
|
|
176
|
+
/**
|
|
177
|
+
* Queries status of an existing charge/checkout transaction.
|
|
178
|
+
*/
|
|
179
|
+
getStatus(referenceOrId: string): Promise<TransactionStatusResponse>;
|
|
180
|
+
/**
|
|
181
|
+
* Lists all wallets associated with the merchant account (cached for 10 minutes).
|
|
182
|
+
*/
|
|
183
|
+
listWallets(): Promise<WalletItem[]>;
|
|
184
|
+
private getHeaders;
|
|
185
|
+
private fetchWithTimeout;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { type CheckoutRequest as C, type MobileOperator as M, type PaymentStatus as P, type StkPushRequest as S, type TransactionStatusResponse as T, type WalletItem as W, ZumboPayClient as Z, type CheckoutResponse as a, type StkPushResponse as b, type ZumboPayConfig as c, type ZumboPayWallets as d };
|