@momorail/cinetpay 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Momorail contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,142 @@
1
+ import { IdempotencyStore, PaymentProvider, ProviderCapabilities, CollectionInput, ProviderCallOptions, Transaction, DisbursementInput, TransactionRef, RawWebhook, WebhookEvent } from '@momorail/core';
2
+
3
+ type CinetpayChannels = 'ALL' | 'MOBILE_MONEY' | 'CREDIT_CARD' | 'WALLET';
4
+ interface CinetpayOptions {
5
+ /** API key from the CinetPay dashboard. */
6
+ apikey: string;
7
+ /** Site id from the CinetPay dashboard. */
8
+ siteId: string;
9
+ /** Secret key from the CinetPay dashboard, used to verify the webhook `x-token`. */
10
+ secretKey: string;
11
+ /** Fallback `notify_url` when a collection doesn't set `callbackUrl`. */
12
+ notifyUrl?: string;
13
+ /** Fallback `return_url` when a collection doesn't set `returnUrl`. */
14
+ returnUrl?: string;
15
+ /** Channels to offer on the hosted checkout. Default `'ALL'`. */
16
+ channels?: CinetpayChannels;
17
+ /** Override the API base URL (e.g. a mock in tests). Default the production checkout host. */
18
+ baseUrl?: string;
19
+ /** Per-request timeout in ms. Default 15000. */
20
+ timeoutMs?: number;
21
+ /** Injectable fetch implementation. Default the global `fetch`. */
22
+ fetch?: typeof fetch;
23
+ /**
24
+ * Account password for the CinetPay **Transfer** API. Provide it to enable
25
+ * disbursement — without it `capabilities().disbursement` is `false`.
26
+ */
27
+ transferPassword?: string;
28
+ /** Override the Transfer API base URL (tests). Default `https://client.cinetpay.com/v1`. */
29
+ transferBaseUrl?: string;
30
+ /** Language for Transfer API messages. Default `'en'`. */
31
+ transferLang?: string;
32
+ /** Where the adapter remembers created transactions. Default: in-memory, per process. */
33
+ idempotencyStore?: IdempotencyStore;
34
+ }
35
+ /**
36
+ * CinetPay Checkout adapter. Implements collection (hosted checkout), status
37
+ * lookup, and webhook verification. Disbursement is a later milestone (it uses
38
+ * CinetPay's separate Transfer API).
39
+ *
40
+ * The adapter records each created transaction in an {@link IdempotencyStore}
41
+ * (in-memory per process by default; pass `idempotencyStore` for a shared,
42
+ * restart-surviving one). It is used to enrich the sparse `/payment/check`
43
+ * response (which omits the payment token) so `getTransaction` returns a stable
44
+ * `id`, and to make a retried `collection` with the same `reference` return the
45
+ * first transaction rather than starting a second.
46
+ */
47
+ declare class CinetpayProvider implements PaymentProvider {
48
+ #private;
49
+ readonly id = "cinetpay";
50
+ constructor(options: CinetpayOptions);
51
+ capabilities(): ProviderCapabilities;
52
+ collection(input: CollectionInput, options?: ProviderCallOptions): Promise<Transaction>;
53
+ disbursement(input: DisbursementInput, _options?: ProviderCallOptions): Promise<Transaction>;
54
+ getTransaction(ref: TransactionRef, options?: ProviderCallOptions): Promise<Transaction>;
55
+ parseWebhook(raw: RawWebhook): Promise<WebhookEvent>;
56
+ }
57
+
58
+ /**
59
+ * Raw CinetPay Checkout API shapes (v2). Only the fields Momorail reads are
60
+ * modelled; everything the provider returns is still handed back untouched on
61
+ * `Transaction.providerRaw`.
62
+ *
63
+ * @see https://docs.cinetpay.com/api/1.0-en/checkout/initialisation
64
+ * @see https://docs.cinetpay.com/api/1.0-en/checkout/verification
65
+ */
66
+ interface CinetpayInitResponse {
67
+ /** `"201"` on success (`CREATED`). */
68
+ code: string;
69
+ message: string;
70
+ description?: string;
71
+ data?: {
72
+ payment_token?: string;
73
+ payment_url?: string;
74
+ };
75
+ api_response_id?: string;
76
+ }
77
+ interface CinetpayCheckResponse {
78
+ /** `"00"` = accepted; other values carry an error or a not-yet-final state. */
79
+ code: string;
80
+ message: string;
81
+ data?: {
82
+ amount?: number | string;
83
+ currency?: string;
84
+ /** `ACCEPTED` | `REFUSED` | `PENDING` | `INITIATED` | `EXPIRED` | `UNKNOWN` */
85
+ status?: string;
86
+ payment_method?: string;
87
+ description?: string;
88
+ metadata?: string;
89
+ operator_id?: string;
90
+ payment_date?: string;
91
+ fund_availability_date?: string;
92
+ } | null;
93
+ }
94
+ /** Form fields CinetPay POSTs to `notify_url`. All values arrive as strings. */
95
+ interface CinetpayNotifyBody {
96
+ cpm_site_id?: string;
97
+ cpm_trans_id?: string;
98
+ cpm_trans_date?: string;
99
+ cpm_amount?: string;
100
+ cpm_currency?: string;
101
+ signature?: string;
102
+ payment_method?: string;
103
+ cel_phone_num?: string;
104
+ cpm_phone_prefixe?: string;
105
+ cpm_language?: string;
106
+ cpm_version?: string;
107
+ cpm_payment_config?: string;
108
+ cpm_page_action?: string;
109
+ cpm_custom?: string;
110
+ cpm_designation?: string;
111
+ cpm_error_message?: string;
112
+ [key: string]: string | undefined;
113
+ }
114
+
115
+ /** Parse an `application/x-www-form-urlencoded` (or already-parsed) notify body. */
116
+ declare function parseNotifyBody(body: string | Uint8Array | Record<string, unknown>): CinetpayNotifyBody;
117
+ /** The exact string CinetPay signs for a given notify body. */
118
+ declare function cinetpaySignaturePayload(fields: CinetpayNotifyBody): string;
119
+ declare function computeCinetpayToken(fields: CinetpayNotifyBody, secretKey: string): string;
120
+ /**
121
+ * Verify the `x-token` header against the body. Returns the parsed fields on
122
+ * success; throws {@link WebhookVerificationError} otherwise.
123
+ */
124
+ declare function verifyCinetpayNotification(headers: Record<string, string | string[] | undefined>, body: string | Uint8Array | Record<string, unknown>, secretKey: string): CinetpayNotifyBody;
125
+
126
+ /** CinetPay Transfer envelope. `code` is `0` (number) on success here, unlike Checkout's `"00"`. */
127
+ interface TransferEnvelope<T = unknown> {
128
+ code: number | string;
129
+ message?: string;
130
+ description?: string;
131
+ data?: T;
132
+ }
133
+ interface TransferRecord {
134
+ client_transaction_id?: string;
135
+ transaction_id?: string;
136
+ amount?: number | string;
137
+ /** `NEW` | `REC` | `VAL` | `REJECT` | `PENDING` … */
138
+ treatment_status?: string;
139
+ comment?: string;
140
+ }
141
+
142
+ export { type CinetpayChannels, type CinetpayCheckResponse, type CinetpayInitResponse, type CinetpayNotifyBody, type CinetpayOptions, CinetpayProvider, type TransferEnvelope, type TransferRecord, cinetpaySignaturePayload, computeCinetpayToken, parseNotifyBody, verifyCinetpayNotification };
package/dist/index.js ADDED
@@ -0,0 +1,650 @@
1
+ // src/provider.ts
2
+ import {
3
+ IdempotencyCache,
4
+ MemoryIdempotencyStore,
5
+ MomorailError as MomorailError2,
6
+ TransactionNotFoundError as TransactionNotFoundError2,
7
+ ValidationError,
8
+ isOperator,
9
+ webhookEventTypeForStatus
10
+ } from "@momorail/core";
11
+
12
+ // src/http.ts
13
+ import { ProviderUnavailableError as ProviderUnavailableError2 } from "@momorail/core";
14
+
15
+ // src/mapping.ts
16
+ import {
17
+ AuthError,
18
+ MomorailError,
19
+ ProviderUnavailableError,
20
+ RateLimitError,
21
+ TransactionNotFoundError
22
+ } from "@momorail/core";
23
+ var PROVIDER_ID = "cinetpay";
24
+ var SUCCESS_CODES = /* @__PURE__ */ new Set(["00", "201"]);
25
+ var AUTH_CODES = /* @__PURE__ */ new Set(["403", "608", "609"]);
26
+ function isNotFound(res) {
27
+ if (res.code === "404") return true;
28
+ if (/not[\s_]*found/i.test(res.message)) return true;
29
+ if (SUCCESS_CODES.has(res.code) || res.code === "662") return false;
30
+ return res.data == null;
31
+ }
32
+ function mapCheckStatus(res) {
33
+ switch (res.data?.status?.toUpperCase()) {
34
+ case "ACCEPTED":
35
+ return "succeeded";
36
+ case "REFUSED":
37
+ return "failed";
38
+ case "PENDING":
39
+ case "INITIATED":
40
+ case "WAITING_FOR_CUSTOMER":
41
+ return "processing";
42
+ case "EXPIRED":
43
+ return "expired";
44
+ default:
45
+ break;
46
+ }
47
+ switch (res.code) {
48
+ case "00":
49
+ return "succeeded";
50
+ case "627":
51
+ case "600":
52
+ return "failed";
53
+ case "662":
54
+ return "processing";
55
+ default:
56
+ return "unknown";
57
+ }
58
+ }
59
+ function mapTransferStatus(status) {
60
+ switch (status?.toUpperCase()) {
61
+ case "VAL":
62
+ return "succeeded";
63
+ case "REJECT":
64
+ case "REJECTED":
65
+ case "REFUND":
66
+ return "failed";
67
+ case "NEW":
68
+ case "REC":
69
+ case "PENDING":
70
+ case "CREATED":
71
+ return "processing";
72
+ default:
73
+ return "unknown";
74
+ }
75
+ }
76
+ var FAILURE_REASONS = {
77
+ "627": "user_declined",
78
+ "600": "provider_error",
79
+ "602": "insufficient_funds",
80
+ "604": "limit_exceeded"
81
+ };
82
+ function mapFailureReason(res) {
83
+ return FAILURE_REASONS[res.code] ?? "other";
84
+ }
85
+ function cinetpayError(code, message, providerRaw) {
86
+ const opts = { provider: PROVIDER_ID, providerRaw };
87
+ if (AUTH_CODES.has(code)) {
88
+ return new AuthError(`CinetPay rejected the credentials (${code}: ${message})`, opts);
89
+ }
90
+ if (code === "404" || /not[\s_]*found/i.test(message)) {
91
+ return new TransactionNotFoundError(
92
+ `CinetPay has no such transaction (${code}: ${message})`,
93
+ opts
94
+ );
95
+ }
96
+ if (code === "429") {
97
+ return new RateLimitError(`CinetPay throttled the request (${code}: ${message})`, opts);
98
+ }
99
+ if (code === "500" || code === "503") {
100
+ return new ProviderUnavailableError(`CinetPay is unavailable (${code}: ${message})`, opts);
101
+ }
102
+ return new MomorailError(`CinetPay error (${code}: ${message})`, "provider_error", opts);
103
+ }
104
+
105
+ // src/http.ts
106
+ var HttpClient = class {
107
+ #baseUrl;
108
+ #timeoutMs;
109
+ #fetch;
110
+ constructor(options) {
111
+ this.#baseUrl = options.baseUrl.replace(/\/$/, "");
112
+ this.#timeoutMs = options.timeoutMs;
113
+ this.#fetch = options.fetch;
114
+ }
115
+ async postJson(path, body, signal) {
116
+ const timeout = AbortSignal.timeout(this.#timeoutMs);
117
+ const composite = signal ? AbortSignal.any([signal, timeout]) : timeout;
118
+ let response;
119
+ try {
120
+ response = await this.#fetch(`${this.#baseUrl}${path}`, {
121
+ method: "POST",
122
+ headers: { "content-type": "application/json", accept: "application/json" },
123
+ body: JSON.stringify(body),
124
+ signal: composite
125
+ });
126
+ } catch (cause) {
127
+ if (signal?.aborted) throw cause;
128
+ throw new ProviderUnavailableError2("CinetPay request failed to complete", {
129
+ provider: PROVIDER_ID,
130
+ cause
131
+ });
132
+ }
133
+ const text = await response.text();
134
+ let parsed;
135
+ try {
136
+ parsed = text ? JSON.parse(text) : {};
137
+ } catch (cause) {
138
+ throw new ProviderUnavailableError2("CinetPay returned a non-JSON response", {
139
+ provider: PROVIDER_ID,
140
+ providerRaw: text,
141
+ cause
142
+ });
143
+ }
144
+ if (response.status >= 500) {
145
+ throw new ProviderUnavailableError2(`CinetPay responded ${response.status}`, {
146
+ provider: PROVIDER_ID,
147
+ providerRaw: parsed
148
+ });
149
+ }
150
+ return parsed;
151
+ }
152
+ };
153
+
154
+ // src/transfer.ts
155
+ import { AuthError as AuthError2, ProviderUnavailableError as ProviderUnavailableError3, RateLimitError as RateLimitError2 } from "@momorail/core";
156
+ var DEFAULT_TRANSFER_BASE_URL = "https://client.cinetpay.com/v1";
157
+ var okCode = (code) => Number(code) === 0;
158
+ var TransferClient = class {
159
+ #baseUrl;
160
+ #timeoutMs;
161
+ #fetch;
162
+ #apikey;
163
+ #password;
164
+ #lang;
165
+ #token;
166
+ #tokenExpiresAt = 0;
167
+ constructor(options) {
168
+ this.#baseUrl = options.baseUrl.replace(/\/$/, "");
169
+ this.#timeoutMs = options.timeoutMs;
170
+ this.#fetch = options.fetch;
171
+ this.#apikey = options.apikey;
172
+ this.#password = options.password;
173
+ this.#lang = options.lang;
174
+ }
175
+ async addContact(contact) {
176
+ await this.#withToken(
177
+ (token) => this.#form(`/transfer/contact?token=${token}&lang=${this.#lang}`, {
178
+ data: JSON.stringify([contact])
179
+ }),
180
+ // A contact that already exists is not an error for our purposes.
181
+ (env) => okCode(env.code) || /exist|d[ée]j[àa]|already/i.test(env.message ?? "")
182
+ );
183
+ }
184
+ send(entry) {
185
+ return this.#withToken(
186
+ (token) => this.#form(`/transfer/money/send/contact?token=${token}&lang=${this.#lang}`, {
187
+ data: JSON.stringify([pruneUndefined(entry)])
188
+ })
189
+ );
190
+ }
191
+ check(clientTransactionId) {
192
+ const q = new URLSearchParams({ lang: this.#lang, client_transaction_id: clientTransactionId });
193
+ return this.#withToken(
194
+ (token) => this.#form(`/transfer/check/money?token=${token}&${q}`, void 0, "GET")
195
+ );
196
+ }
197
+ async #login() {
198
+ const env = await this.#form("/auth/login", {
199
+ apikey: this.#apikey,
200
+ password: this.#password
201
+ });
202
+ const token = env.data?.token;
203
+ if (!okCode(env.code) || !token) {
204
+ throw new AuthError2(
205
+ `CinetPay Transfer login failed (${env.code}: ${env.message ?? "no token"})`,
206
+ { provider: PROVIDER_ID, providerRaw: env }
207
+ );
208
+ }
209
+ this.#token = token;
210
+ this.#tokenExpiresAt = Date.now() + 4 * 6e4;
211
+ return token;
212
+ }
213
+ async #tokenValue() {
214
+ if (this.#token && Date.now() < this.#tokenExpiresAt) return this.#token;
215
+ return this.#login();
216
+ }
217
+ /** Run `call` with a valid token; on a token-auth failure, re-login once and retry. */
218
+ async #withToken(call, accept = (env) => okCode(env.code)) {
219
+ let env = await call(await this.#tokenValue());
220
+ if (!accept(env) && /token/i.test(`${env.code} ${env.message ?? ""}`)) {
221
+ this.#token = void 0;
222
+ env = await call(await this.#tokenValue());
223
+ }
224
+ return env;
225
+ }
226
+ async #form(path, fields, method = "POST") {
227
+ let response;
228
+ try {
229
+ response = await this.#fetch(`${this.#baseUrl}${path}`, {
230
+ method,
231
+ headers: method === "POST" ? { "content-type": "application/x-www-form-urlencoded", accept: "application/json" } : { accept: "application/json" },
232
+ body: method === "POST" && fields ? new URLSearchParams(fields).toString() : void 0,
233
+ signal: AbortSignal.timeout(this.#timeoutMs)
234
+ });
235
+ } catch (cause) {
236
+ throw new ProviderUnavailableError3("CinetPay Transfer request failed to complete", {
237
+ provider: PROVIDER_ID,
238
+ cause
239
+ });
240
+ }
241
+ if (response.status === 429) {
242
+ throw new RateLimitError2("CinetPay Transfer throttled the request", {
243
+ provider: PROVIDER_ID
244
+ });
245
+ }
246
+ const text = await response.text();
247
+ let parsed;
248
+ try {
249
+ parsed = text ? JSON.parse(text) : {};
250
+ } catch (cause) {
251
+ throw new ProviderUnavailableError3("CinetPay Transfer returned a non-JSON response", {
252
+ provider: PROVIDER_ID,
253
+ providerRaw: text,
254
+ cause
255
+ });
256
+ }
257
+ if (response.status >= 500) {
258
+ throw new ProviderUnavailableError3(`CinetPay Transfer responded ${response.status}`, {
259
+ provider: PROVIDER_ID,
260
+ providerRaw: parsed
261
+ });
262
+ }
263
+ return parsed;
264
+ }
265
+ };
266
+ function pruneUndefined(obj) {
267
+ const out = {};
268
+ for (const [key, value] of Object.entries(obj)) {
269
+ if (value !== void 0) out[key] = value;
270
+ }
271
+ return out;
272
+ }
273
+
274
+ // src/webhook.ts
275
+ import { createHmac, timingSafeEqual } from "crypto";
276
+ import { WebhookVerificationError } from "@momorail/core";
277
+ var HMAC_FIELDS = [
278
+ "cpm_site_id",
279
+ "cpm_trans_id",
280
+ "cpm_trans_date",
281
+ "cpm_amount",
282
+ "cpm_currency",
283
+ "signature",
284
+ "payment_method",
285
+ "cel_phone_num",
286
+ "cpm_phone_prefixe",
287
+ "cpm_language",
288
+ "cpm_version",
289
+ "cpm_payment_config",
290
+ "cpm_page_action",
291
+ "cpm_custom",
292
+ "cpm_designation",
293
+ "cpm_error_message"
294
+ ];
295
+ function parseNotifyBody(body) {
296
+ if (typeof body === "string" || body instanceof Uint8Array) {
297
+ const raw = typeof body === "string" ? body : new TextDecoder().decode(body);
298
+ const params = new URLSearchParams(raw);
299
+ return Object.fromEntries(params.entries());
300
+ }
301
+ const out = {};
302
+ for (const [key, value] of Object.entries(body)) {
303
+ if (value != null) out[key] = String(value);
304
+ }
305
+ return out;
306
+ }
307
+ function cinetpaySignaturePayload(fields) {
308
+ return HMAC_FIELDS.map((name) => fields[name] ?? "").join("");
309
+ }
310
+ function computeCinetpayToken(fields, secretKey) {
311
+ return createHmac("sha256", secretKey).update(cinetpaySignaturePayload(fields), "utf8").digest("hex");
312
+ }
313
+ function safeEqualHex(a, b) {
314
+ if (a.length !== b.length) return false;
315
+ try {
316
+ return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
317
+ } catch {
318
+ return false;
319
+ }
320
+ }
321
+ function verifyCinetpayNotification(headers, body, secretKey) {
322
+ const headerToken = headerValue(headers, "x-token");
323
+ if (!headerToken) {
324
+ throw new WebhookVerificationError("Missing x-token header on CinetPay notification", {
325
+ provider: PROVIDER_ID
326
+ });
327
+ }
328
+ const fields = parseNotifyBody(body);
329
+ const expected = computeCinetpayToken(fields, secretKey);
330
+ if (!safeEqualHex(headerToken.toLowerCase(), expected)) {
331
+ throw new WebhookVerificationError("CinetPay x-token does not match the request body", {
332
+ provider: PROVIDER_ID
333
+ });
334
+ }
335
+ return fields;
336
+ }
337
+ function headerValue(headers, name) {
338
+ const direct = headers[name] ?? headers[name.toLowerCase()];
339
+ const found = direct ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
340
+ return Array.isArray(found) ? found[0] : found;
341
+ }
342
+
343
+ // src/provider.ts
344
+ var DEFAULT_BASE_URL = "https://api-checkout.cinetpay.com/v2";
345
+ var DEFAULT_TIMEOUT_MS = 15e3;
346
+ var CALLING_CODE = {
347
+ ci: "225",
348
+ sn: "221",
349
+ ml: "223",
350
+ bf: "226",
351
+ bj: "229",
352
+ tg: "228",
353
+ ne: "227",
354
+ cm: "237",
355
+ cg: "242",
356
+ cd: "243",
357
+ td: "235",
358
+ ga: "241"
359
+ };
360
+ var CAPABILITIES = {
361
+ collection: true,
362
+ disbursement: false,
363
+ lookupByReference: true,
364
+ hostedCheckout: true,
365
+ operators: [
366
+ "orange_ml",
367
+ "moov_ml",
368
+ "orange_ci",
369
+ "mtn_ci",
370
+ "moov_ci",
371
+ "wave_ci",
372
+ "orange_sn",
373
+ "free_sn",
374
+ "orange_bf",
375
+ "moov_bf",
376
+ "mtn_bj",
377
+ "moov_bj"
378
+ ],
379
+ currencies: ["XOF", "XAF"]
380
+ };
381
+ var CinetpayProvider = class {
382
+ id = PROVIDER_ID;
383
+ #apikey;
384
+ #siteId;
385
+ #secretKey;
386
+ #notifyUrl;
387
+ #returnUrl;
388
+ #channels;
389
+ #http;
390
+ #transfer;
391
+ #cache;
392
+ constructor(options) {
393
+ for (const key of ["apikey", "siteId", "secretKey"]) {
394
+ if (!options[key]?.trim()) {
395
+ throw new ValidationError(`CinetpayProvider requires a non-empty "${key}"`);
396
+ }
397
+ }
398
+ this.#apikey = options.apikey;
399
+ this.#siteId = options.siteId;
400
+ this.#secretKey = options.secretKey;
401
+ this.#notifyUrl = options.notifyUrl;
402
+ this.#returnUrl = options.returnUrl;
403
+ this.#channels = options.channels ?? "ALL";
404
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
405
+ const fetchImpl = options.fetch ?? globalThis.fetch;
406
+ this.#cache = new IdempotencyCache(
407
+ options.idempotencyStore ?? new MemoryIdempotencyStore(),
408
+ this.id
409
+ );
410
+ this.#http = new HttpClient({
411
+ baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
412
+ timeoutMs,
413
+ fetch: fetchImpl
414
+ });
415
+ this.#transfer = options.transferPassword ? new TransferClient({
416
+ baseUrl: options.transferBaseUrl ?? DEFAULT_TRANSFER_BASE_URL,
417
+ timeoutMs,
418
+ fetch: fetchImpl,
419
+ apikey: options.apikey,
420
+ password: options.transferPassword,
421
+ lang: options.transferLang ?? "en"
422
+ }) : void 0;
423
+ }
424
+ capabilities() {
425
+ return { ...CAPABILITIES, disbursement: this.#transfer !== void 0 };
426
+ }
427
+ async collection(input, options) {
428
+ if (input.customer.operator !== void 0 && !isOperator(input.customer.operator)) {
429
+ throw new ValidationError(`Unknown operator: ${input.customer.operator}`, {
430
+ provider: this.id
431
+ });
432
+ }
433
+ const cached = await this.#cache.byReference(input.reference);
434
+ if (cached) return structuredClone(cached);
435
+ const notifyUrl = input.callbackUrl ?? this.#notifyUrl;
436
+ const returnUrl = input.returnUrl ?? this.#returnUrl;
437
+ if (!notifyUrl) {
438
+ throw new ValidationError(
439
+ "CinetPay needs a webhook URL: pass collection input.callbackUrl or set CinetpayOptions.notifyUrl",
440
+ { provider: this.id }
441
+ );
442
+ }
443
+ const body = pruneUndefined2({
444
+ apikey: this.#apikey,
445
+ site_id: this.#siteId,
446
+ transaction_id: input.reference,
447
+ amount: input.amount.amount,
448
+ currency: input.amount.currency,
449
+ description: input.description ?? `Payment ${input.reference}`,
450
+ notify_url: notifyUrl,
451
+ return_url: returnUrl,
452
+ channels: this.#channels,
453
+ customer_name: input.customer.lastName,
454
+ customer_surname: input.customer.firstName,
455
+ customer_email: input.customer.email,
456
+ customer_phone_number: input.customer.phone,
457
+ metadata: input.metadata ? JSON.stringify(input.metadata) : void 0
458
+ });
459
+ const res = await this.#http.postJson("/payment", body, options?.signal);
460
+ if (res.code !== "201" || !res.data?.payment_token) {
461
+ throw cinetpayError(res.code, res.message, res);
462
+ }
463
+ const now = (/* @__PURE__ */ new Date()).toISOString();
464
+ const txn = {
465
+ provider: this.id,
466
+ id: res.data.payment_token,
467
+ reference: input.reference,
468
+ type: "collection",
469
+ status: "pending",
470
+ amount: input.amount,
471
+ operator: input.customer.operator,
472
+ customer: input.customer,
473
+ checkoutUrl: res.data.payment_url,
474
+ providerRaw: res,
475
+ createdAt: now,
476
+ updatedAt: now
477
+ };
478
+ await this.#cache.remember(txn);
479
+ return structuredClone(txn);
480
+ }
481
+ async disbursement(input, _options) {
482
+ if (!this.#transfer) {
483
+ throw new MomorailError2(
484
+ "CinetPay disbursement needs CinetpayOptions.transferPassword (the Transfer API)",
485
+ "not_supported",
486
+ { provider: this.id }
487
+ );
488
+ }
489
+ const operator = input.recipient.operator;
490
+ if (!operator || !isOperator(operator)) {
491
+ throw new ValidationError("CinetPay disbursement needs recipient.operator", {
492
+ provider: this.id
493
+ });
494
+ }
495
+ const prefix = CALLING_CODE[countryOf(operator)];
496
+ if (!prefix) {
497
+ throw new ValidationError(`CinetPay has no calling code for operator "${operator}"`, {
498
+ provider: this.id
499
+ });
500
+ }
501
+ if (input.amount.amount % 5 !== 0) {
502
+ throw new ValidationError("CinetPay transfers must be a multiple of 5", {
503
+ provider: this.id
504
+ });
505
+ }
506
+ const cached = await this.#cache.byReference(input.reference);
507
+ if (cached) return structuredClone(cached);
508
+ const phone = stripPrefix(input.recipient.phone, prefix);
509
+ await this.#transfer.addContact({
510
+ prefix,
511
+ phone,
512
+ name: input.recipient.lastName ?? "Momorail",
513
+ surname: input.recipient.firstName ?? "Recipient",
514
+ email: input.recipient.email ?? `${input.reference}@recipient.invalid`
515
+ });
516
+ const res = await this.#transfer.send({
517
+ prefix,
518
+ phone,
519
+ amount: input.amount.amount,
520
+ client_transaction_id: input.reference,
521
+ notify_url: input.callbackUrl ?? this.#notifyUrl
522
+ });
523
+ const record = firstRecord(res.data);
524
+ if (Number(res.code) !== 0) {
525
+ throw cinetpayError(String(res.code), res.message ?? "transfer failed", res);
526
+ }
527
+ const status = mapTransferStatus(record?.treatment_status);
528
+ const now = (/* @__PURE__ */ new Date()).toISOString();
529
+ const txn = {
530
+ provider: this.id,
531
+ id: String(record?.transaction_id ?? input.reference),
532
+ reference: input.reference,
533
+ type: "disbursement",
534
+ status: status === "unknown" ? "processing" : status,
535
+ amount: input.amount,
536
+ operator,
537
+ customer: input.recipient,
538
+ providerRaw: res,
539
+ createdAt: now,
540
+ updatedAt: now
541
+ };
542
+ await this.#cache.remember(txn);
543
+ return structuredClone(txn);
544
+ }
545
+ async getTransaction(ref, options) {
546
+ const known = (ref.reference ? await this.#cache.byReference(ref.reference) : void 0) ?? (ref.id ? await this.#cache.byId(ref.id) : void 0);
547
+ if (known?.type === "disbursement" && this.#transfer) {
548
+ const res2 = await this.#transfer.check(known.reference);
549
+ const record = firstRecord(res2.data);
550
+ if (!record) {
551
+ throw new TransactionNotFoundError2("CinetPay has no such transfer", { provider: this.id });
552
+ }
553
+ const status2 = mapTransferStatus(record.treatment_status);
554
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
555
+ const txn2 = {
556
+ ...known,
557
+ status: status2 === "unknown" ? known.status : status2,
558
+ failureReason: status2 === "failed" ? "provider_error" : known.failureReason,
559
+ providerRaw: res2,
560
+ updatedAt: now2
561
+ };
562
+ await this.#cache.remember(txn2);
563
+ return structuredClone(txn2);
564
+ }
565
+ const lookup = pruneUndefined2({
566
+ apikey: this.#apikey,
567
+ site_id: this.#siteId,
568
+ transaction_id: ref.reference ?? known?.reference,
569
+ token: ref.reference ? void 0 : ref.id
570
+ });
571
+ if (!lookup.transaction_id && !lookup.token) {
572
+ throw new ValidationError("getTransaction requires an id or a reference", {
573
+ provider: this.id
574
+ });
575
+ }
576
+ const res = await this.#http.postJson(
577
+ "/payment/check",
578
+ lookup,
579
+ options?.signal
580
+ );
581
+ if (isNotFound(res)) {
582
+ throw cinetpayError(res.code === "00" ? "404" : res.code, res.message, res);
583
+ }
584
+ const status = mapCheckStatus(res);
585
+ const now = (/* @__PURE__ */ new Date()).toISOString();
586
+ const txn = {
587
+ provider: this.id,
588
+ id: known?.id ?? ref.id ?? ref.reference ?? "",
589
+ reference: known?.reference ?? ref.reference ?? ref.id ?? "",
590
+ type: "collection",
591
+ status,
592
+ amount: known?.amount ?? coerceAmount(res),
593
+ operator: known?.operator,
594
+ customer: known?.customer,
595
+ checkoutUrl: known?.checkoutUrl,
596
+ failureReason: status === "failed" ? mapFailureReason(res) : void 0,
597
+ providerRaw: res,
598
+ createdAt: known?.createdAt ?? now,
599
+ updatedAt: now
600
+ };
601
+ await this.#cache.remember(txn);
602
+ return structuredClone(txn);
603
+ }
604
+ async parseWebhook(raw) {
605
+ const fields = verifyCinetpayNotification(raw.headers, raw.body, this.#secretKey);
606
+ const transactionId = fields.cpm_trans_id;
607
+ if (!transactionId) {
608
+ throw new ValidationError("CinetPay notification is missing cpm_trans_id", {
609
+ provider: this.id
610
+ });
611
+ }
612
+ const transaction = await this.getTransaction({ reference: transactionId });
613
+ return {
614
+ type: webhookEventTypeForStatus(transaction.status),
615
+ transaction,
616
+ providerRaw: fields
617
+ };
618
+ }
619
+ };
620
+ function countryOf(operator) {
621
+ return operator.slice(operator.lastIndexOf("_") + 1);
622
+ }
623
+ function stripPrefix(phone, prefix) {
624
+ const digits = phone.replace(/\D/g, "");
625
+ return digits.startsWith(prefix) ? digits.slice(prefix.length) : digits;
626
+ }
627
+ function firstRecord(data) {
628
+ return Array.isArray(data) ? data[0] : void 0;
629
+ }
630
+ function coerceAmount(res) {
631
+ const raw = res.data?.amount;
632
+ const amount = typeof raw === "string" ? Number.parseInt(raw, 10) : raw ?? 0;
633
+ const currency = res.data?.currency === "XAF" ? "XAF" : "XOF";
634
+ return { amount: Number.isFinite(amount) ? amount : 0, currency };
635
+ }
636
+ function pruneUndefined2(obj) {
637
+ const out = {};
638
+ for (const [key, value] of Object.entries(obj)) {
639
+ if (value !== void 0) out[key] = value;
640
+ }
641
+ return out;
642
+ }
643
+ export {
644
+ CinetpayProvider,
645
+ cinetpaySignaturePayload,
646
+ computeCinetpayToken,
647
+ parseNotifyBody,
648
+ verifyCinetpayNotification
649
+ };
650
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.ts","../src/http.ts","../src/mapping.ts","../src/transfer.ts","../src/webhook.ts"],"sourcesContent":["import {\n type CollectionInput,\n type DisbursementInput,\n IdempotencyCache,\n type IdempotencyStore,\n MemoryIdempotencyStore,\n MomorailError,\n type Operator,\n type PaymentProvider,\n type ProviderCallOptions,\n type ProviderCapabilities,\n type RawWebhook,\n type Transaction,\n TransactionNotFoundError,\n type TransactionRef,\n ValidationError,\n type WebhookEvent,\n isOperator,\n webhookEventTypeForStatus,\n} from '@momorail/core';\nimport { HttpClient } from './http.js';\nimport {\n PROVIDER_ID,\n cinetpayError,\n isNotFound,\n mapCheckStatus,\n mapFailureReason,\n mapTransferStatus,\n} from './mapping.js';\nimport { DEFAULT_TRANSFER_BASE_URL, TransferClient, type TransferRecord } from './transfer.js';\nimport type { CinetpayCheckResponse, CinetpayInitResponse } from './types.js';\nimport { verifyCinetpayNotification } from './webhook.js';\n\nconst DEFAULT_BASE_URL = 'https://api-checkout.cinetpay.com/v2';\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/** ISO country → E.164 calling code, for the markets CinetPay Transfer covers. */\nconst CALLING_CODE: Record<string, string> = {\n ci: '225',\n sn: '221',\n ml: '223',\n bf: '226',\n bj: '229',\n tg: '228',\n ne: '227',\n cm: '237',\n cg: '242',\n cd: '243',\n td: '235',\n ga: '241',\n};\n\nexport type CinetpayChannels = 'ALL' | 'MOBILE_MONEY' | 'CREDIT_CARD' | 'WALLET';\n\nexport interface CinetpayOptions {\n /** API key from the CinetPay dashboard. */\n apikey: string;\n /** Site id from the CinetPay dashboard. */\n siteId: string;\n /** Secret key from the CinetPay dashboard, used to verify the webhook `x-token`. */\n secretKey: string;\n /** Fallback `notify_url` when a collection doesn't set `callbackUrl`. */\n notifyUrl?: string;\n /** Fallback `return_url` when a collection doesn't set `returnUrl`. */\n returnUrl?: string;\n /** Channels to offer on the hosted checkout. Default `'ALL'`. */\n channels?: CinetpayChannels;\n /** Override the API base URL (e.g. a mock in tests). Default the production checkout host. */\n baseUrl?: string;\n /** Per-request timeout in ms. Default 15000. */\n timeoutMs?: number;\n /** Injectable fetch implementation. Default the global `fetch`. */\n fetch?: typeof fetch;\n /**\n * Account password for the CinetPay **Transfer** API. Provide it to enable\n * disbursement — without it `capabilities().disbursement` is `false`.\n */\n transferPassword?: string;\n /** Override the Transfer API base URL (tests). Default `https://client.cinetpay.com/v1`. */\n transferBaseUrl?: string;\n /** Language for Transfer API messages. Default `'en'`. */\n transferLang?: string;\n /** Where the adapter remembers created transactions. Default: in-memory, per process. */\n idempotencyStore?: IdempotencyStore;\n}\n\nconst CAPABILITIES: ProviderCapabilities = {\n collection: true,\n disbursement: false,\n lookupByReference: true,\n hostedCheckout: true,\n operators: [\n 'orange_ml',\n 'moov_ml',\n 'orange_ci',\n 'mtn_ci',\n 'moov_ci',\n 'wave_ci',\n 'orange_sn',\n 'free_sn',\n 'orange_bf',\n 'moov_bf',\n 'mtn_bj',\n 'moov_bj',\n ],\n currencies: ['XOF', 'XAF'],\n};\n\n/**\n * CinetPay Checkout adapter. Implements collection (hosted checkout), status\n * lookup, and webhook verification. Disbursement is a later milestone (it uses\n * CinetPay's separate Transfer API).\n *\n * The adapter records each created transaction in an {@link IdempotencyStore}\n * (in-memory per process by default; pass `idempotencyStore` for a shared,\n * restart-surviving one). It is used to enrich the sparse `/payment/check`\n * response (which omits the payment token) so `getTransaction` returns a stable\n * `id`, and to make a retried `collection` with the same `reference` return the\n * first transaction rather than starting a second.\n */\nexport class CinetpayProvider implements PaymentProvider {\n readonly id = PROVIDER_ID;\n\n readonly #apikey: string;\n readonly #siteId: string;\n readonly #secretKey: string;\n readonly #notifyUrl: string | undefined;\n readonly #returnUrl: string | undefined;\n readonly #channels: CinetpayChannels;\n readonly #http: HttpClient;\n readonly #transfer: TransferClient | undefined;\n readonly #cache: IdempotencyCache;\n\n constructor(options: CinetpayOptions) {\n for (const key of ['apikey', 'siteId', 'secretKey'] as const) {\n if (!options[key]?.trim()) {\n throw new ValidationError(`CinetpayProvider requires a non-empty \"${key}\"`);\n }\n }\n this.#apikey = options.apikey;\n this.#siteId = options.siteId;\n this.#secretKey = options.secretKey;\n this.#notifyUrl = options.notifyUrl;\n this.#returnUrl = options.returnUrl;\n this.#channels = options.channels ?? 'ALL';\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const fetchImpl = options.fetch ?? globalThis.fetch;\n this.#cache = new IdempotencyCache(\n options.idempotencyStore ?? new MemoryIdempotencyStore(),\n this.id,\n );\n this.#http = new HttpClient({\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs,\n fetch: fetchImpl,\n });\n this.#transfer = options.transferPassword\n ? new TransferClient({\n baseUrl: options.transferBaseUrl ?? DEFAULT_TRANSFER_BASE_URL,\n timeoutMs,\n fetch: fetchImpl,\n apikey: options.apikey,\n password: options.transferPassword,\n lang: options.transferLang ?? 'en',\n })\n : undefined;\n }\n\n capabilities(): ProviderCapabilities {\n return { ...CAPABILITIES, disbursement: this.#transfer !== undefined };\n }\n\n async collection(input: CollectionInput, options?: ProviderCallOptions): Promise<Transaction> {\n if (input.customer.operator !== undefined && !isOperator(input.customer.operator)) {\n throw new ValidationError(`Unknown operator: ${input.customer.operator}`, {\n provider: this.id,\n });\n }\n\n const cached = await this.#cache.byReference(input.reference);\n if (cached) return structuredClone(cached);\n\n const notifyUrl = input.callbackUrl ?? this.#notifyUrl;\n const returnUrl = input.returnUrl ?? this.#returnUrl;\n if (!notifyUrl) {\n throw new ValidationError(\n 'CinetPay needs a webhook URL: pass collection input.callbackUrl or set CinetpayOptions.notifyUrl',\n { provider: this.id },\n );\n }\n\n const body = pruneUndefined({\n apikey: this.#apikey,\n site_id: this.#siteId,\n transaction_id: input.reference,\n amount: input.amount.amount,\n currency: input.amount.currency,\n description: input.description ?? `Payment ${input.reference}`,\n notify_url: notifyUrl,\n return_url: returnUrl,\n channels: this.#channels,\n customer_name: input.customer.lastName,\n customer_surname: input.customer.firstName,\n customer_email: input.customer.email,\n customer_phone_number: input.customer.phone,\n metadata: input.metadata ? JSON.stringify(input.metadata) : undefined,\n });\n\n const res = await this.#http.postJson<CinetpayInitResponse>('/payment', body, options?.signal);\n if (res.code !== '201' || !res.data?.payment_token) {\n throw cinetpayError(res.code, res.message, res);\n }\n\n const now = new Date().toISOString();\n const txn: Transaction = {\n provider: this.id,\n id: res.data.payment_token,\n reference: input.reference,\n type: 'collection',\n status: 'pending',\n amount: input.amount,\n operator: input.customer.operator,\n customer: input.customer,\n checkoutUrl: res.data.payment_url,\n providerRaw: res,\n createdAt: now,\n updatedAt: now,\n };\n\n await this.#cache.remember(txn);\n return structuredClone(txn);\n }\n\n async disbursement(\n input: DisbursementInput,\n _options?: ProviderCallOptions,\n ): Promise<Transaction> {\n if (!this.#transfer) {\n throw new MomorailError(\n 'CinetPay disbursement needs CinetpayOptions.transferPassword (the Transfer API)',\n 'not_supported',\n { provider: this.id },\n );\n }\n const operator = input.recipient.operator;\n if (!operator || !isOperator(operator)) {\n throw new ValidationError('CinetPay disbursement needs recipient.operator', {\n provider: this.id,\n });\n }\n const prefix = CALLING_CODE[countryOf(operator)];\n if (!prefix) {\n throw new ValidationError(`CinetPay has no calling code for operator \"${operator}\"`, {\n provider: this.id,\n });\n }\n if (input.amount.amount % 5 !== 0) {\n throw new ValidationError('CinetPay transfers must be a multiple of 5', {\n provider: this.id,\n });\n }\n\n const cached = await this.#cache.byReference(input.reference);\n if (cached) return structuredClone(cached);\n\n const phone = stripPrefix(input.recipient.phone, prefix);\n await this.#transfer.addContact({\n prefix,\n phone,\n name: input.recipient.lastName ?? 'Momorail',\n surname: input.recipient.firstName ?? 'Recipient',\n email: input.recipient.email ?? `${input.reference}@recipient.invalid`,\n });\n\n const res = await this.#transfer.send({\n prefix,\n phone,\n amount: input.amount.amount,\n client_transaction_id: input.reference,\n notify_url: input.callbackUrl ?? this.#notifyUrl,\n });\n const record = firstRecord(res.data);\n if (Number(res.code) !== 0) {\n throw cinetpayError(String(res.code), res.message ?? 'transfer failed', res);\n }\n\n const status = mapTransferStatus(record?.treatment_status);\n const now = new Date().toISOString();\n const txn: Transaction = {\n provider: this.id,\n id: String(record?.transaction_id ?? input.reference),\n reference: input.reference,\n type: 'disbursement',\n status: status === 'unknown' ? 'processing' : status,\n amount: input.amount,\n operator,\n customer: input.recipient,\n providerRaw: res,\n createdAt: now,\n updatedAt: now,\n };\n\n await this.#cache.remember(txn);\n return structuredClone(txn);\n }\n\n async getTransaction(ref: TransactionRef, options?: ProviderCallOptions): Promise<Transaction> {\n const known =\n (ref.reference ? await this.#cache.byReference(ref.reference) : undefined) ??\n (ref.id ? await this.#cache.byId(ref.id) : undefined);\n\n if (known?.type === 'disbursement' && this.#transfer) {\n const res = await this.#transfer.check(known.reference);\n const record = firstRecord(res.data);\n if (!record) {\n throw new TransactionNotFoundError('CinetPay has no such transfer', { provider: this.id });\n }\n const status = mapTransferStatus(record.treatment_status);\n const now = new Date().toISOString();\n const txn: Transaction = {\n ...known,\n status: status === 'unknown' ? known.status : status,\n failureReason: status === 'failed' ? 'provider_error' : known.failureReason,\n providerRaw: res,\n updatedAt: now,\n };\n await this.#cache.remember(txn);\n return structuredClone(txn);\n }\n\n const lookup = pruneUndefined({\n apikey: this.#apikey,\n site_id: this.#siteId,\n transaction_id: ref.reference ?? known?.reference,\n token: ref.reference ? undefined : ref.id,\n });\n if (!lookup.transaction_id && !lookup.token) {\n throw new ValidationError('getTransaction requires an id or a reference', {\n provider: this.id,\n });\n }\n\n const res = await this.#http.postJson<CinetpayCheckResponse>(\n '/payment/check',\n lookup,\n options?.signal,\n );\n\n if (isNotFound(res)) {\n throw cinetpayError(res.code === '00' ? '404' : res.code, res.message, res);\n }\n\n const status = mapCheckStatus(res);\n const now = new Date().toISOString();\n const txn: Transaction = {\n provider: this.id,\n id: known?.id ?? ref.id ?? ref.reference ?? '',\n reference: known?.reference ?? ref.reference ?? ref.id ?? '',\n type: 'collection',\n status,\n amount: known?.amount ?? coerceAmount(res),\n operator: known?.operator,\n customer: known?.customer,\n checkoutUrl: known?.checkoutUrl,\n failureReason: status === 'failed' ? mapFailureReason(res) : undefined,\n providerRaw: res,\n createdAt: known?.createdAt ?? now,\n updatedAt: now,\n };\n\n await this.#cache.remember(txn);\n return structuredClone(txn);\n }\n\n async parseWebhook(raw: RawWebhook): Promise<WebhookEvent> {\n const fields = verifyCinetpayNotification(raw.headers, raw.body, this.#secretKey);\n const transactionId = fields.cpm_trans_id;\n if (!transactionId) {\n throw new ValidationError('CinetPay notification is missing cpm_trans_id', {\n provider: this.id,\n });\n }\n\n const transaction = await this.getTransaction({ reference: transactionId });\n return {\n type: webhookEventTypeForStatus(transaction.status),\n transaction,\n providerRaw: fields,\n };\n }\n}\n\nfunction countryOf(operator: Operator): string {\n return operator.slice(operator.lastIndexOf('_') + 1);\n}\n\nfunction stripPrefix(phone: string, prefix: string): string {\n const digits = phone.replace(/\\D/g, '');\n return digits.startsWith(prefix) ? digits.slice(prefix.length) : digits;\n}\n\nfunction firstRecord(data: TransferRecord[] | undefined): TransferRecord | undefined {\n return Array.isArray(data) ? data[0] : undefined;\n}\n\nfunction coerceAmount(res: CinetpayCheckResponse): Transaction['amount'] {\n const raw = res.data?.amount;\n const amount = typeof raw === 'string' ? Number.parseInt(raw, 10) : (raw ?? 0);\n const currency = res.data?.currency === 'XAF' ? 'XAF' : 'XOF';\n return { amount: Number.isFinite(amount) ? amount : 0, currency };\n}\n\nfunction pruneUndefined<T extends Record<string, unknown>>(obj: T): { [K in keyof T]: T[K] } {\n const out = {} as { [K in keyof T]: T[K] };\n for (const [key, value] of Object.entries(obj)) {\n if (value !== undefined) out[key as keyof T] = value as T[keyof T];\n }\n return out;\n}\n","import { ProviderUnavailableError } from '@momorail/core';\nimport { PROVIDER_ID } from './mapping.js';\n\nexport type FetchLike = typeof fetch;\n\nexport interface HttpClientOptions {\n baseUrl: string;\n timeoutMs: number;\n fetch: FetchLike;\n}\n\n/** Minimal JSON POST helper: merges abort signals, enforces a timeout, parses JSON. */\nexport class HttpClient {\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #fetch: FetchLike;\n\n constructor(options: HttpClientOptions) {\n this.#baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.#timeoutMs = options.timeoutMs;\n this.#fetch = options.fetch;\n }\n\n async postJson<T>(path: string, body: unknown, signal?: AbortSignal): Promise<T> {\n const timeout = AbortSignal.timeout(this.#timeoutMs);\n const composite = signal ? AbortSignal.any([signal, timeout]) : timeout;\n\n let response: Response;\n try {\n response = await this.#fetch(`${this.#baseUrl}${path}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify(body),\n signal: composite,\n });\n } catch (cause) {\n if (signal?.aborted) throw cause;\n throw new ProviderUnavailableError('CinetPay request failed to complete', {\n provider: PROVIDER_ID,\n cause,\n });\n }\n\n const text = await response.text();\n let parsed: unknown;\n try {\n parsed = text ? JSON.parse(text) : {};\n } catch (cause) {\n throw new ProviderUnavailableError('CinetPay returned a non-JSON response', {\n provider: PROVIDER_ID,\n providerRaw: text,\n cause,\n });\n }\n\n if (response.status >= 500) {\n throw new ProviderUnavailableError(`CinetPay responded ${response.status}`, {\n provider: PROVIDER_ID,\n providerRaw: parsed,\n });\n }\n\n return parsed as T;\n }\n}\n","import {\n AuthError,\n type FailureReason,\n MomorailError,\n ProviderUnavailableError,\n RateLimitError,\n TransactionNotFoundError,\n type TransactionStatus,\n} from '@momorail/core';\nimport type { CinetpayCheckResponse } from './types.js';\n\nexport const PROVIDER_ID = 'cinetpay';\n\nconst SUCCESS_CODES = new Set(['00', '201']);\n/** Codes that mean the caller's apikey / site_id pair was rejected. */\nconst AUTH_CODES = new Set(['403', '608', '609']);\n\n/**\n * True when a `/payment/check` response means \"no such transaction\".\n *\n * NOTE: CinetPay's exact not-found code is not unambiguous in the public docs\n * (`404` / `AUTH_NOT_FOUND` are both seen). This also treats a null `data` with\n * a non-success, non-pending code as not-found. Re-confirm against the sandbox\n * before a production release.\n */\nexport function isNotFound(res: CinetpayCheckResponse): boolean {\n if (res.code === '404') return true;\n if (/not[\\s_]*found/i.test(res.message)) return true;\n if (SUCCESS_CODES.has(res.code) || res.code === '662') return false;\n return res.data == null;\n}\n\n/**\n * Reduce a `/payment/check` response to a Momorail status. Prefers the explicit\n * `data.status` string and falls back to the top-level `code`.\n */\nexport function mapCheckStatus(res: CinetpayCheckResponse): TransactionStatus {\n switch (res.data?.status?.toUpperCase()) {\n case 'ACCEPTED':\n return 'succeeded';\n case 'REFUSED':\n return 'failed';\n case 'PENDING':\n case 'INITIATED':\n case 'WAITING_FOR_CUSTOMER':\n return 'processing';\n case 'EXPIRED':\n return 'expired';\n default:\n break;\n }\n\n switch (res.code) {\n case '00':\n return 'succeeded';\n case '627':\n case '600':\n return 'failed';\n case '662':\n return 'processing';\n default:\n return 'unknown';\n }\n}\n\n/** Map a CinetPay Transfer `treatment_status` onto a Momorail status. */\nexport function mapTransferStatus(status: string | undefined): TransactionStatus {\n switch (status?.toUpperCase()) {\n case 'VAL':\n return 'succeeded';\n case 'REJECT':\n case 'REJECTED':\n case 'REFUND':\n return 'failed';\n case 'NEW':\n case 'REC':\n case 'PENDING':\n case 'CREATED':\n return 'processing';\n default:\n return 'unknown';\n }\n}\n\nconst FAILURE_REASONS: Record<string, FailureReason> = {\n '627': 'user_declined',\n '600': 'provider_error',\n '602': 'insufficient_funds',\n '604': 'limit_exceeded',\n};\n\nexport function mapFailureReason(res: CinetpayCheckResponse): FailureReason {\n return FAILURE_REASONS[res.code] ?? 'other';\n}\n\n/**\n * Translate a non-OK CinetPay response (either endpoint) into the matching\n * `MomorailError` subclass. `code` is CinetPay's top-level `code` field.\n */\nexport function cinetpayError(code: string, message: string, providerRaw: unknown): MomorailError {\n const opts = { provider: PROVIDER_ID, providerRaw } as const;\n\n if (AUTH_CODES.has(code)) {\n return new AuthError(`CinetPay rejected the credentials (${code}: ${message})`, opts);\n }\n if (code === '404' || /not[\\s_]*found/i.test(message)) {\n return new TransactionNotFoundError(\n `CinetPay has no such transaction (${code}: ${message})`,\n opts,\n );\n }\n if (code === '429') {\n return new RateLimitError(`CinetPay throttled the request (${code}: ${message})`, opts);\n }\n if (code === '500' || code === '503') {\n return new ProviderUnavailableError(`CinetPay is unavailable (${code}: ${message})`, opts);\n }\n return new MomorailError(`CinetPay error (${code}: ${message})`, 'provider_error', opts);\n}\n","import { AuthError, ProviderUnavailableError, RateLimitError } from '@momorail/core';\nimport type { FetchLike } from './http.js';\nimport { PROVIDER_ID } from './mapping.js';\n\nexport const DEFAULT_TRANSFER_BASE_URL = 'https://client.cinetpay.com/v1';\n\n/** CinetPay Transfer envelope. `code` is `0` (number) on success here, unlike Checkout's `\"00\"`. */\nexport interface TransferEnvelope<T = unknown> {\n code: number | string;\n message?: string;\n description?: string;\n data?: T;\n}\n\nexport interface TransferContact {\n prefix: string;\n phone: string;\n name: string;\n surname: string;\n email: string;\n}\n\nexport interface TransferSendEntry {\n prefix: string;\n phone: string;\n amount: number;\n client_transaction_id: string;\n notify_url?: string;\n payment_method?: string;\n}\n\nexport interface TransferRecord {\n client_transaction_id?: string;\n transaction_id?: string;\n amount?: number | string;\n /** `NEW` | `REC` | `VAL` | `REJECT` | `PENDING` … */\n treatment_status?: string;\n comment?: string;\n}\n\nexport interface TransferClientOptions {\n baseUrl: string;\n timeoutMs: number;\n fetch: FetchLike;\n apikey: string;\n password: string;\n lang: string;\n}\n\nconst okCode = (code: number | string): boolean => Number(code) === 0;\n\n/**\n * Thin client for CinetPay's Transfer API. Owns the short-lived login token\n * (~5 min TTL): it is fetched lazily, reused, and re-fetched once on a\n * token-auth failure. All calls are `application/x-www-form-urlencoded`;\n * responses are JSON. The caller's abort signal is not forwarded — only the\n * per-request timeout applies.\n */\nexport class TransferClient {\n readonly #baseUrl: string;\n readonly #timeoutMs: number;\n readonly #fetch: FetchLike;\n readonly #apikey: string;\n readonly #password: string;\n readonly #lang: string;\n\n #token: string | undefined;\n #tokenExpiresAt = 0;\n\n constructor(options: TransferClientOptions) {\n this.#baseUrl = options.baseUrl.replace(/\\/$/, '');\n this.#timeoutMs = options.timeoutMs;\n this.#fetch = options.fetch;\n this.#apikey = options.apikey;\n this.#password = options.password;\n this.#lang = options.lang;\n }\n\n async addContact(contact: TransferContact): Promise<void> {\n await this.#withToken(\n (token) =>\n this.#form(`/transfer/contact?token=${token}&lang=${this.#lang}`, {\n data: JSON.stringify([contact]),\n }),\n // A contact that already exists is not an error for our purposes.\n (env) => okCode(env.code) || /exist|d[ée]j[àa]|already/i.test(env.message ?? ''),\n );\n }\n\n send(entry: TransferSendEntry): Promise<TransferEnvelope<TransferRecord[]>> {\n return this.#withToken((token) =>\n this.#form(`/transfer/money/send/contact?token=${token}&lang=${this.#lang}`, {\n data: JSON.stringify([pruneUndefined(entry)]),\n }),\n );\n }\n\n check(clientTransactionId: string): Promise<TransferEnvelope<TransferRecord[]>> {\n const q = new URLSearchParams({ lang: this.#lang, client_transaction_id: clientTransactionId });\n return this.#withToken((token) =>\n this.#form(`/transfer/check/money?token=${token}&${q}`, undefined, 'GET'),\n );\n }\n\n async #login(): Promise<string> {\n const env = await this.#form<{ token?: string }>('/auth/login', {\n apikey: this.#apikey,\n password: this.#password,\n });\n const token = env.data?.token;\n if (!okCode(env.code) || !token) {\n throw new AuthError(\n `CinetPay Transfer login failed (${env.code}: ${env.message ?? 'no token'})`,\n { provider: PROVIDER_ID, providerRaw: env },\n );\n }\n this.#token = token;\n this.#tokenExpiresAt = Date.now() + 4 * 60_000; // 5 min TTL, refresh a minute early\n return token;\n }\n\n async #tokenValue(): Promise<string> {\n if (this.#token && Date.now() < this.#tokenExpiresAt) return this.#token;\n return this.#login();\n }\n\n /** Run `call` with a valid token; on a token-auth failure, re-login once and retry. */\n async #withToken<T>(\n call: (token: string) => Promise<TransferEnvelope<T>>,\n accept: (env: TransferEnvelope<T>) => boolean = (env) => okCode(env.code),\n ): Promise<TransferEnvelope<T>> {\n let env = await call(await this.#tokenValue());\n if (!accept(env) && /token/i.test(`${env.code} ${env.message ?? ''}`)) {\n this.#token = undefined;\n env = await call(await this.#tokenValue());\n }\n return env;\n }\n\n async #form<T>(\n path: string,\n fields: Record<string, string> | undefined,\n method: 'GET' | 'POST' = 'POST',\n ): Promise<TransferEnvelope<T>> {\n let response: Response;\n try {\n response = await this.#fetch(`${this.#baseUrl}${path}`, {\n method,\n headers:\n method === 'POST'\n ? { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' }\n : { accept: 'application/json' },\n body: method === 'POST' && fields ? new URLSearchParams(fields).toString() : undefined,\n signal: AbortSignal.timeout(this.#timeoutMs),\n });\n } catch (cause) {\n throw new ProviderUnavailableError('CinetPay Transfer request failed to complete', {\n provider: PROVIDER_ID,\n cause,\n });\n }\n\n if (response.status === 429) {\n throw new RateLimitError('CinetPay Transfer throttled the request', {\n provider: PROVIDER_ID,\n });\n }\n\n const text = await response.text();\n let parsed: TransferEnvelope<T>;\n try {\n parsed = (text ? JSON.parse(text) : {}) as TransferEnvelope<T>;\n } catch (cause) {\n throw new ProviderUnavailableError('CinetPay Transfer returned a non-JSON response', {\n provider: PROVIDER_ID,\n providerRaw: text,\n cause,\n });\n }\n\n if (response.status >= 500) {\n throw new ProviderUnavailableError(`CinetPay Transfer responded ${response.status}`, {\n provider: PROVIDER_ID,\n providerRaw: parsed,\n });\n }\n\n return parsed;\n }\n}\n\nfunction pruneUndefined<T extends object>(obj: T): { [K in keyof T]: T[K] } {\n const out = {} as { [K in keyof T]: T[K] };\n for (const [key, value] of Object.entries(obj)) {\n if (value !== undefined) out[key as keyof T] = value as T[keyof T];\n }\n return out;\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WebhookVerificationError } from '@momorail/core';\nimport { PROVIDER_ID } from './mapping.js';\nimport type { CinetpayNotifyBody } from './types.js';\n\n/**\n * Field order CinetPay concatenates before HMAC-ing the notify body. See\n * https://docs.cinetpay.com/api/1.0-en/checkout/hmac — the string is the raw\n * values joined with no separator, then `hash_hmac('sha256', data, secretKey)`.\n */\nconst HMAC_FIELDS = [\n 'cpm_site_id',\n 'cpm_trans_id',\n 'cpm_trans_date',\n 'cpm_amount',\n 'cpm_currency',\n 'signature',\n 'payment_method',\n 'cel_phone_num',\n 'cpm_phone_prefixe',\n 'cpm_language',\n 'cpm_version',\n 'cpm_payment_config',\n 'cpm_page_action',\n 'cpm_custom',\n 'cpm_designation',\n 'cpm_error_message',\n] as const satisfies readonly (keyof CinetpayNotifyBody)[];\n\n/** Parse an `application/x-www-form-urlencoded` (or already-parsed) notify body. */\nexport function parseNotifyBody(\n body: string | Uint8Array | Record<string, unknown>,\n): CinetpayNotifyBody {\n if (typeof body === 'string' || body instanceof Uint8Array) {\n const raw = typeof body === 'string' ? body : new TextDecoder().decode(body);\n const params = new URLSearchParams(raw);\n return Object.fromEntries(params.entries());\n }\n const out: CinetpayNotifyBody = {};\n for (const [key, value] of Object.entries(body)) {\n if (value != null) out[key] = String(value);\n }\n return out;\n}\n\n/** The exact string CinetPay signs for a given notify body. */\nexport function cinetpaySignaturePayload(fields: CinetpayNotifyBody): string {\n return HMAC_FIELDS.map((name) => fields[name] ?? '').join('');\n}\n\nexport function computeCinetpayToken(fields: CinetpayNotifyBody, secretKey: string): string {\n return createHmac('sha256', secretKey)\n .update(cinetpaySignaturePayload(fields), 'utf8')\n .digest('hex');\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n try {\n return timingSafeEqual(Buffer.from(a, 'hex'), Buffer.from(b, 'hex'));\n } catch {\n return false;\n }\n}\n\n/**\n * Verify the `x-token` header against the body. Returns the parsed fields on\n * success; throws {@link WebhookVerificationError} otherwise.\n */\nexport function verifyCinetpayNotification(\n headers: Record<string, string | string[] | undefined>,\n body: string | Uint8Array | Record<string, unknown>,\n secretKey: string,\n): CinetpayNotifyBody {\n const headerToken = headerValue(headers, 'x-token');\n if (!headerToken) {\n throw new WebhookVerificationError('Missing x-token header on CinetPay notification', {\n provider: PROVIDER_ID,\n });\n }\n\n const fields = parseNotifyBody(body);\n const expected = computeCinetpayToken(fields, secretKey);\n if (!safeEqualHex(headerToken.toLowerCase(), expected)) {\n throw new WebhookVerificationError('CinetPay x-token does not match the request body', {\n provider: PROVIDER_ID,\n });\n }\n return fields;\n}\n\nfunction headerValue(\n headers: Record<string, string | string[] | undefined>,\n name: string,\n): string | undefined {\n const direct = headers[name] ?? headers[name.toLowerCase()];\n const found =\n direct ??\n Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];\n return Array.isArray(found) ? found[0] : found;\n}\n"],"mappings":"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","names":["MomorailError","TransactionNotFoundError","ProviderUnavailableError","ProviderUnavailableError","AuthError","ProviderUnavailableError","RateLimitError","AuthError","ProviderUnavailableError","RateLimitError","pruneUndefined","MomorailError","res","TransactionNotFoundError","status","now","txn"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@momorail/cinetpay",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "CinetPay adapter for Momorail (collection, status check, webhook verification)",
6
+ "keywords": [
7
+ "mobile-money",
8
+ "payments",
9
+ "west-africa",
10
+ "africa",
11
+ "orange-money",
12
+ "mtn-momo",
13
+ "wave",
14
+ "xof",
15
+ "byo-keys",
16
+ "typescript",
17
+ "cinetpay",
18
+ "adapter"
19
+ ],
20
+ "license": "MIT",
21
+ "author": "Boukymen <boukymen@gmail.com>",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/Boukymen/momorail.git",
25
+ "directory": "packages/cinetpay"
26
+ },
27
+ "homepage": "https://github.com/Boukymen/momorail/tree/main/packages/cinetpay#readme",
28
+ "bugs": "https://github.com/Boukymen/momorail/issues",
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@momorail/core": "0.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "msw": "^2.7.0",
48
+ "tsup": "^8.3.5",
49
+ "typescript": "^5.7.2",
50
+ "vitest": "^2.1.8",
51
+ "@momorail/conformance": "0.1.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run"
57
+ }
58
+ }