@laranex/myanmar-payments 0.0.0-stage → 4.0.0-dev.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +10 -2
  4. package/dist/cjs/aya-pay/aya-pay.d.ts +122 -0
  5. package/dist/cjs/aya-pay/aya-pay.js +301 -0
  6. package/dist/cjs/aya-pay/index.d.ts +1 -0
  7. package/dist/cjs/aya-pay/index.js +8 -0
  8. package/dist/cjs/core/amount.d.ts +57 -0
  9. package/dist/cjs/core/amount.js +107 -0
  10. package/dist/cjs/core/cache.d.ts +26 -0
  11. package/dist/cjs/core/cache.js +35 -0
  12. package/dist/cjs/core/callback.d.ts +180 -0
  13. package/dist/cjs/core/callback.js +304 -0
  14. package/dist/cjs/core/crypto.d.ts +19 -0
  15. package/dist/cjs/core/crypto.js +50 -0
  16. package/dist/cjs/core/env.d.ts +21 -0
  17. package/dist/cjs/core/env.js +54 -0
  18. package/dist/cjs/core/errors.d.ts +56 -0
  19. package/dist/cjs/core/errors.js +78 -0
  20. package/dist/cjs/core/http.d.ts +81 -0
  21. package/dist/cjs/core/http.js +128 -0
  22. package/dist/cjs/core/index.d.ts +8 -0
  23. package/dist/cjs/core/index.js +30 -0
  24. package/dist/cjs/core/json.d.ts +44 -0
  25. package/dist/cjs/core/json.js +254 -0
  26. package/dist/cjs/core/results.d.ts +122 -0
  27. package/dist/cjs/core/results.js +147 -0
  28. package/dist/cjs/core/status.d.ts +50 -0
  29. package/dist/cjs/core/status.js +60 -0
  30. package/dist/cjs/core/validate.d.ts +46 -0
  31. package/dist/cjs/core/validate.js +148 -0
  32. package/dist/cjs/core/values.d.ts +16 -0
  33. package/dist/cjs/core/values.js +45 -0
  34. package/dist/cjs/cyber-source/cyber-source.d.ts +95 -0
  35. package/dist/cjs/cyber-source/cyber-source.js +188 -0
  36. package/dist/cjs/cyber-source/index.d.ts +1 -0
  37. package/dist/cjs/cyber-source/index.js +7 -0
  38. package/dist/cjs/index.d.ts +7 -0
  39. package/dist/cjs/index.js +25 -0
  40. package/dist/cjs/kbz-pay/index.d.ts +2 -0
  41. package/dist/cjs/kbz-pay/index.js +8 -0
  42. package/dist/cjs/kbz-pay/kbz-pay.d.ts +102 -0
  43. package/dist/cjs/kbz-pay/kbz-pay.js +266 -0
  44. package/dist/cjs/kbz-pay/signer.d.ts +15 -0
  45. package/dist/cjs/kbz-pay/signer.js +36 -0
  46. package/dist/cjs/myanmar-payments.d.ts +50 -0
  47. package/dist/cjs/myanmar-payments.js +73 -0
  48. package/dist/cjs/package.json +3 -0
  49. package/dist/cjs/wave-money/index.d.ts +1 -0
  50. package/dist/cjs/wave-money/index.js +6 -0
  51. package/dist/cjs/wave-money/wave-money.d.ts +111 -0
  52. package/dist/cjs/wave-money/wave-money.js +251 -0
  53. package/dist/cjs/yoma-mmqr/index.d.ts +1 -0
  54. package/dist/cjs/yoma-mmqr/index.js +6 -0
  55. package/dist/cjs/yoma-mmqr/yoma-mmqr.d.ts +105 -0
  56. package/dist/cjs/yoma-mmqr/yoma-mmqr.js +233 -0
  57. package/dist/esm/aya-pay/aya-pay.d.ts +122 -0
  58. package/dist/esm/aya-pay/aya-pay.js +296 -0
  59. package/dist/esm/aya-pay/index.d.ts +1 -0
  60. package/dist/esm/aya-pay/index.js +1 -0
  61. package/dist/esm/core/amount.d.ts +57 -0
  62. package/dist/esm/core/amount.js +103 -0
  63. package/dist/esm/core/cache.d.ts +26 -0
  64. package/dist/esm/core/cache.js +31 -0
  65. package/dist/esm/core/callback.d.ts +180 -0
  66. package/dist/esm/core/callback.js +294 -0
  67. package/dist/esm/core/crypto.d.ts +19 -0
  68. package/dist/esm/core/crypto.js +41 -0
  69. package/dist/esm/core/env.d.ts +21 -0
  70. package/dist/esm/core/env.js +45 -0
  71. package/dist/esm/core/errors.d.ts +56 -0
  72. package/dist/esm/core/errors.js +70 -0
  73. package/dist/esm/core/http.d.ts +81 -0
  74. package/dist/esm/core/http.js +121 -0
  75. package/dist/esm/core/index.d.ts +8 -0
  76. package/dist/esm/core/index.js +7 -0
  77. package/dist/esm/core/json.d.ts +44 -0
  78. package/dist/esm/core/json.js +244 -0
  79. package/dist/esm/core/results.d.ts +122 -0
  80. package/dist/esm/core/results.js +139 -0
  81. package/dist/esm/core/status.d.ts +50 -0
  82. package/dist/esm/core/status.js +56 -0
  83. package/dist/esm/core/validate.d.ts +46 -0
  84. package/dist/esm/core/validate.js +142 -0
  85. package/dist/esm/core/values.d.ts +16 -0
  86. package/dist/esm/core/values.js +38 -0
  87. package/dist/esm/cyber-source/cyber-source.d.ts +95 -0
  88. package/dist/esm/cyber-source/cyber-source.js +184 -0
  89. package/dist/esm/cyber-source/index.d.ts +1 -0
  90. package/dist/esm/cyber-source/index.js +1 -0
  91. package/dist/esm/index.d.ts +7 -0
  92. package/dist/esm/index.js +7 -0
  93. package/dist/esm/kbz-pay/index.d.ts +2 -0
  94. package/dist/esm/kbz-pay/index.js +2 -0
  95. package/dist/esm/kbz-pay/kbz-pay.d.ts +102 -0
  96. package/dist/esm/kbz-pay/kbz-pay.js +262 -0
  97. package/dist/esm/kbz-pay/signer.d.ts +15 -0
  98. package/dist/esm/kbz-pay/signer.js +32 -0
  99. package/dist/esm/myanmar-payments.d.ts +50 -0
  100. package/dist/esm/myanmar-payments.js +69 -0
  101. package/dist/esm/wave-money/index.d.ts +1 -0
  102. package/dist/esm/wave-money/index.js +1 -0
  103. package/dist/esm/wave-money/wave-money.d.ts +111 -0
  104. package/dist/esm/wave-money/wave-money.js +246 -0
  105. package/dist/esm/yoma-mmqr/index.d.ts +1 -0
  106. package/dist/esm/yoma-mmqr/index.js +1 -0
  107. package/dist/esm/yoma-mmqr/yoma-mmqr.d.ts +105 -0
  108. package/dist/esm/yoma-mmqr/yoma-mmqr.js +229 -0
  109. package/package.json +151 -4
  110. package/skills/node-myanmar-payments/SKILL.md +109 -0
@@ -0,0 +1,122 @@
1
+ type Raw = Readonly<Record<string, unknown>>;
2
+ /** Returned when a payment is initiated. Each flow has its own class; switch on `flow`. */
3
+ export type PaymentResult = RedirectPayment | FormPayment | QrPayment | AppPayment;
4
+ /**
5
+ * Send the customer's browser to `url` to complete the payment.
6
+ */
7
+ export declare class RedirectPayment {
8
+ /** Always `redirect`. */
9
+ readonly flow: 'redirect';
10
+ /** Your order id, as sent to the gateway. */
11
+ readonly orderId: string;
12
+ /** The gateway page to redirect the customer to. */
13
+ readonly url: string;
14
+ /** The gateway's id for this attempt (KBZ `prepay_id`, Wave `transaction_id`). */
15
+ readonly gatewayReference: string | undefined;
16
+ /** The gateway's response, for logging. */
17
+ readonly raw: Raw;
18
+ constructor(init: {
19
+ orderId: string;
20
+ url: string;
21
+ gatewayReference?: string | undefined;
22
+ raw?: Raw;
23
+ });
24
+ }
25
+ /** One signed hidden field of a {@link FormPayment}. */
26
+ export interface FormField {
27
+ readonly name: string;
28
+ readonly value: string;
29
+ }
30
+ /**
31
+ * POST `fields` to `action` from the customer's browser. {@link FormPayment.toHtml} renders a page
32
+ * that does this automatically.
33
+ */
34
+ export declare class FormPayment {
35
+ /** Always `form`. */
36
+ readonly flow: 'form';
37
+ /** Your order id, as sent to the gateway. */
38
+ readonly orderId: string;
39
+ /** The gateway URL the form posts to. */
40
+ readonly action: string;
41
+ /** The signed hidden fields, in signing order. Post them unchanged. */
42
+ readonly fields: readonly FormField[];
43
+ /** The encoding the gateway expects for the form. */
44
+ readonly enctype: string;
45
+ constructor(init: {
46
+ orderId: string;
47
+ action: string;
48
+ fields: readonly FormField[];
49
+ enctype?: string | undefined;
50
+ });
51
+ /** The value of the named field, or `undefined`. */
52
+ field(name: string): string | undefined;
53
+ /** The fields as an object, e.g. to render the form with your own template. */
54
+ values(): Record<string, string>;
55
+ /** A complete HTML page that posts the form as soon as it loads. Every value is escaped. */
56
+ toHtml(): string;
57
+ }
58
+ /**
59
+ * Show a QR code for the customer to scan. Gateways return either a payload to encode
60
+ * (`qrString`) or a ready-made image (`qrImage`).
61
+ */
62
+ export declare class QrPayment {
63
+ /** Always `qr`. */
64
+ readonly flow: 'qr';
65
+ /** Your order id, as sent to the gateway. */
66
+ readonly orderId: string;
67
+ /** A QR payload to encode into an image yourself (KBZ Pay). */
68
+ readonly qrString: string | undefined;
69
+ /** A base64 encoded image to display as is (Yoma MMQR). */
70
+ readonly qrImage: string | undefined;
71
+ /** When the QR stops being payable, when the gateway limits it. */
72
+ readonly expiresAt: Date | undefined;
73
+ /** The gateway's id for this QR, used to check its status (Yoma `refLabel`, KBZ `prepay_id`). */
74
+ readonly reference: string | undefined;
75
+ /** The gateway's response, for logging. */
76
+ readonly raw: Raw;
77
+ constructor(init: {
78
+ orderId: string;
79
+ qrString?: string | undefined;
80
+ qrImage?: string | undefined;
81
+ expiresAt?: Date | undefined;
82
+ reference?: string | undefined;
83
+ raw?: Raw;
84
+ });
85
+ /** The image as a data URI for an `<img src>`, or `undefined` when there is no image. */
86
+ qrImageDataUri(mimeType?: string): string | undefined;
87
+ }
88
+ /**
89
+ * Pass these values to your mobile app, which hands them to the wallet's SDK.
90
+ * `JSON.stringify(payment)` writes `orderId`, `orderInfo`, `sign` and `signType`.
91
+ */
92
+ export declare class AppPayment {
93
+ /** Always `app`. */
94
+ readonly flow: 'app';
95
+ /** Your order id, as sent to the gateway. */
96
+ readonly orderId: string;
97
+ /** The signed order string the SDK expects. */
98
+ readonly orderInfo: string;
99
+ /** The signature of `orderInfo`. */
100
+ readonly sign: string;
101
+ /** The signature algorithm, e.g. `SHA256`. */
102
+ readonly signType: string;
103
+ /** The gateway's response, for logging. Left out of `toJSON()`. */
104
+ readonly raw: Raw;
105
+ constructor(init: {
106
+ orderId: string;
107
+ orderInfo: string;
108
+ sign: string;
109
+ signType: string;
110
+ raw?: Raw;
111
+ });
112
+ /** The values your app needs: `orderId`, `orderInfo`, `sign` and `signType`. */
113
+ toJSON(): {
114
+ orderId: string;
115
+ orderInfo: string;
116
+ sign: string;
117
+ signType: string;
118
+ };
119
+ }
120
+ /** Escapes text for HTML content and attribute values. @internal */
121
+ export declare function escapeHtml(value: string): string;
122
+ export {};
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Send the customer's browser to `url` to complete the payment.
3
+ */
4
+ export class RedirectPayment {
5
+ /** Always `redirect`. */
6
+ flow = 'redirect';
7
+ /** Your order id, as sent to the gateway. */
8
+ orderId;
9
+ /** The gateway page to redirect the customer to. */
10
+ url;
11
+ /** The gateway's id for this attempt (KBZ `prepay_id`, Wave `transaction_id`). */
12
+ gatewayReference;
13
+ /** The gateway's response, for logging. */
14
+ raw;
15
+ constructor(init) {
16
+ this.orderId = init.orderId;
17
+ this.url = init.url;
18
+ this.gatewayReference = init.gatewayReference;
19
+ this.raw = init.raw ?? {};
20
+ }
21
+ }
22
+ /**
23
+ * POST `fields` to `action` from the customer's browser. {@link FormPayment.toHtml} renders a page
24
+ * that does this automatically.
25
+ */
26
+ export class FormPayment {
27
+ /** Always `form`. */
28
+ flow = 'form';
29
+ /** Your order id, as sent to the gateway. */
30
+ orderId;
31
+ /** The gateway URL the form posts to. */
32
+ action;
33
+ /** The signed hidden fields, in signing order. Post them unchanged. */
34
+ fields;
35
+ /** The encoding the gateway expects for the form. */
36
+ enctype;
37
+ constructor(init) {
38
+ this.orderId = init.orderId;
39
+ this.action = init.action;
40
+ this.fields = Object.freeze(init.fields.map((field) => Object.freeze({ ...field })));
41
+ this.enctype = init.enctype ?? 'application/x-www-form-urlencoded';
42
+ }
43
+ /** The value of the named field, or `undefined`. */
44
+ field(name) {
45
+ return this.fields.find((field) => field.name === name)?.value;
46
+ }
47
+ /** The fields as an object, e.g. to render the form with your own template. */
48
+ values() {
49
+ return Object.fromEntries(this.fields.map((field) => [field.name, field.value]));
50
+ }
51
+ /** A complete HTML page that posts the form as soon as it loads. Every value is escaped. */
52
+ toHtml() {
53
+ const inputs = this.fields
54
+ .map((field) => `<input type="hidden" name="${escapeHtml(field.name)}" value="${escapeHtml(field.value)}">`)
55
+ .join('');
56
+ return ('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Redirecting to payment</title></head><body>' +
57
+ `<form id="payment-form" method="POST" action="${escapeHtml(this.action)}" enctype="${escapeHtml(this.enctype)}">` +
58
+ inputs +
59
+ '<noscript><button type="submit">Continue to payment</button></noscript></form>' +
60
+ '<script>document.getElementById("payment-form").submit();</script></body></html>');
61
+ }
62
+ }
63
+ /**
64
+ * Show a QR code for the customer to scan. Gateways return either a payload to encode
65
+ * (`qrString`) or a ready-made image (`qrImage`).
66
+ */
67
+ export class QrPayment {
68
+ /** Always `qr`. */
69
+ flow = 'qr';
70
+ /** Your order id, as sent to the gateway. */
71
+ orderId;
72
+ /** A QR payload to encode into an image yourself (KBZ Pay). */
73
+ qrString;
74
+ /** A base64 encoded image to display as is (Yoma MMQR). */
75
+ qrImage;
76
+ /** When the QR stops being payable, when the gateway limits it. */
77
+ expiresAt;
78
+ /** The gateway's id for this QR, used to check its status (Yoma `refLabel`, KBZ `prepay_id`). */
79
+ reference;
80
+ /** The gateway's response, for logging. */
81
+ raw;
82
+ constructor(init) {
83
+ this.orderId = init.orderId;
84
+ this.qrString = init.qrString;
85
+ this.qrImage = init.qrImage;
86
+ this.expiresAt = init.expiresAt;
87
+ this.reference = init.reference;
88
+ this.raw = init.raw ?? {};
89
+ }
90
+ /** The image as a data URI for an `<img src>`, or `undefined` when there is no image. */
91
+ qrImageDataUri(mimeType = 'image/png') {
92
+ return this.qrImage === undefined ? undefined : `data:${mimeType};base64,${this.qrImage}`;
93
+ }
94
+ }
95
+ /**
96
+ * Pass these values to your mobile app, which hands them to the wallet's SDK.
97
+ * `JSON.stringify(payment)` writes `orderId`, `orderInfo`, `sign` and `signType`.
98
+ */
99
+ export class AppPayment {
100
+ /** Always `app`. */
101
+ flow = 'app';
102
+ /** Your order id, as sent to the gateway. */
103
+ orderId;
104
+ /** The signed order string the SDK expects. */
105
+ orderInfo;
106
+ /** The signature of `orderInfo`. */
107
+ sign;
108
+ /** The signature algorithm, e.g. `SHA256`. */
109
+ signType;
110
+ /** The gateway's response, for logging. Left out of `toJSON()`. */
111
+ raw;
112
+ constructor(init) {
113
+ this.orderId = init.orderId;
114
+ this.orderInfo = init.orderInfo;
115
+ this.sign = init.sign;
116
+ this.signType = init.signType;
117
+ this.raw = init.raw ?? {};
118
+ }
119
+ /** The values your app needs: `orderId`, `orderInfo`, `sign` and `signType`. */
120
+ toJSON() {
121
+ return {
122
+ orderId: this.orderId,
123
+ orderInfo: this.orderInfo,
124
+ sign: this.sign,
125
+ signType: this.signType,
126
+ };
127
+ }
128
+ }
129
+ const HTML_ESCAPES = {
130
+ '&': '&amp;',
131
+ '<': '&lt;',
132
+ '>': '&gt;',
133
+ '"': '&#34;',
134
+ "'": '&#39;',
135
+ };
136
+ /** Escapes text for HTML content and attribute values. @internal */
137
+ export function escapeHtml(value) {
138
+ return value.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
139
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A gateway-independent payment status. Every gateway's own status values are mapped onto these.
3
+ */
4
+ export type PaymentStatus = 'successful' | 'pending' | 'failed' | 'cancelled' | 'expired' | 'unknown';
5
+ /**
6
+ * The payment statuses, plus helpers.
7
+ *
8
+ * ```ts
9
+ * if (callback.status === PaymentStatus.Successful) { ... }
10
+ * PaymentStatus.isFinal(result.status)
11
+ * ```
12
+ */
13
+ export declare const PaymentStatus: Readonly<{
14
+ /** The customer paid. The only status that means money was collected. */
15
+ Successful: "successful";
16
+ /** The payment is still in progress or waiting on the customer. */
17
+ Pending: "pending";
18
+ /** The payment was attempted and failed or was rejected. */
19
+ Failed: "failed";
20
+ /** The payment or order was canceled or closed before completing. */
21
+ Cancelled: "cancelled";
22
+ /** The payment window ran out before the customer paid. */
23
+ Expired: "expired";
24
+ /** The gateway sent a status this package does not recognize. Inspect `gatewayStatus`. */
25
+ Unknown: "unknown";
26
+ /** Every status, in the order above. */
27
+ values: readonly ["successful", "pending", "failed", "cancelled", "expired", "unknown"];
28
+ /** Whether the status will not change any more: `false` for `pending` and `unknown`. */
29
+ isFinal(status: PaymentStatus): boolean;
30
+ }>;
31
+ /**
32
+ * Maps a gateway status onto a {@link PaymentStatus}, trimming whitespace. Statuses missing from
33
+ * the map resolve to `unknown`.
34
+ */
35
+ export declare function resolveStatus(statuses: Readonly<Record<string, PaymentStatus>>, gatewayStatus: string | null | undefined): PaymentStatus;
36
+ /**
37
+ * How the customer completes a payment after it has been initiated.
38
+ */
39
+ export type PaymentFlow = 'redirect' | 'form' | 'qr' | 'app';
40
+ /** The payment flows. */
41
+ export declare const PaymentFlow: Readonly<{
42
+ /** Send the customer's browser to a gateway-hosted URL. */
43
+ readonly Redirect: "redirect";
44
+ /** POST a signed form from the customer's browser to the gateway. */
45
+ readonly Form: "form";
46
+ /** Show a QR code the customer scans with their wallet app. */
47
+ readonly Qr: "qr";
48
+ /** Hand a signed payload to your mobile app, which opens the wallet SDK. */
49
+ readonly App: "app";
50
+ }>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The payment statuses, plus helpers.
3
+ *
4
+ * ```ts
5
+ * if (callback.status === PaymentStatus.Successful) { ... }
6
+ * PaymentStatus.isFinal(result.status)
7
+ * ```
8
+ */
9
+ export const PaymentStatus = Object.freeze({
10
+ /** The customer paid. The only status that means money was collected. */
11
+ Successful: 'successful',
12
+ /** The payment is still in progress or waiting on the customer. */
13
+ Pending: 'pending',
14
+ /** The payment was attempted and failed or was rejected. */
15
+ Failed: 'failed',
16
+ /** The payment or order was canceled or closed before completing. */
17
+ Cancelled: 'cancelled',
18
+ /** The payment window ran out before the customer paid. */
19
+ Expired: 'expired',
20
+ /** The gateway sent a status this package does not recognize. Inspect `gatewayStatus`. */
21
+ Unknown: 'unknown',
22
+ /** Every status, in the order above. */
23
+ values: Object.freeze([
24
+ 'successful',
25
+ 'pending',
26
+ 'failed',
27
+ 'cancelled',
28
+ 'expired',
29
+ 'unknown',
30
+ ]),
31
+ /** Whether the status will not change any more: `false` for `pending` and `unknown`. */
32
+ isFinal(status) {
33
+ return status !== 'pending' && status !== 'unknown';
34
+ },
35
+ });
36
+ /**
37
+ * Maps a gateway status onto a {@link PaymentStatus}, trimming whitespace. Statuses missing from
38
+ * the map resolve to `unknown`.
39
+ */
40
+ export function resolveStatus(statuses, gatewayStatus) {
41
+ const key = (gatewayStatus ?? '').trim();
42
+ return Object.prototype.hasOwnProperty.call(statuses, key)
43
+ ? statuses[key]
44
+ : 'unknown';
45
+ }
46
+ /** The payment flows. */
47
+ export const PaymentFlow = Object.freeze({
48
+ /** Send the customer's browser to a gateway-hosted URL. */
49
+ Redirect: 'redirect',
50
+ /** POST a signed form from the customer's browser to the gateway. */
51
+ Form: 'form',
52
+ /** Show a QR code the customer scans with their wallet app. */
53
+ Qr: 'qr',
54
+ /** Hand a signed payload to your mobile app, which opens the wallet SDK. */
55
+ App: 'app',
56
+ });
@@ -0,0 +1,46 @@
1
+ import { Amount } from './amount.js';
2
+ /** What a gateway's documentation allows for an amount. @internal */
3
+ export interface AmountRule {
4
+ /** The gateway's display name, used in error messages. */
5
+ gateway: string;
6
+ /** The most fractional digits allowed: 0 means whole amounts only, `null` means no limit. */
7
+ maxDecimals: number | null;
8
+ /** Limits the amount's text length. */
9
+ maxLength?: number;
10
+ /** Accepts an amount of 0. */
11
+ allowZero?: boolean;
12
+ }
13
+ /**
14
+ * Converts an amount input to an {@link Amount}, or `undefined` when it is missing or not a valid
15
+ * non-negative amount. Validation reports why.
16
+ *
17
+ * @internal
18
+ */
19
+ export declare function toAmount(value: unknown): Amount | undefined;
20
+ /** UTF-8 byte length, matching the byte limits in the gateway documentation. @internal */
21
+ export declare function byteLength(value: string): number;
22
+ /**
23
+ * Collects one error per field (the first one wins) and throws them together.
24
+ *
25
+ * @internal
26
+ */
27
+ export declare class Validator {
28
+ private readonly errors;
29
+ required(field: string, value: unknown): this;
30
+ /** Fails when the value is set but not a string. */
31
+ string(field: string, value: unknown): this;
32
+ length(field: string, value: string | undefined, min: number, max: number): this;
33
+ max(field: string, value: string | undefined, max: number): this;
34
+ pattern(field: string, value: string | undefined, pattern: RegExp, description: string): this;
35
+ /** Fails when a set value is not an integer within [min, max]. */
36
+ between(field: string, value: unknown, min: number, max: number): this;
37
+ amount(field: string, value: unknown, rule: AmountRule): this;
38
+ /** Fails when a set value is not an absolute http or https URL. */
39
+ url(field: string, value: string | undefined): this;
40
+ when(condition: boolean, field: string, message: string): this;
41
+ /** Whether any check failed so far. */
42
+ failed(): boolean;
43
+ /** Throws an {@link InvalidPaymentDataError} when any check failed. */
44
+ validate(): void;
45
+ private fail;
46
+ }
@@ -0,0 +1,142 @@
1
+ import { Amount } from './amount.js';
2
+ import { InvalidPaymentDataError } from './errors.js';
3
+ /**
4
+ * Converts an amount input to an {@link Amount}, or `undefined` when it is missing or not a valid
5
+ * non-negative amount. Validation reports why.
6
+ *
7
+ * @internal
8
+ */
9
+ export function toAmount(value) {
10
+ if (value instanceof Amount) {
11
+ return value;
12
+ }
13
+ if ((typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) ||
14
+ (typeof value === 'bigint' && value >= 0n)) {
15
+ return Amount.kyat(value);
16
+ }
17
+ return undefined;
18
+ }
19
+ /** UTF-8 byte length, matching the byte limits in the gateway documentation. @internal */
20
+ export function byteLength(value) {
21
+ return Buffer.byteLength(value, 'utf8');
22
+ }
23
+ /**
24
+ * Collects one error per field (the first one wins) and throws them together.
25
+ *
26
+ * @internal
27
+ */
28
+ export class Validator {
29
+ errors = {};
30
+ required(field, value) {
31
+ if (typeof value !== 'string' || value.trim() === '') {
32
+ this.fail(field, `The ${field} field is required.`);
33
+ }
34
+ return this;
35
+ }
36
+ /** Fails when the value is set but not a string. */
37
+ string(field, value) {
38
+ if (value !== undefined && value !== null && typeof value !== 'string') {
39
+ this.fail(field, `The ${field} field must be a string.`);
40
+ }
41
+ return this;
42
+ }
43
+ length(field, value, min, max) {
44
+ if (typeof value === 'string' && value !== '') {
45
+ const size = byteLength(value);
46
+ if (size < min || size > max) {
47
+ this.fail(field, `The ${field} field must be between ${min} and ${max} characters.`);
48
+ }
49
+ }
50
+ return this;
51
+ }
52
+ max(field, value, max) {
53
+ if (typeof value === 'string' && byteLength(value) > max) {
54
+ this.fail(field, `The ${field} field must not be greater than ${max} characters.`);
55
+ }
56
+ return this;
57
+ }
58
+ pattern(field, value, pattern, description) {
59
+ if (typeof value === 'string' && value !== '' && !pattern.test(value)) {
60
+ this.fail(field, `The ${field} field may only contain ${description}.`);
61
+ }
62
+ return this;
63
+ }
64
+ /** Fails when a set value is not an integer within [min, max]. */
65
+ between(field, value, min, max) {
66
+ if (value !== undefined &&
67
+ value !== null &&
68
+ (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max)) {
69
+ this.fail(field, `The ${field} field must be between ${min} and ${max}.`);
70
+ }
71
+ return this;
72
+ }
73
+ amount(field, value, rule) {
74
+ if (value === undefined || value === null) {
75
+ return this.fail(field, `The ${field} field is required.`);
76
+ }
77
+ if (typeof value === 'number' && Number.isFinite(value) && !Number.isInteger(value)) {
78
+ return this.fail(field, `The ${field} field must be a whole number of kyat or an Amount; use Amount.parse('10.50') for decimal amounts.`);
79
+ }
80
+ if (!(value instanceof Amount) && typeof value !== 'number' && typeof value !== 'bigint') {
81
+ return this.fail(field, `The ${field} field must be an Amount, e.g. Amount.kyat(1000) or Amount.parse('1000.50').`);
82
+ }
83
+ const amount = toAmount(value);
84
+ if (amount === undefined) {
85
+ return this.fail(field, `The ${field} field must be a non-negative number such as 1000 or 1000.50.`);
86
+ }
87
+ const places = amount.decimalPlaces();
88
+ if (rule.maxDecimals === 0 && places > 0) {
89
+ return this.fail(field, `${rule.gateway} does not accept decimal amounts; the ${field} field must be a whole number.`);
90
+ }
91
+ if (rule.maxDecimals !== null && rule.maxDecimals > 0 && places > rule.maxDecimals) {
92
+ return this.fail(field, `${rule.gateway} accepts at most ${rule.maxDecimals} decimal places; the ${field} field has ${places}.`);
93
+ }
94
+ if (rule.allowZero !== true && amount.isZero()) {
95
+ return this.fail(field, `The ${field} field must be greater than 0.`);
96
+ }
97
+ if (rule.maxLength !== undefined && amount.toString().length > rule.maxLength) {
98
+ return this.fail(field, `The ${field} field must not be greater than ${rule.maxLength} characters.`);
99
+ }
100
+ return this;
101
+ }
102
+ /** Fails when a set value is not an absolute http or https URL. */
103
+ url(field, value) {
104
+ if (typeof value !== 'string' || value === '') {
105
+ return this;
106
+ }
107
+ let valid;
108
+ try {
109
+ const parsed = new URL(value);
110
+ valid = (parsed.protocol === 'http:' || parsed.protocol === 'https:') && parsed.host !== '';
111
+ }
112
+ catch {
113
+ valid = false;
114
+ }
115
+ if (!valid) {
116
+ this.fail(field, `The ${field} field must be a valid http or https URL.`);
117
+ }
118
+ return this;
119
+ }
120
+ when(condition, field, message) {
121
+ if (condition) {
122
+ this.fail(field, message);
123
+ }
124
+ return this;
125
+ }
126
+ /** Whether any check failed so far. */
127
+ failed() {
128
+ return Object.keys(this.errors).length > 0;
129
+ }
130
+ /** Throws an {@link InvalidPaymentDataError} when any check failed. */
131
+ validate() {
132
+ if (this.failed()) {
133
+ throw new InvalidPaymentDataError(this.errors);
134
+ }
135
+ }
136
+ fail(field, message) {
137
+ if (!Object.prototype.hasOwnProperty.call(this.errors, field)) {
138
+ this.errors[field] = message;
139
+ }
140
+ return this;
141
+ }
142
+ }
@@ -0,0 +1,16 @@
1
+ import { type LosslessObject } from './json.js';
2
+ /**
3
+ * Returns a scalar as the text a gateway signs: strings as is, numbers exactly as sent, booleans as
4
+ * `true`/`false`. Objects, arrays, `null` and `undefined` return `undefined`.
5
+ *
6
+ * @internal
7
+ */
8
+ export declare function scalarString(value: unknown): string | undefined;
9
+ /** The value at `key` as a string, or `''` when it is missing or not a scalar. @internal */
10
+ export declare function get(source: Record<string, unknown>, key: string): string;
11
+ /** The value at `key` as a string, or `undefined` when it is missing, `null` or not a scalar. @internal */
12
+ export declare function optional(source: Record<string, unknown>, key: string): string | undefined;
13
+ /** The value at `key` with surrounding whitespace removed. @internal */
14
+ export declare function trimmed(source: Record<string, unknown>, key: string): string;
15
+ /** The value at `key` when it is an object (not an array). @internal */
16
+ export declare function object(source: Record<string, unknown>, key: string): LosslessObject | undefined;
@@ -0,0 +1,38 @@
1
+ import { isLosslessObject, JsonNumber } from './json.js';
2
+ /**
3
+ * Returns a scalar as the text a gateway signs: strings as is, numbers exactly as sent, booleans as
4
+ * `true`/`false`. Objects, arrays, `null` and `undefined` return `undefined`.
5
+ *
6
+ * @internal
7
+ */
8
+ export function scalarString(value) {
9
+ switch (typeof value) {
10
+ case 'string':
11
+ return value;
12
+ case 'boolean':
13
+ return value ? 'true' : 'false';
14
+ case 'number':
15
+ return Number.isFinite(value) ? String(value) : undefined;
16
+ case 'bigint':
17
+ return value.toString();
18
+ default:
19
+ return value instanceof JsonNumber ? value.text : undefined;
20
+ }
21
+ }
22
+ /** The value at `key` as a string, or `''` when it is missing or not a scalar. @internal */
23
+ export function get(source, key) {
24
+ return scalarString(source[key]) ?? '';
25
+ }
26
+ /** The value at `key` as a string, or `undefined` when it is missing, `null` or not a scalar. @internal */
27
+ export function optional(source, key) {
28
+ return scalarString(source[key]);
29
+ }
30
+ /** The value at `key` with surrounding whitespace removed. @internal */
31
+ export function trimmed(source, key) {
32
+ return get(source, key).trim();
33
+ }
34
+ /** The value at `key` when it is an object (not an array). @internal */
35
+ export function object(source, key) {
36
+ const value = source[key];
37
+ return isLosslessObject(value) ? value : undefined;
38
+ }