paykit-bd 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.
@@ -0,0 +1,475 @@
1
+ import { T as TokenStore, L as Logger, R as RawWebhookRequest, W as WebhookEvent, P as PaymentProvider, C as CreatePaymentInput, a as CreatedPayment, b as Payment, c as RefundInput, d as Refund } from './token-store-C8IMMLPJ.js';
2
+
3
+ type BkashEnvironment = "sandbox" | "live";
4
+ /**
5
+ * Hosts are the published tokenized-checkout endpoints, confirmed against the
6
+ * sandbox. If bKash issued you a different host during onboarding, set
7
+ * `baseUrl` and it wins over `environment`.
8
+ */
9
+ declare const BKASH_HOSTS: Record<BkashEnvironment, string>;
10
+ /** The version segment the checkout and agreement endpoints sit under. */
11
+ declare const BKASH_API_VERSION = "v1.2.0-beta";
12
+ /**
13
+ * bKash mode codes. They select what `/tokenized/checkout/create` actually does,
14
+ * and getting one wrong is the most common integration bug.
15
+ */
16
+ declare const BKASH_MODE: {
17
+ /** Create an agreement — no money moves, you get an agreementID back. */
18
+ readonly CREATE_AGREEMENT: "0000";
19
+ /** Charge an existing agreement. Customer confirms with a PIN only. */
20
+ readonly AGREEMENT_PAYMENT: "0001";
21
+ /** One-off payment, no agreement. Customer is redirected to bKash. */
22
+ readonly ONE_OFF_PAYMENT: "0011";
23
+ };
24
+ type BkashMode = (typeof BKASH_MODE)[keyof typeof BKASH_MODE];
25
+ interface BkashConfig {
26
+ /** Picks the base URL. Ignored when `baseUrl` is set. */
27
+ environment?: BkashEnvironment;
28
+ /** Host origin override, e.g. `https://tokenized.sandbox.bka.sh`. No trailing path. */
29
+ baseUrl?: string;
30
+ username: string;
31
+ password: string;
32
+ appKey: string;
33
+ appSecret: string;
34
+ /** Default redirect target after the customer approves, fails or cancels. */
35
+ callbackUrl?: string;
36
+ /**
37
+ * Where tokens live between calls. Defaults to an in-process store, which is
38
+ * only safe for a single instance — see {@link TokenStore}.
39
+ */
40
+ tokenStore?: TokenStore;
41
+ /**
42
+ * Namespaces the stored token. Change it if one process serves more than one
43
+ * bKash merchant account. Defaults to a hash of the app key.
44
+ */
45
+ tokenKey?: string;
46
+ /**
47
+ * Refresh the token this long before it actually expires. bKash suggests the
48
+ * 50th–55th minute of a 60-minute token; default is 10 minutes of headroom.
49
+ */
50
+ refreshSkewMs?: number;
51
+ /**
52
+ * Hard ceiling on token acquisitions per rolling hour. bKash blocks the
53
+ * merchant for an hour past two refreshes, so the default leaves no slack.
54
+ * Raise it only if bKash has told you your limit differs.
55
+ */
56
+ maxRefreshesPerHour?: number;
57
+ /** Per-request timeout in ms. Default 30000. */
58
+ timeoutMs?: number;
59
+ /** Pin the SNS topic your IPN messages arrive on. Strongly recommended. */
60
+ webhookTopicArn?: string;
61
+ logger?: Logger;
62
+ }
63
+ interface ResolvedBkashConfig extends Required<Omit<BkashConfig, "logger" | "tokenStore" | "callbackUrl" | "webhookTopicArn" | "baseUrl">> {
64
+ origin: string;
65
+ callbackUrl?: string;
66
+ webhookTopicArn?: string;
67
+ }
68
+ /** Full URLs for every endpoint, derived from one origin. */
69
+ declare function endpoints(origin: string): {
70
+ grantToken: string;
71
+ refreshToken: string;
72
+ /** Serves agreement creation and both payment modes; `mode` decides which. */
73
+ create: string;
74
+ /** Finalises whatever `create` started, agreement or payment. */
75
+ execute: string;
76
+ queryPayment: string;
77
+ queryAgreement: string;
78
+ cancelAgreement: string;
79
+ refund: string;
80
+ refundStatus: string;
81
+ /** Pre-v2 refund, still provisioned for some merchants. */
82
+ legacyRefund: string;
83
+ };
84
+ declare function resolveConfig(config: BkashConfig): ResolvedBkashConfig;
85
+ /**
86
+ * Build a config from process.env. Reads BKASH_ENV, BKASH_USERNAME,
87
+ * BKASH_PASSWORD, BKASH_APP_KEY, BKASH_APP_SECRET, BKASH_BASE_URL,
88
+ * BKASH_CALLBACK_URL and BKASH_WEBHOOK_TOPIC_ARN.
89
+ */
90
+ declare function configFromEnv(env?: Record<string, string | undefined>): BkashConfig;
91
+
92
+ /** bKash spells these lower-case on the wire. */
93
+ type BkashIntent = "sale" | "authorization";
94
+ /** bKash's own wire shapes. Field names are theirs, including the inconsistent ones. */
95
+ interface GrantTokenResponse {
96
+ statusCode?: string;
97
+ statusMessage?: string;
98
+ token_type?: string;
99
+ id_token?: string;
100
+ refresh_token?: string;
101
+ expires_in?: number;
102
+ }
103
+ interface AgreementResponse {
104
+ statusCode?: string;
105
+ statusMessage?: string;
106
+ errorCode?: string;
107
+ errorMessage?: string;
108
+ paymentID?: string;
109
+ bkashURL?: string;
110
+ callbackURL?: string;
111
+ successCallbackURL?: string;
112
+ failureCallbackURL?: string;
113
+ cancelledCallbackURL?: string;
114
+ agreementID?: string;
115
+ agreementStatus?: string;
116
+ agreementCreateTime?: string;
117
+ agreementExecuteTime?: string;
118
+ payerReference?: string;
119
+ customerMsisdn?: string;
120
+ }
121
+ interface CreatePaymentRequest {
122
+ mode: BkashMode;
123
+ payerReference: string;
124
+ callbackURL: string;
125
+ amount: string;
126
+ currency: "BDT";
127
+ intent: BkashIntent;
128
+ merchantInvoiceNumber?: string;
129
+ /** Required for mode 0001 — the agreement being charged. */
130
+ agreementID?: string;
131
+ /** Aggregator / sub-merchant identifier, where bKash has issued one. */
132
+ merchantAssociationInfo?: string;
133
+ }
134
+ interface CreatePaymentResponse {
135
+ statusCode?: string;
136
+ statusMessage?: string;
137
+ errorCode?: string;
138
+ errorMessage?: string;
139
+ paymentID?: string;
140
+ /** Absent for mode 0001: an agreement payment needs no redirect. */
141
+ bkashURL?: string;
142
+ callbackURL?: string;
143
+ successCallbackURL?: string;
144
+ failureCallbackURL?: string;
145
+ cancelledCallbackURL?: string;
146
+ amount?: string;
147
+ intent?: string;
148
+ currency?: string;
149
+ agreementID?: string;
150
+ paymentCreateTime?: string;
151
+ transactionStatus?: string;
152
+ merchantInvoiceNumber?: string;
153
+ }
154
+ interface ExecutePaymentResponse {
155
+ statusCode?: string;
156
+ statusMessage?: string;
157
+ errorCode?: string;
158
+ errorMessage?: string;
159
+ paymentID?: string;
160
+ agreementID?: string;
161
+ payerReference?: string;
162
+ customerMsisdn?: string;
163
+ /** The financial transaction id. Only present once the payment completed. */
164
+ trxID?: string;
165
+ amount?: string;
166
+ transactionStatus?: string;
167
+ paymentExecuteTime?: string;
168
+ currency?: string;
169
+ intent?: string;
170
+ merchantInvoiceNumber?: string;
171
+ }
172
+ /**
173
+ * Query Payment. Note `merchantInvoice` — the query endpoint drops the `Number`
174
+ * suffix that create and execute both use. Confirmed against sandbox.
175
+ */
176
+ interface QueryPaymentResponse {
177
+ statusCode?: string;
178
+ statusMessage?: string;
179
+ errorCode?: string;
180
+ errorMessage?: string;
181
+ paymentID?: string;
182
+ mode?: string;
183
+ paymentCreateTime?: string;
184
+ paymentExecuteTime?: string;
185
+ amount?: string;
186
+ currency?: string;
187
+ intent?: string;
188
+ merchantInvoice?: string;
189
+ merchantInvoiceNumber?: string;
190
+ trxID?: string;
191
+ transactionStatus?: string;
192
+ verificationStatus?: string;
193
+ /** Undocumented, but returned: what is still refundable on this payment. */
194
+ maxRefundableAmount?: string;
195
+ payerReference?: string;
196
+ customerMsisdn?: string;
197
+ agreementID?: string;
198
+ agreementStatus?: string;
199
+ agreementCreateTime?: string;
200
+ agreementExecuteTime?: string;
201
+ }
202
+ interface RefundRequest {
203
+ /** Lower-case `d` on the v2 API, unlike every other endpoint's `paymentID`. */
204
+ paymentId: string;
205
+ trxId: string;
206
+ refundAmount: string;
207
+ /**
208
+ * Mandatory in practice, though the docs read as though it were optional.
209
+ * Omit `sku` or `reason` and the v2 refund API rejects the call at its schema
210
+ * layer with `{"message": "Invalid request body"}` — no code, no field name,
211
+ * nothing to debug from. Verified against the sandbox: the identical request
212
+ * succeeds once both are present.
213
+ */
214
+ sku: string;
215
+ reason: string;
216
+ }
217
+ interface RefundResponse {
218
+ originalTrxId?: string;
219
+ refundTrxId?: string;
220
+ refundTransactionStatus?: string;
221
+ originalTrxAmount?: string;
222
+ refundAmount?: string;
223
+ currency?: string;
224
+ completedTime?: string;
225
+ sku?: string;
226
+ reason?: string;
227
+ /** v2 error envelope. */
228
+ internalCode?: string;
229
+ externalCode?: string;
230
+ errorMessageEn?: string;
231
+ errorMessageBn?: string;
232
+ }
233
+ interface RefundStatusResponse {
234
+ originalTrxId?: string;
235
+ originalTrxAmount?: string;
236
+ originalTrxCompletedTime?: string;
237
+ refundTransactions?: Array<{
238
+ refundTrxId?: string;
239
+ refundTransactionStatus?: string;
240
+ refundAmount?: string;
241
+ completedTime?: string;
242
+ }>;
243
+ internalCode?: string;
244
+ externalCode?: string;
245
+ errorMessageEn?: string;
246
+ errorMessageBn?: string;
247
+ }
248
+ /**
249
+ * The query string bKash appends when it redirects the customer back to your
250
+ * callback URL. `status` is the only field worth branching on, and none of it
251
+ * is trustworthy on its own — always confirm with executePayment or getPayment.
252
+ */
253
+ interface BkashCallbackQuery {
254
+ paymentID?: string;
255
+ status?: "success" | "failure" | "cancel" | string;
256
+ /** Present from v1.2.0-beta. Opaque; bKash publishes no verification scheme. */
257
+ signature?: string;
258
+ apiVersion?: string;
259
+ product?: string;
260
+ }
261
+
262
+ interface SnsEnvelope {
263
+ Type?: string;
264
+ MessageId?: string;
265
+ TopicArn?: string;
266
+ Subject?: string;
267
+ Message?: string;
268
+ Timestamp?: string;
269
+ SignatureVersion?: string;
270
+ Signature?: string;
271
+ SigningCertURL?: string;
272
+ SubscribeURL?: string;
273
+ Token?: string;
274
+ UnsubscribeURL?: string;
275
+ }
276
+ /** The JSON string inside `Message` for a payment notification. */
277
+ interface BkashIpnMessage {
278
+ dateTime?: string;
279
+ debitMSISDN?: string;
280
+ creditOrganizationName?: string;
281
+ creditShortCode?: string;
282
+ trxID?: string;
283
+ transactionStatus?: string;
284
+ transactionType?: string;
285
+ amount?: string;
286
+ currency?: string;
287
+ transactionReference?: string;
288
+ merchantInvoiceNumber?: string;
289
+ /** Coupon-funded payments carry three extra fields. */
290
+ couponAmount?: string;
291
+ merchantShareAmount?: string;
292
+ saleAmount?: string;
293
+ }
294
+ /** bKash's numeric transactionType codes, as published with the IPN docs. */
295
+ declare const BKASH_TRANSACTION_TYPES: Record<string, string>;
296
+ interface WebhookVerifierOptions {
297
+ /**
298
+ * Accept messages only from these SNS topic ARNs. Leave unset and any topic
299
+ * with a valid Amazon signature is accepted — which includes topics belonging
300
+ * to someone else's merchant account. Pin it: the ARN is in the TopicArn field
301
+ * of the first message you receive.
302
+ */
303
+ topicArn?: string | string[];
304
+ /**
305
+ * Reject messages older than this. Unset by default, because SNS legitimately
306
+ * retries for days and a tight window silently drops real payments. Make the
307
+ * handler idempotent on `eventId` instead.
308
+ */
309
+ maxAgeMs?: number;
310
+ /** How long a fetched signing certificate is reused. Default 24h. */
311
+ certCacheMs?: number;
312
+ logger?: Logger;
313
+ /** Injected in tests. Defaults to `fetch`. */
314
+ fetchImpl?: typeof fetch;
315
+ }
316
+ declare class BkashWebhookVerifier {
317
+ #private;
318
+ constructor(options?: WebhookVerifierOptions);
319
+ /**
320
+ * Verify an inbound request and normalise it.
321
+ *
322
+ * `request.body` must be the raw bytes as received. A body that has been
323
+ * parsed and re-serialised will not match the signature.
324
+ */
325
+ verify(request: RawWebhookRequest): Promise<WebhookEvent>;
326
+ /**
327
+ * Confirm an SNS subscription by visiting its SubscribeURL.
328
+ *
329
+ * Deliberately not automatic: it is an outbound call that switches on real
330
+ * payment traffic, so your handler decides when to make it. Verify the
331
+ * message first — this method re-checks the URL host but assumes the
332
+ * signature was already proven.
333
+ */
334
+ confirmSubscription(envelope: SnsEnvelope): Promise<void>;
335
+ /** Parse an already-verified envelope's inner payment message. */
336
+ static parseMessage(envelope: SnsEnvelope): BkashIpnMessage | null;
337
+ }
338
+ /**
339
+ * The exact byte sequence SNS signed: each present field as `name\nvalue\n`, in
340
+ * this order. Field order and the choice of fields are part of the signature —
341
+ * changing either makes every message fail to verify.
342
+ */
343
+ declare function canonicalString(envelope: SnsEnvelope): string;
344
+ /** Reject any URL that is not an Amazon SNS certificate or confirmation endpoint. */
345
+ declare function assertSnsUrl(rawUrl: string, field: string): URL;
346
+
347
+ interface BkashClientOptions {
348
+ tokenStore?: TokenStore;
349
+ logger?: Logger;
350
+ webhook?: Omit<WebhookVerifierOptions, "logger">;
351
+ }
352
+ interface CreateAgreementInput {
353
+ /** The customer's wallet number. Pre-fills the bKash entry screen. */
354
+ payerReference: string;
355
+ callbackUrl?: string;
356
+ }
357
+ interface AgreementHandle {
358
+ paymentId: string;
359
+ /** Send the customer here to enter their wallet number and OTP. */
360
+ redirectUrl: string | null;
361
+ status: string;
362
+ raw: AgreementResponse;
363
+ }
364
+ interface Agreement {
365
+ agreementId: string;
366
+ paymentId?: string;
367
+ customerMsisdn?: string;
368
+ payerReference?: string;
369
+ status: string;
370
+ createdAt?: Date;
371
+ executedAt?: Date;
372
+ raw: AgreementResponse;
373
+ }
374
+ /**
375
+ * bKash tokenized checkout.
376
+ *
377
+ * Implements {@link PaymentProvider}, plus the agreement operations that have no
378
+ * equivalent at other gateways.
379
+ *
380
+ * ```ts
381
+ * const bkash = new BkashClient(configFromEnv());
382
+ * const payment = await bkash.createPayment({ amount: "500", reference: "ORD-1" });
383
+ * // send the customer to payment.redirectUrl, then when they come back:
384
+ * const settled = await bkash.executePayment(payment.paymentId);
385
+ * ```
386
+ */
387
+ declare class BkashClient implements PaymentProvider {
388
+ #private;
389
+ readonly id = "bkash";
390
+ constructor(config: BkashConfig, options?: BkashClientOptions);
391
+ /** Which environment and host this client is pointed at. */
392
+ get environment(): {
393
+ environment: string;
394
+ origin: string;
395
+ };
396
+ /** Token budget for the current rolling hour. */
397
+ tokenBudget(): Promise<{
398
+ refreshesUsed: number;
399
+ refreshesAllowed: number;
400
+ acquisitionsUsed: number;
401
+ }>;
402
+ /**
403
+ * Force a token refresh, spending one unit of the hourly budget. Normal use
404
+ * should leave this alone and let the client renew when it needs to; it is
405
+ * here so the refresh path can be exercised deliberately.
406
+ */
407
+ refreshToken(): Promise<string>;
408
+ /**
409
+ * Step 1 of 2. Start an agreement so this customer can later pay with a PIN
410
+ * alone. Send them to `redirectUrl`, then call {@link executeAgreement}.
411
+ */
412
+ createAgreement(input: CreateAgreementInput): Promise<AgreementHandle>;
413
+ /**
414
+ * Step 2 of 2. Call once the customer returns to your callback URL. The
415
+ * `agreementID` it returns is what you store against the customer — it is the
416
+ * whole point of the flow and bKash will not hand it to you again.
417
+ */
418
+ executeAgreement(paymentId: string): Promise<Agreement>;
419
+ getAgreement(agreementId: string): Promise<Agreement>;
420
+ /** Ends the agreement. The customer must go through the OTP flow again after this. */
421
+ cancelAgreement(agreementId: string): Promise<Agreement>;
422
+ /**
423
+ * Start a payment.
424
+ *
425
+ * Passing `extra.agreementID` charges an existing agreement (mode 0001) and
426
+ * returns no `redirectUrl` — the customer confirms with a PIN in the bKash
427
+ * app. Without it this is a one-off payment (mode 0011) and the customer must
428
+ * be sent to `redirectUrl`.
429
+ */
430
+ createPayment(input: CreatePaymentInput): Promise<CreatedPayment>;
431
+ /**
432
+ * Finalise a payment after the customer approves it. Valid exactly once per
433
+ * payment id.
434
+ *
435
+ * If bKash answers that the payment was already executed, this reads the real
436
+ * outcome with {@link getPayment} instead of throwing — that response means
437
+ * the money moved, and the caller wants the result, not an error.
438
+ */
439
+ executePayment(paymentId: string): Promise<Payment>;
440
+ /** Read current state. Safe to call as often as you like — this is the recovery path. */
441
+ getPayment(paymentId: string): Promise<Payment>;
442
+ /** How much of this payment can still be refunded, per bKash. */
443
+ getRefundableAmount(paymentId: string): Promise<string | null>;
444
+ /**
445
+ * Refund all or part of a completed payment.
446
+ *
447
+ * The v2 API allows up to ten partial refunds per transaction, within 60 days.
448
+ * Omit `amount` for a full refund, which is read from bKash's own
449
+ * `maxRefundableAmount` rather than assumed.
450
+ */
451
+ refund(input: RefundInput): Promise<Refund>;
452
+ /** Every refund recorded against one transaction. */
453
+ getRefunds(input: {
454
+ paymentId: string;
455
+ transactionId: string;
456
+ }): Promise<{
457
+ originalTransactionId: string;
458
+ originalAmount: string;
459
+ refunds: Refund[];
460
+ raw: RefundStatusResponse;
461
+ }>;
462
+ /** Verify an inbound IPN message and normalise it. Throws if it is not genuine. */
463
+ verifyWebhook(request: RawWebhookRequest): Promise<WebhookEvent>;
464
+ get webhooks(): BkashWebhookVerifier;
465
+ /**
466
+ * Read the query string bKash appends when it redirects a customer back.
467
+ *
468
+ * Nothing here is proof of payment — it is a URL the customer's own browser
469
+ * followed and could have edited. Always confirm with {@link executePayment}
470
+ * or {@link getPayment} before releasing an order.
471
+ */
472
+ static parseCallback(input: string | URL | URLSearchParams | Record<string, string>): BkashCallbackQuery;
473
+ }
474
+
475
+ export { type Agreement as A, BkashClient as B, type CreateAgreementInput as C, type ExecutePaymentResponse as E, type GrantTokenResponse as G, type QueryPaymentResponse as Q, type ResolvedBkashConfig as R, type SnsEnvelope as S, type WebhookVerifierOptions as W, BkashWebhookVerifier as a, type AgreementHandle as b, type AgreementResponse as c, BKASH_API_VERSION as d, BKASH_HOSTS as e, BKASH_MODE as f, BKASH_TRANSACTION_TYPES as g, type BkashCallbackQuery as h, type BkashClientOptions as i, type BkashConfig as j, type BkashEnvironment as k, type BkashIntent as l, type BkashIpnMessage as m, type BkashMode as n, type CreatePaymentRequest as o, type CreatePaymentResponse as p, type RefundRequest as q, type RefundResponse as r, type RefundStatusResponse as s, assertSnsUrl as t, canonicalString as u, configFromEnv as v, endpoints as w, resolveConfig as x };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Every error this library throws is a PaykitError, so a caller can write one
3
+ * catch block and still tell a declined payment apart from a dead socket.
4
+ *
5
+ * `instanceof` is answered from a brand rather than from the prototype chain,
6
+ * and that is deliberate. This package ships several entry points — `paykit-bd`,
7
+ * `paykit-bd/bkash`, `paykit-bd/bkash/next` — and each is a separate bundle
8
+ * carrying its own copy of these classes. A plain `instanceof` compares class
9
+ * identity, so an error thrown by the bkash client would *not* match the class
10
+ * imported from the root entry. It would fail silently, which in a payment
11
+ * library means a customer's declined PIN handled as an unknown error. The
12
+ * brand also holds when two versions of this package end up installed at once,
13
+ * or across a worker boundary, where plain `instanceof` fails for the same
14
+ * reason.
15
+ */
16
+ declare const BRANDS: unique symbol;
17
+ declare abstract class PaykitError extends Error {
18
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
19
+ /** Class lineage, innermost last. Read `instanceof` instead of this. */
20
+ readonly [BRANDS]: readonly string[];
21
+ /** Gateway this came from — `"bkash"`, or `"paykit"` for local failures. */
22
+ readonly provider: string;
23
+ /** Stable machine-readable code. Gateway codes are passed through verbatim. */
24
+ readonly code: string;
25
+ /** True when retrying the identical request could plausibly succeed. */
26
+ readonly retryable: boolean;
27
+ /** Untouched gateway response body, for logging. */
28
+ readonly raw: unknown;
29
+ protected constructor(message: string, opts: {
30
+ provider: string;
31
+ code: string;
32
+ retryable?: boolean;
33
+ raw?: unknown;
34
+ cause?: unknown;
35
+ }, brands?: readonly string[]);
36
+ toJSON(): Record<string, unknown>;
37
+ }
38
+ /** The gateway answered, and the answer was an error. `code` is its own code. */
39
+ declare class ProviderError extends PaykitError {
40
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
41
+ constructor(message: string, opts: {
42
+ provider: string;
43
+ code: string;
44
+ retryable?: boolean;
45
+ raw?: unknown;
46
+ cause?: unknown;
47
+ }, brands?: readonly string[]);
48
+ }
49
+ /** The request never produced a usable answer: DNS, TLS, timeout, 5xx, bad JSON. */
50
+ declare class NetworkError extends PaykitError {
51
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
52
+ /** HTTP status, when there was one. */
53
+ readonly status?: number;
54
+ constructor(message: string, opts: {
55
+ provider: string;
56
+ code?: string;
57
+ status?: number;
58
+ raw?: unknown;
59
+ cause?: unknown;
60
+ });
61
+ }
62
+ /** Something is wrong with how the client was constructed or configured. */
63
+ declare class ConfigError extends PaykitError {
64
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
65
+ constructor(message: string, opts?: {
66
+ provider?: string;
67
+ code?: string;
68
+ });
69
+ }
70
+ /**
71
+ * An inbound webhook did not prove it came from the gateway. Never treat the
72
+ * payload as real after this — it is an unauthenticated stranger's JSON.
73
+ */
74
+ declare class WebhookVerificationError extends PaykitError {
75
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
76
+ constructor(message: string, opts: {
77
+ provider: string;
78
+ code?: string;
79
+ raw?: unknown;
80
+ cause?: unknown;
81
+ });
82
+ }
83
+ /**
84
+ * A local guard stopped a call before it left the process — the refresh-token
85
+ * budget being the one that matters in practice.
86
+ */
87
+ declare class RateLimitError extends PaykitError {
88
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
89
+ /** Epoch ms when the guard will let the call through. */
90
+ readonly retryAt?: number;
91
+ constructor(message: string, opts: {
92
+ provider: string;
93
+ code?: string;
94
+ retryAt?: number;
95
+ });
96
+ }
97
+ /**
98
+ * Brand an error class defined outside this module, so a gateway's own error
99
+ * type takes part in the same `instanceof` scheme.
100
+ */
101
+ declare function brandCheckFor(tag: string): (value: unknown) => boolean;
102
+
103
+ export { ConfigError as C, NetworkError as N, ProviderError as P, RateLimitError as R, WebhookVerificationError as W, PaykitError as a, brandCheckFor as b };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Every error this library throws is a PaykitError, so a caller can write one
3
+ * catch block and still tell a declined payment apart from a dead socket.
4
+ *
5
+ * `instanceof` is answered from a brand rather than from the prototype chain,
6
+ * and that is deliberate. This package ships several entry points — `paykit-bd`,
7
+ * `paykit-bd/bkash`, `paykit-bd/bkash/next` — and each is a separate bundle
8
+ * carrying its own copy of these classes. A plain `instanceof` compares class
9
+ * identity, so an error thrown by the bkash client would *not* match the class
10
+ * imported from the root entry. It would fail silently, which in a payment
11
+ * library means a customer's declined PIN handled as an unknown error. The
12
+ * brand also holds when two versions of this package end up installed at once,
13
+ * or across a worker boundary, where plain `instanceof` fails for the same
14
+ * reason.
15
+ */
16
+ declare const BRANDS: unique symbol;
17
+ declare abstract class PaykitError extends Error {
18
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
19
+ /** Class lineage, innermost last. Read `instanceof` instead of this. */
20
+ readonly [BRANDS]: readonly string[];
21
+ /** Gateway this came from — `"bkash"`, or `"paykit"` for local failures. */
22
+ readonly provider: string;
23
+ /** Stable machine-readable code. Gateway codes are passed through verbatim. */
24
+ readonly code: string;
25
+ /** True when retrying the identical request could plausibly succeed. */
26
+ readonly retryable: boolean;
27
+ /** Untouched gateway response body, for logging. */
28
+ readonly raw: unknown;
29
+ protected constructor(message: string, opts: {
30
+ provider: string;
31
+ code: string;
32
+ retryable?: boolean;
33
+ raw?: unknown;
34
+ cause?: unknown;
35
+ }, brands?: readonly string[]);
36
+ toJSON(): Record<string, unknown>;
37
+ }
38
+ /** The gateway answered, and the answer was an error. `code` is its own code. */
39
+ declare class ProviderError extends PaykitError {
40
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
41
+ constructor(message: string, opts: {
42
+ provider: string;
43
+ code: string;
44
+ retryable?: boolean;
45
+ raw?: unknown;
46
+ cause?: unknown;
47
+ }, brands?: readonly string[]);
48
+ }
49
+ /** The request never produced a usable answer: DNS, TLS, timeout, 5xx, bad JSON. */
50
+ declare class NetworkError extends PaykitError {
51
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
52
+ /** HTTP status, when there was one. */
53
+ readonly status?: number;
54
+ constructor(message: string, opts: {
55
+ provider: string;
56
+ code?: string;
57
+ status?: number;
58
+ raw?: unknown;
59
+ cause?: unknown;
60
+ });
61
+ }
62
+ /** Something is wrong with how the client was constructed or configured. */
63
+ declare class ConfigError extends PaykitError {
64
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
65
+ constructor(message: string, opts?: {
66
+ provider?: string;
67
+ code?: string;
68
+ });
69
+ }
70
+ /**
71
+ * An inbound webhook did not prove it came from the gateway. Never treat the
72
+ * payload as real after this — it is an unauthenticated stranger's JSON.
73
+ */
74
+ declare class WebhookVerificationError extends PaykitError {
75
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
76
+ constructor(message: string, opts: {
77
+ provider: string;
78
+ code?: string;
79
+ raw?: unknown;
80
+ cause?: unknown;
81
+ });
82
+ }
83
+ /**
84
+ * A local guard stopped a call before it left the process — the refresh-token
85
+ * budget being the one that matters in practice.
86
+ */
87
+ declare class RateLimitError extends PaykitError {
88
+ static [Symbol.hasInstance]: (value: unknown) => boolean;
89
+ /** Epoch ms when the guard will let the call through. */
90
+ readonly retryAt?: number;
91
+ constructor(message: string, opts: {
92
+ provider: string;
93
+ code?: string;
94
+ retryAt?: number;
95
+ });
96
+ }
97
+ /**
98
+ * Brand an error class defined outside this module, so a gateway's own error
99
+ * type takes part in the same `instanceof` scheme.
100
+ */
101
+ declare function brandCheckFor(tag: string): (value: unknown) => boolean;
102
+
103
+ export { ConfigError as C, NetworkError as N, ProviderError as P, RateLimitError as R, WebhookVerificationError as W, PaykitError as a, brandCheckFor as b };