@madapay/alefa 0.0.0-stage → 0.1.1

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,608 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var core = require('@madapay/core');
5
+
6
+ // src/index.ts
7
+ var PROVIDER = "alefa";
8
+ function safeProviderError(payload) {
9
+ if (typeof payload !== "object" || payload === null) return {};
10
+ const p = payload;
11
+ const out = {};
12
+ for (const k of ["code", "error", "message", "detail"]) {
13
+ if (typeof p[k] === "string") out[`provider_${k}`] = p[k].slice(0, 200);
14
+ }
15
+ return out;
16
+ }
17
+ async function alefaRequest(opts) {
18
+ const controller = new AbortController();
19
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
20
+ const onAbort = () => controller.abort();
21
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
22
+ let response;
23
+ try {
24
+ response = await opts.fetchImpl(opts.url, {
25
+ method: opts.method,
26
+ headers: opts.headers,
27
+ body: opts.body === void 0 ? void 0 : JSON.stringify(opts.body),
28
+ signal: controller.signal
29
+ });
30
+ } catch {
31
+ if (opts.signal?.aborted) throw new Error("Operation aborted by caller");
32
+ const timedOut = controller.signal.aborted;
33
+ if (opts.uncertainOnFailure || timedOut) {
34
+ throw new core.ProviderTimeoutError(
35
+ timedOut ? `Alefa request timed out after ${opts.timeoutMs}ms` : "Alefa request failed after dispatch; outcome is uncertain (network error)",
36
+ { timeoutMs: opts.timeoutMs },
37
+ PROVIDER
38
+ );
39
+ }
40
+ throw new core.ProviderUnavailableError("Alefa is unreachable", void 0, PROVIDER);
41
+ } finally {
42
+ clearTimeout(timer);
43
+ opts.signal?.removeEventListener("abort", onAbort);
44
+ }
45
+ const text = await response.text().catch(() => "");
46
+ let payload;
47
+ try {
48
+ payload = text ? JSON.parse(text) : void 0;
49
+ } catch {
50
+ payload = void 0;
51
+ }
52
+ if (response.ok) {
53
+ if (payload === void 0) {
54
+ throw opts.uncertainOnFailure ? new core.ProviderTimeoutError(
55
+ "Alefa returned an unreadable success response; outcome is uncertain",
56
+ { status: response.status },
57
+ PROVIDER
58
+ ) : new core.ProviderUnavailableError(
59
+ "Alefa returned an unreadable response",
60
+ void 0,
61
+ PROVIDER
62
+ );
63
+ }
64
+ return payload;
65
+ }
66
+ const details = { status: response.status, ...safeProviderError(payload) };
67
+ const s = response.status;
68
+ if (s === 401 || s === 403) {
69
+ throw new core.AuthenticationError("Alefa rejected credentials", details, PROVIDER);
70
+ }
71
+ if (s === 404) {
72
+ throw new core.PaymentNotFoundError("Alefa payment or resource not found", details, PROVIDER);
73
+ }
74
+ if (s === 409) {
75
+ throw new core.DuplicatePaymentError(
76
+ "Alefa idempotency conflict: request reference already processed or conflicting payload",
77
+ details,
78
+ PROVIDER
79
+ );
80
+ }
81
+ if (s === 400 || s === 422) {
82
+ const errorMsg = (typeof payload?.message === "string" ? payload.message : void 0) || "Alefa rejected request validation";
83
+ throw new core.ValidationError(errorMsg, "VALIDATION_FAILED", details, PROVIDER);
84
+ }
85
+ if (s >= 500) {
86
+ throw new core.ProviderUnavailableError(`Alefa service unavailable (HTTP ${s})`, details, PROVIDER);
87
+ }
88
+ throw new core.ProviderUnavailableError(
89
+ `Alefa returned unexpected status HTTP ${s}`,
90
+ details,
91
+ PROVIDER
92
+ );
93
+ }
94
+
95
+ // src/status.ts
96
+ function mapAlefaStatus(rawStatus) {
97
+ if (typeof rawStatus !== "string" || rawStatus.trim() === "") {
98
+ return { status: "processing", known: false };
99
+ }
100
+ const s = rawStatus.trim().toUpperCase();
101
+ switch (s) {
102
+ case "CREATED":
103
+ case "INIT":
104
+ case "INITIAL":
105
+ return { status: "created", known: true };
106
+ case "PENDING":
107
+ case "WAITING":
108
+ case "AWAITING_ACTION":
109
+ case "ACTION_REQUIRED":
110
+ case "PENDING_ACTION":
111
+ case "PENDING_CUSTOMER_ACTION":
112
+ return { status: "pending", known: true };
113
+ case "PROCESSING":
114
+ case "IN_PROGRESS":
115
+ case "IN_FLIGHT":
116
+ return { status: "processing", known: true };
117
+ case "SUCCEEDED":
118
+ case "SUCCESS":
119
+ case "COMPLETED":
120
+ case "PAID":
121
+ return { status: "succeeded", known: true };
122
+ case "FAILED":
123
+ case "DECLINED":
124
+ case "REJECTED":
125
+ case "ERROR":
126
+ return { status: "failed", known: true };
127
+ case "EXPIRED":
128
+ case "TIMEOUT":
129
+ case "TIMED_OUT":
130
+ return { status: "expired", known: true };
131
+ case "CANCELLED":
132
+ case "CANCELED":
133
+ return { status: "cancelled", known: true };
134
+ default:
135
+ return { status: "processing", known: false };
136
+ }
137
+ }
138
+ var ALEFA_CONTRACT_METADATA = {
139
+ contractVersion: "Alefa Payment Gateway API v1.0.0",
140
+ reviewDate: "2026-10-31",
141
+ source: "https://alefa.mg/docs",
142
+ operatingEntity: "Alefa SAS (Antananarivo, Madagascar)",
143
+ indicativeCommissionPercent: 2.5,
144
+ settlementFeeAriary: 0
145
+ };
146
+ var alefaSandboxNumbers = {
147
+ mvola: "+261340000001",
148
+ mvola_qr: "+261340000002",
149
+ mvola_expired: "+261340000003",
150
+ mvola_failed: "+261340000004",
151
+ orange_money: "+261320000001",
152
+ orange_money_ussd: "+261320000002",
153
+ orange_money_expired: "+261320000003",
154
+ orange_money_failed: "+261320000004",
155
+ airtel_money: "+261330000001",
156
+ airtel_money_redirect: "+261330000002",
157
+ airtel_money_expired: "+261330000003",
158
+ airtel_money_failed: "+261330000004"
159
+ };
160
+ var alefaQrActionFixture = {
161
+ type: "qr_code",
162
+ qrData: "alefa://pay/tx_alefa_qr_12345?amount=10000&cur=MGA&ref=ORDER-QR-001",
163
+ qrImageUrl: "https://api.sandbox.alefa.mg/qr/tx_alefa_qr_12345.png",
164
+ expiresAt: "2026-10-31T20:00:00.000Z"
165
+ };
166
+ var alefaUssdActionFixture = {
167
+ type: "ussd",
168
+ ussdCode: "#144*31*654321#",
169
+ instructions: "Composez #144*31*654321# depuis votre mobile Orange Money et validez avec votre code secret.",
170
+ expiresAt: "2026-10-31T19:30:00.000Z"
171
+ };
172
+ var alefaRedirectActionFixture = {
173
+ type: "redirect_url",
174
+ url: "https://pay.sandbox.alefa.mg/checkout/tx_alefa_red_12345",
175
+ returnUrl: "https://merchant.mg/checkout/complete"
176
+ };
177
+ function createMockAlefaPaymentResponse(overrides = {}) {
178
+ return {
179
+ transactionId: overrides.transactionId ?? `alefa_tx_${Date.now()}`,
180
+ reference: overrides.reference ?? `ORDER_${Date.now()}`,
181
+ status: overrides.status ?? "PENDING",
182
+ amount: overrides.amount ?? 1e4,
183
+ currency: "MGA",
184
+ operator: overrides.operator ?? "mvola",
185
+ customerPhone: overrides.customerPhone ?? "0340000001",
186
+ checkoutUrl: overrides.checkoutUrl ?? "https://pay.sandbox.alefa.mg/checkout/mock",
187
+ action: overrides.action ?? alefaQrActionFixture,
188
+ expiresAt: overrides.expiresAt ?? new Date(Date.now() + 30 * 60 * 1e3).toISOString(),
189
+ createdAt: overrides.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
190
+ updatedAt: overrides.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
191
+ };
192
+ }
193
+ function createMockAlefaWebhook(payload, secret) {
194
+ const fullPayload = {
195
+ eventId: payload.eventId ?? `evt_alefa_${Date.now()}`,
196
+ eventType: payload.eventType ?? "payment.succeeded",
197
+ transactionId: payload.transactionId ?? `alefa_tx_${Date.now()}`,
198
+ reference: payload.reference ?? `ORDER_${Date.now()}`,
199
+ status: payload.status ?? "SUCCEEDED",
200
+ amount: payload.amount ?? 1e4,
201
+ currency: "MGA",
202
+ operator: payload.operator ?? "mvola",
203
+ timestamp: payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
204
+ };
205
+ const bodyString = JSON.stringify(fullPayload);
206
+ const rawBody = new TextEncoder().encode(bodyString);
207
+ const signature = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
208
+ return {
209
+ rawBody,
210
+ headers: {
211
+ "content-type": "application/json",
212
+ "x-alefa-signature": signature,
213
+ "x-alefa-timestamp": fullPayload.timestamp
214
+ },
215
+ payload: fullPayload
216
+ };
217
+ }
218
+
219
+ // src/index.ts
220
+ var ALEFA_BASE_URLS = {
221
+ sandbox: "https://api.sandbox.alefa.mg",
222
+ production: "https://api.alefa.mg"
223
+ };
224
+ var PROVIDER2 = "alefa";
225
+ var SUPPORTED_METHODS = ["mvola", "orange_money", "airtel_money"];
226
+ var MIN_AMOUNT_MGA = 100;
227
+ var MAX_AMOUNT_MGA = 5e6;
228
+ function validateConfig(config) {
229
+ if (!config || typeof config.apiKey !== "string" || config.apiKey.trim() === "") {
230
+ throw new core.AuthenticationError("Alefa apiKey is required", void 0, PROVIDER2);
231
+ }
232
+ if (config.environment !== "sandbox" && config.environment !== "production") {
233
+ throw new core.ValidationError(
234
+ "Alefa environment must be explicitly 'sandbox' or 'production'",
235
+ "VALIDATION_FAILED",
236
+ void 0,
237
+ PROVIDER2
238
+ );
239
+ }
240
+ const baseUrl = (config.baseUrl ?? ALEFA_BASE_URLS[config.environment]).replace(/\/+$/, "");
241
+ if (!baseUrl.startsWith("https://")) {
242
+ throw new core.ValidationError(
243
+ "Alefa baseUrl must use https",
244
+ "VALIDATION_FAILED",
245
+ void 0,
246
+ PROVIDER2
247
+ );
248
+ }
249
+ const looksSandbox = /sandbox|test|dev/i.test(new URL(baseUrl).hostname);
250
+ if (config.environment === "production" && looksSandbox) {
251
+ throw new core.ValidationError(
252
+ "Alefa production environment configured with a sandbox base URL",
253
+ "VALIDATION_FAILED",
254
+ void 0,
255
+ PROVIDER2
256
+ );
257
+ }
258
+ if (config.environment === "sandbox" && !looksSandbox) {
259
+ throw new core.ValidationError(
260
+ "Alefa sandbox environment configured with a non-sandbox base URL",
261
+ "VALIDATION_FAILED",
262
+ void 0,
263
+ PROVIDER2
264
+ );
265
+ }
266
+ return baseUrl;
267
+ }
268
+ function asRecord(value) {
269
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
270
+ throw new core.ValidationError(
271
+ "Unexpected Alefa response format",
272
+ "VALIDATION_FAILED",
273
+ void 0,
274
+ PROVIDER2
275
+ );
276
+ }
277
+ return value;
278
+ }
279
+ function parseCustomerAction(rawAction) {
280
+ if (typeof rawAction !== "object" || rawAction === null) return void 0;
281
+ const a = rawAction;
282
+ if (a.type === "qr_code" && typeof a.qrData === "string") {
283
+ return {
284
+ type: "qr_code",
285
+ qrData: a.qrData,
286
+ qrImageUrl: typeof a.qrImageUrl === "string" ? a.qrImageUrl : void 0,
287
+ expiresAt: typeof a.expiresAt === "string" ? a.expiresAt : void 0
288
+ };
289
+ }
290
+ if (a.type === "ussd" && typeof a.ussdCode === "string") {
291
+ return {
292
+ type: "ussd",
293
+ ussdCode: a.ussdCode,
294
+ instructions: typeof a.instructions === "string" ? a.instructions : void 0,
295
+ expiresAt: typeof a.expiresAt === "string" ? a.expiresAt : void 0
296
+ };
297
+ }
298
+ if (a.type === "redirect_url" && typeof a.url === "string") {
299
+ return {
300
+ type: "redirect_url",
301
+ url: a.url,
302
+ returnUrl: typeof a.returnUrl === "string" ? a.returnUrl : void 0
303
+ };
304
+ }
305
+ return void 0;
306
+ }
307
+ var AlefaPaymentProvider = class {
308
+ id = PROVIDER2;
309
+ name = "Alefa";
310
+ apiKey;
311
+ webhookSecret;
312
+ baseUrl;
313
+ environment;
314
+ callbackUrl;
315
+ defaultValidityMinutes;
316
+ timeoutMs;
317
+ maxCreationRetries;
318
+ retryInitialDelayMs;
319
+ fetchImpl;
320
+ onUnknownStatus;
321
+ constructor(config) {
322
+ this.baseUrl = validateConfig(config);
323
+ this.apiKey = config.apiKey;
324
+ this.environment = config.environment;
325
+ this.webhookSecret = config.webhookSecret;
326
+ this.callbackUrl = config.callbackUrl;
327
+ this.defaultValidityMinutes = config.defaultValidityMinutes ?? 30;
328
+ this.timeoutMs = config.timeoutMs ?? 15e3;
329
+ this.maxCreationRetries = config.maxCreationRetries ?? 2;
330
+ this.retryInitialDelayMs = config.retryInitialDelayMs ?? 200;
331
+ this.fetchImpl = config.fetch ?? ((url, init) => fetch(url, init));
332
+ this.onUnknownStatus = config.onUnknownStatus;
333
+ }
334
+ /** Never exposes credentials or secrets. */
335
+ toJSON() {
336
+ return { id: this.id, environment: this.environment, baseUrl: this.baseUrl };
337
+ }
338
+ /**
339
+ * Capability truth table (D-07, ALEFA-01 through ALEFA-04).
340
+ * QR/USSD customer actions and hosted checkout are enabled.
341
+ * Direct refunds, payouts, payment links, and fee quotes remain unsupported.
342
+ */
343
+ getCapabilities() {
344
+ return {
345
+ payment: true,
346
+ asynchronousConfirmation: true,
347
+ synchronousConfirmation: false,
348
+ webhooks: true,
349
+ polling: true,
350
+ refunds: false,
351
+ payouts: false,
352
+ paymentLinks: false,
353
+ hostedCheckout: true,
354
+ feeQuote: false,
355
+ cancellation: false,
356
+ customerActions: true,
357
+ supportedMethods: [...SUPPORTED_METHODS],
358
+ syncConfirmation: false,
359
+ asyncConfirmation: true,
360
+ webhookEvents: true,
361
+ feeCalculation: false
362
+ };
363
+ }
364
+ headers(extra = {}) {
365
+ return {
366
+ Authorization: `Bearer ${this.apiKey}`,
367
+ "Content-Type": "application/json",
368
+ Accept: "application/json",
369
+ ...extra
370
+ };
371
+ }
372
+ async createPayment(request) {
373
+ if (!SUPPORTED_METHODS.includes(request.paymentMethod)) {
374
+ throw new core.ValidationError(
375
+ `Alefa does not support payment method '${request.paymentMethod}'. Supported: ${SUPPORTED_METHODS.join(", ")}`,
376
+ "VALIDATION_FAILED",
377
+ { method: request.paymentMethod },
378
+ PROVIDER2
379
+ );
380
+ }
381
+ core.validateCurrency(request.currency);
382
+ core.validateAmount(request.amount, {
383
+ min: MIN_AMOUNT_MGA,
384
+ max: MAX_AMOUNT_MGA,
385
+ providerName: PROVIDER2
386
+ });
387
+ if (!request.idempotencyKey) {
388
+ throw new core.ValidationError(
389
+ "Alefa payment creation requires an idempotency key",
390
+ "VALIDATION_FAILED",
391
+ void 0,
392
+ PROVIDER2
393
+ );
394
+ }
395
+ const customerPhone = core.toNationalFormat(request.customer.phone);
396
+ const preferredAction = request.providerData?.preferredAction || "auto";
397
+ const validityMinutes = typeof request.providerData?.validityMinutes === "number" ? request.providerData.validityMinutes : this.defaultValidityMinutes;
398
+ const body = {
399
+ amount: request.amount,
400
+ currency: request.currency,
401
+ operator: request.paymentMethod,
402
+ customer: { phone: customerPhone },
403
+ reference: request.reference,
404
+ preferredAction,
405
+ validityMinutes
406
+ };
407
+ if (this.callbackUrl) body.callbackUrl = this.callbackUrl;
408
+ if (request.description) body.description = request.description;
409
+ if (request.returnUrls) body.returnUrls = request.returnUrls;
410
+ if (request.metadata) body.metadata = request.metadata;
411
+ const raw = await core.executeWithRetry(
412
+ () => alefaRequest({
413
+ method: "POST",
414
+ url: `${this.baseUrl}/api/v1/payments`,
415
+ headers: this.headers({ "Idempotency-Key": request.idempotencyKey }),
416
+ body,
417
+ timeoutMs: this.timeoutMs,
418
+ fetchImpl: this.fetchImpl,
419
+ uncertainOnFailure: true
420
+ }),
421
+ { maxRetries: this.maxCreationRetries, initialDelayMs: this.retryInitialDelayMs }
422
+ );
423
+ const p = asRecord(raw);
424
+ const providerReference = String(p.transactionId || p.paymentReference || p.id || "");
425
+ if (!providerReference) {
426
+ throw new core.ValidationError(
427
+ "Alefa response missing transactionId",
428
+ "VALIDATION_FAILED",
429
+ void 0,
430
+ PROVIDER2
431
+ );
432
+ }
433
+ const { status, known } = mapAlefaStatus(p.status);
434
+ if (!known) {
435
+ this.onUnknownStatus?.(String(p.status), request.reference);
436
+ }
437
+ const action = parseCustomerAction(p.action);
438
+ return {
439
+ providerReference,
440
+ reference: typeof p.reference === "string" ? p.reference : request.reference,
441
+ amount: typeof p.amount === "number" ? p.amount : request.amount,
442
+ currency: "MGA",
443
+ status,
444
+ paymentMethod: request.paymentMethod,
445
+ rawProviderStatus: typeof p.status === "string" ? p.status : void 0,
446
+ checkoutUrl: typeof p.checkoutUrl === "string" ? p.checkoutUrl : void 0,
447
+ action,
448
+ expiresAt: typeof p.expiresAt === "string" ? p.expiresAt : void 0
449
+ };
450
+ }
451
+ async getPayment(providerReferenceOrLookup) {
452
+ let identifier;
453
+ let isMerchantRef = false;
454
+ if (typeof providerReferenceOrLookup === "string") {
455
+ identifier = providerReferenceOrLookup.trim();
456
+ } else {
457
+ if (providerReferenceOrLookup.providerReference) {
458
+ identifier = providerReferenceOrLookup.providerReference.trim();
459
+ } else if (providerReferenceOrLookup.reference) {
460
+ identifier = providerReferenceOrLookup.reference.trim();
461
+ isMerchantRef = true;
462
+ } else if (providerReferenceOrLookup.id) {
463
+ identifier = providerReferenceOrLookup.id.trim();
464
+ } else {
465
+ throw new core.ValidationError(
466
+ "Invalid payment lookup selector",
467
+ "INVALID_SELECTOR",
468
+ void 0,
469
+ PROVIDER2
470
+ );
471
+ }
472
+ }
473
+ if (!identifier) {
474
+ throw new core.ValidationError(
475
+ "Empty payment identifier provided",
476
+ "INVALID_SELECTOR",
477
+ void 0,
478
+ PROVIDER2
479
+ );
480
+ }
481
+ const url = isMerchantRef ? `${this.baseUrl}/api/v1/payments?reference=${encodeURIComponent(identifier)}` : `${this.baseUrl}/api/v1/payments/${encodeURIComponent(identifier)}`;
482
+ const raw = await alefaRequest({
483
+ method: "GET",
484
+ url,
485
+ headers: this.headers(),
486
+ timeoutMs: this.timeoutMs,
487
+ fetchImpl: this.fetchImpl,
488
+ uncertainOnFailure: false
489
+ });
490
+ const p = asRecord(raw);
491
+ const providerReference = String(p.transactionId || p.paymentReference || p.id || identifier);
492
+ const { status, known } = mapAlefaStatus(p.status);
493
+ if (!known) {
494
+ this.onUnknownStatus?.(String(p.status), providerReference);
495
+ }
496
+ const action = parseCustomerAction(p.action);
497
+ const operatorMethod = typeof p.operator === "string" && SUPPORTED_METHODS.includes(p.operator) ? p.operator : "mvola";
498
+ return {
499
+ providerReference,
500
+ reference: typeof p.reference === "string" ? p.reference : providerReference,
501
+ amount: typeof p.amount === "number" ? p.amount : 0,
502
+ currency: "MGA",
503
+ status,
504
+ paymentMethod: operatorMethod,
505
+ rawProviderStatus: typeof p.status === "string" ? p.status : void 0,
506
+ checkoutUrl: typeof p.checkoutUrl === "string" ? p.checkoutUrl : void 0,
507
+ action,
508
+ expiresAt: typeof p.expiresAt === "string" ? p.expiresAt : void 0
509
+ };
510
+ }
511
+ async verifyWebhook(request) {
512
+ if (!request || !(request.rawBody instanceof Uint8Array)) {
513
+ throw new core.WebhookVerificationError(
514
+ "Webhook verification requires unmodified rawBody as a Uint8Array (D-04)",
515
+ void 0,
516
+ PROVIDER2
517
+ );
518
+ }
519
+ if (!this.webhookSecret) {
520
+ throw new core.WebhookVerificationError(
521
+ "Alefa webhook verification requires a configured webhookSecret",
522
+ void 0,
523
+ PROVIDER2
524
+ );
525
+ }
526
+ const headerSig = request.headers["x-alefa-signature"] || request.headers["X-Alefa-Signature"] || request.headers["x-signature"];
527
+ if (!headerSig || typeof headerSig !== "string") {
528
+ throw new core.WebhookVerificationError(
529
+ "Missing required X-Alefa-Signature header on webhook request",
530
+ void 0,
531
+ PROVIDER2
532
+ );
533
+ }
534
+ const timestampHeader = request.headers["x-alefa-timestamp"] || request.headers["X-Alefa-Timestamp"];
535
+ if (typeof timestampHeader === "string") {
536
+ const webhookTime = new Date(timestampHeader).getTime();
537
+ const now = Date.now();
538
+ const maxDriftMs = 3e5;
539
+ if (Number.isNaN(webhookTime) || Math.abs(now - webhookTime) > maxDriftMs) {
540
+ throw new core.WebhookVerificationError(
541
+ `Alefa webhook timestamp drift exceeds ${maxDriftMs / 1e3}s tolerance`,
542
+ void 0,
543
+ PROVIDER2
544
+ );
545
+ }
546
+ }
547
+ const computedSig = crypto.createHmac("sha256", this.webhookSecret).update(request.rawBody).digest("hex");
548
+ const expectedBuf = Buffer.from(computedSig, "hex");
549
+ const actualBuf = Buffer.from(headerSig.trim(), "hex");
550
+ if (expectedBuf.length !== actualBuf.length || !crypto.timingSafeEqual(expectedBuf, actualBuf)) {
551
+ throw new core.WebhookVerificationError(
552
+ "Alefa webhook signature verification failed: invalid HMAC digest",
553
+ void 0,
554
+ PROVIDER2
555
+ );
556
+ }
557
+ let payload;
558
+ try {
559
+ const text = new TextDecoder().decode(request.rawBody);
560
+ payload = asRecord(JSON.parse(text));
561
+ } catch {
562
+ throw new core.WebhookVerificationError(
563
+ "Alefa webhook body is not valid JSON after signature validation",
564
+ void 0,
565
+ PROVIDER2
566
+ );
567
+ }
568
+ const transactionId = String(
569
+ payload.transactionId || payload.paymentReference || payload.id || ""
570
+ );
571
+ if (!transactionId) {
572
+ throw new core.WebhookVerificationError(
573
+ "Alefa webhook payload missing transactionId or payment reference",
574
+ void 0,
575
+ PROVIDER2
576
+ );
577
+ }
578
+ const { status, known } = mapAlefaStatus(payload.status);
579
+ if (!known) {
580
+ this.onUnknownStatus?.(String(payload.status), transactionId);
581
+ }
582
+ const eventId = String(payload.eventId || payload.id || `evt_${Date.now()}`);
583
+ const occurredAt = typeof payload.timestamp === "string" ? payload.timestamp : (/* @__PURE__ */ new Date()).toISOString();
584
+ return {
585
+ providerEventId: eventId,
586
+ providerReference: transactionId,
587
+ reference: typeof payload.reference === "string" ? payload.reference : void 0,
588
+ status,
589
+ occurredAt,
590
+ rawProviderStatus: typeof payload.status === "string" ? payload.status : void 0
591
+ };
592
+ }
593
+ };
594
+
595
+ exports.ALEFA_BASE_URLS = ALEFA_BASE_URLS;
596
+ exports.ALEFA_CONTRACT_METADATA = ALEFA_CONTRACT_METADATA;
597
+ exports.AlefaPaymentProvider = AlefaPaymentProvider;
598
+ exports.AlefaProvider = AlefaPaymentProvider;
599
+ exports.alefaQrActionFixture = alefaQrActionFixture;
600
+ exports.alefaRedirectActionFixture = alefaRedirectActionFixture;
601
+ exports.alefaRequest = alefaRequest;
602
+ exports.alefaSandboxNumbers = alefaSandboxNumbers;
603
+ exports.alefaUssdActionFixture = alefaUssdActionFixture;
604
+ exports.createMockAlefaPaymentResponse = createMockAlefaPaymentResponse;
605
+ exports.createMockAlefaWebhook = createMockAlefaWebhook;
606
+ exports.mapAlefaStatus = mapAlefaStatus;
607
+ //# sourceMappingURL=index.cjs.map
608
+ //# sourceMappingURL=index.cjs.map