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