@laranex/myanmar-payments 0.0.0-stage → 4.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,95 @@
1
+ import { type AmountInput } from '../core/amount.js';
2
+ import { CallbackRequest, PaymentCallback } from '../core/callback.js';
3
+ import { type EnvSource } from '../core/env.js';
4
+ import { FormPayment } from '../core/results.js';
5
+ /** The settings of {@link CyberSourceConfig}. */
6
+ export interface CyberSourceConfigOptions {
7
+ /** The Secure Acceptance profile id. */
8
+ profileId: string;
9
+ /** The profile's access key. */
10
+ accessKey: string;
11
+ /** The profile's secret key, which signs the fields. */
12
+ secretKey: string;
13
+ /** Use the test environment (default `true`). */
14
+ sandbox?: boolean | undefined;
15
+ /** Overrides the Secure Acceptance base URL. */
16
+ baseUrl?: string | undefined;
17
+ }
18
+ /**
19
+ * A CyberSource Secure Acceptance profile. A missing credential throws a `ConfigurationError`.
20
+ */
21
+ export declare class CyberSourceConfig {
22
+ static readonly SANDBOX_URL = "https://testsecureacceptance.cybersource.com";
23
+ static readonly PRODUCTION_URL = "https://secureacceptance.cybersource.com";
24
+ readonly profileId: string;
25
+ readonly accessKey: string;
26
+ readonly secretKey: string;
27
+ readonly sandbox: boolean;
28
+ /** The base URL in use. */
29
+ readonly baseUrl: string;
30
+ constructor(options: CyberSourceConfigOptions);
31
+ /**
32
+ * Reads `CYBER_SOURCE_PROFILE_ID`, `CYBER_SOURCE_ACCESS_KEY`, `CYBER_SOURCE_SECRET_KEY`,
33
+ * `CYBER_SOURCE_SANDBOX` and `CYBER_SOURCE_BASE_URL`.
34
+ */
35
+ static fromEnv(env?: EnvSource): CyberSourceConfig;
36
+ }
37
+ /** What CyberSource does with the card. */
38
+ export type CyberSourceTransactionType = 'sale' | 'authorization' | 'sale,create_payment_token' | 'authorization,create_payment_token';
39
+ /** The CyberSource transaction types. */
40
+ export declare const CyberSourceTransactionType: Readonly<{
41
+ /** Authorize and capture in one step. */
42
+ readonly Sale: "sale";
43
+ /** Authorize only; capture later. */
44
+ readonly Authorization: "authorization";
45
+ /** A sale that also saves the card as a payment token. */
46
+ readonly SaleAndCreateToken: "sale,create_payment_token";
47
+ /** An authorization that also saves the card as a payment token. */
48
+ readonly AuthorizationAndCreateToken: "authorization,create_payment_token";
49
+ }>;
50
+ /**
51
+ * A Secure Acceptance card payment. CyberSource is multi-currency.
52
+ */
53
+ export interface CyberSourcePaymentData {
54
+ /** Your order id (`reference_number`), at most 50 characters. Echoed back as `req_reference_number`. */
55
+ orderId: string;
56
+ /** The total in `currency`: 0 or more, any decimals, at most 15 characters, e.g. `Amount.parse('10.50')`. */
57
+ amount: AmountInput;
58
+ /** The URL CyberSource posts the result to (`override_backoffice_post_url`), at most 255 characters. */
59
+ callbackUrl: string;
60
+ /** The receipt page (`override_custom_receipt_page`), at most 255 characters. */
61
+ returnUrl?: string | undefined;
62
+ /** The page shown on cancel (`override_custom_cancel_page`), at most 255 characters. */
63
+ cancelUrl?: string | undefined;
64
+ /** An ISO 4217 code (default `MMK`). */
65
+ currency?: string | undefined;
66
+ /** What to do with the card (default `sale`). */
67
+ transactionType?: CyberSourceTransactionType | undefined;
68
+ /** The hosted page language, e.g. `en-us` (the default). */
69
+ locale?: string | undefined;
70
+ }
71
+ /**
72
+ * CyberSource Secure Acceptance hosted checkout for card payments: signs the form and verifies the
73
+ * result posts. Makes no network calls.
74
+ */
75
+ export declare class CyberSource {
76
+ /** The configuration in use. */
77
+ readonly config: CyberSourceConfig;
78
+ constructor(config: CyberSourceConfig | CyberSourceConfigOptions);
79
+ /** A gateway configured from the `CYBER_SOURCE_*` environment variables. */
80
+ static fromEnv(env?: EnvSource): CyberSource;
81
+ /** Checks the payment against the Secure Acceptance field reference; throws an `InvalidPaymentDataError`. */
82
+ static validate(data: CyberSourcePaymentData): void;
83
+ /**
84
+ * Signs the payment fields. The customer's browser must POST the returned form to CyberSource;
85
+ * `toHtml()` renders a page that does it.
86
+ */
87
+ initiate(data: CyberSourcePaymentData): FormPayment;
88
+ /**
89
+ * Verifies CyberSource's result post. The same check works for the browser post to your receipt
90
+ * page.
91
+ */
92
+ handleCallback(request: CallbackRequest): PaymentCallback;
93
+ /** Signs the fields listed in `signed_field_names`; `undefined` when a listed field is missing. */
94
+ private sign;
95
+ }
@@ -0,0 +1,184 @@
1
+ import {} from '../core/amount.js';
2
+ import { CallbackRequest, losslessInput, PaymentCallback } from '../core/callback.js';
3
+ import { hmacSha256Base64, randomHex, safeEqual } from '../core/crypto.js';
4
+ import { defaultEnv, envFirst, envSandbox, optionalSetting, requireSetting, trimUrl, } from '../core/env.js';
5
+ import { SignatureVerificationError } from '../core/errors.js';
6
+ import { toPlainObject } from '../core/json.js';
7
+ import { FormPayment } from '../core/results.js';
8
+ import { resolveStatus } from '../core/status.js';
9
+ import { toAmount, Validator } from '../core/validate.js';
10
+ import { get, optional, scalarString, trimmed } from '../core/values.js';
11
+ /**
12
+ * A CyberSource Secure Acceptance profile. A missing credential throws a `ConfigurationError`.
13
+ */
14
+ export class CyberSourceConfig {
15
+ static SANDBOX_URL = 'https://testsecureacceptance.cybersource.com';
16
+ static PRODUCTION_URL = 'https://secureacceptance.cybersource.com';
17
+ profileId;
18
+ accessKey;
19
+ secretKey;
20
+ sandbox;
21
+ /** The base URL in use. */
22
+ baseUrl;
23
+ constructor(options) {
24
+ this.profileId = requireSetting('cyber_source', 'profile_id', options.profileId);
25
+ this.accessKey = requireSetting('cyber_source', 'access_key', options.accessKey);
26
+ this.secretKey = requireSetting('cyber_source', 'secret_key', options.secretKey);
27
+ this.sandbox = options.sandbox ?? true;
28
+ this.baseUrl = trimUrl(optionalSetting(options.baseUrl) ??
29
+ (this.sandbox ? CyberSourceConfig.SANDBOX_URL : CyberSourceConfig.PRODUCTION_URL));
30
+ }
31
+ /**
32
+ * Reads `CYBER_SOURCE_PROFILE_ID`, `CYBER_SOURCE_ACCESS_KEY`, `CYBER_SOURCE_SECRET_KEY`,
33
+ * `CYBER_SOURCE_SANDBOX` and `CYBER_SOURCE_BASE_URL`.
34
+ */
35
+ static fromEnv(env = defaultEnv()) {
36
+ return new CyberSourceConfig({
37
+ profileId: envFirst(env, 'CYBER_SOURCE_PROFILE_ID'),
38
+ accessKey: envFirst(env, 'CYBER_SOURCE_ACCESS_KEY'),
39
+ secretKey: envFirst(env, 'CYBER_SOURCE_SECRET_KEY'),
40
+ sandbox: envSandbox(env, 'CYBER_SOURCE_SANDBOX'),
41
+ baseUrl: envFirst(env, 'CYBER_SOURCE_BASE_URL'),
42
+ });
43
+ }
44
+ }
45
+ /** The CyberSource transaction types. */
46
+ export const CyberSourceTransactionType = Object.freeze({
47
+ /** Authorize and capture in one step. */
48
+ Sale: 'sale',
49
+ /** Authorize only; capture later. */
50
+ Authorization: 'authorization',
51
+ /** A sale that also saves the card as a payment token. */
52
+ SaleAndCreateToken: 'sale,create_payment_token',
53
+ /** An authorization that also saves the card as a payment token. */
54
+ AuthorizationAndCreateToken: 'authorization,create_payment_token',
55
+ });
56
+ const TRANSACTION_TYPES = Object.values(CyberSourceTransactionType);
57
+ const SIGNED_FIELDS = [
58
+ 'access_key',
59
+ 'profile_id',
60
+ 'transaction_uuid',
61
+ 'signed_field_names',
62
+ 'signed_date_time',
63
+ 'locale',
64
+ 'transaction_type',
65
+ 'reference_number',
66
+ 'amount',
67
+ 'currency',
68
+ 'override_custom_receipt_page',
69
+ 'override_backoffice_post_url',
70
+ 'override_custom_cancel_page',
71
+ ];
72
+ const STATUSES = {
73
+ ACCEPT: 'successful',
74
+ REVIEW: 'pending',
75
+ DECLINE: 'failed',
76
+ ERROR: 'failed',
77
+ CANCEL: 'cancelled',
78
+ };
79
+ /**
80
+ * CyberSource Secure Acceptance hosted checkout for card payments: signs the form and verifies the
81
+ * result posts. Makes no network calls.
82
+ */
83
+ export class CyberSource {
84
+ /** The configuration in use. */
85
+ config;
86
+ constructor(config) {
87
+ this.config = config instanceof CyberSourceConfig ? config : new CyberSourceConfig(config);
88
+ }
89
+ /** A gateway configured from the `CYBER_SOURCE_*` environment variables. */
90
+ static fromEnv(env = defaultEnv()) {
91
+ return new CyberSource(CyberSourceConfig.fromEnv(env));
92
+ }
93
+ /** Checks the payment against the Secure Acceptance field reference; throws an `InvalidPaymentDataError`. */
94
+ static validate(data) {
95
+ new Validator()
96
+ .required('orderId', data.orderId)
97
+ .max('orderId', data.orderId, 50)
98
+ .amount('amount', data.amount, {
99
+ gateway: 'CyberSource',
100
+ maxDecimals: null,
101
+ maxLength: 15,
102
+ allowZero: true,
103
+ })
104
+ .required('callbackUrl', data.callbackUrl)
105
+ .url('callbackUrl', data.callbackUrl)
106
+ .max('callbackUrl', data.callbackUrl, 255)
107
+ .string('returnUrl', data.returnUrl)
108
+ .url('returnUrl', data.returnUrl)
109
+ .max('returnUrl', data.returnUrl, 255)
110
+ .string('cancelUrl', data.cancelUrl)
111
+ .url('cancelUrl', data.cancelUrl)
112
+ .max('cancelUrl', data.cancelUrl, 255)
113
+ .pattern('currency', String(data.currency || 'MMK'), /^[A-Z]{3}$/, 'a three letter ISO 4217 code')
114
+ .pattern('locale', String(data.locale || 'en-us'), /^[a-z]{2}-[a-z]{2}$/, 'a locale code such as en-us')
115
+ .when(!TRANSACTION_TYPES.includes(String(data.transactionType || 'sale')), 'transactionType', 'The transactionType field is not a supported transaction type.')
116
+ .validate();
117
+ }
118
+ /**
119
+ * Signs the payment fields. The customer's browser must POST the returned form to CyberSource;
120
+ * `toHtml()` renders a page that does it.
121
+ */
122
+ initiate(data) {
123
+ CyberSource.validate(data);
124
+ const values = {
125
+ access_key: this.config.accessKey,
126
+ profile_id: this.config.profileId,
127
+ transaction_uuid: randomHex(),
128
+ signed_field_names: SIGNED_FIELDS.join(','),
129
+ signed_date_time: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
130
+ locale: data.locale || 'en-us',
131
+ transaction_type: data.transactionType || 'sale',
132
+ reference_number: data.orderId,
133
+ amount: String(toAmount(data.amount)),
134
+ currency: data.currency || 'MMK',
135
+ override_custom_receipt_page: data.returnUrl ?? '',
136
+ override_backoffice_post_url: data.callbackUrl,
137
+ override_custom_cancel_page: data.cancelUrl ?? '',
138
+ };
139
+ const fields = SIGNED_FIELDS.map((name) => ({ name, value: values[name] }));
140
+ fields.push({ name: 'signature', value: this.sign(values) });
141
+ return new FormPayment({
142
+ orderId: data.orderId,
143
+ action: `${this.config.baseUrl}/pay`,
144
+ fields,
145
+ });
146
+ }
147
+ /**
148
+ * Verifies CyberSource's result post. The same check works for the browser post to your receipt
149
+ * page.
150
+ */
151
+ handleCallback(request) {
152
+ const payload = losslessInput(request);
153
+ const expected = this.sign(payload);
154
+ if (expected === undefined || !safeEqual(expected, get(payload, 'signature'))) {
155
+ throw new SignatureVerificationError('CyberSource callback signature verification failed.', toPlainObject(payload));
156
+ }
157
+ const decision = trimmed(payload, 'decision').toUpperCase();
158
+ return new PaymentCallback({
159
+ orderId: get(payload, 'req_reference_number'),
160
+ status: resolveStatus(STATUSES, decision),
161
+ gatewayStatus: decision,
162
+ gatewayReference: optional(payload, 'transaction_id'),
163
+ amount: get(payload, 'auth_amount') || optional(payload, 'req_amount'),
164
+ raw: toPlainObject(payload),
165
+ });
166
+ }
167
+ /** Signs the fields listed in `signed_field_names`; `undefined` when a listed field is missing. */
168
+ sign(fields) {
169
+ const pairs = [];
170
+ for (const name of get(fields, 'signed_field_names').split(',')) {
171
+ if (name === '') {
172
+ continue;
173
+ }
174
+ const value = scalarString(fields[name]);
175
+ if (value === undefined) {
176
+ return undefined;
177
+ }
178
+ pairs.push(`${name}=${value}`);
179
+ }
180
+ return pairs.length === 0
181
+ ? undefined
182
+ : hmacSha256Base64(this.config.secretKey, pairs.join(','));
183
+ }
184
+ }
@@ -0,0 +1 @@
1
+ export { CyberSource, CyberSourceConfig, CyberSourceTransactionType, type CyberSourceConfigOptions, type CyberSourcePaymentData, } from './cyber-source.js';
@@ -0,0 +1 @@
1
+ export { CyberSource, CyberSourceConfig, CyberSourceTransactionType, } from './cyber-source.js';
@@ -0,0 +1,7 @@
1
+ export * from './core/index.js';
2
+ export * from './aya-pay/index.js';
3
+ export * from './cyber-source/index.js';
4
+ export * from './kbz-pay/index.js';
5
+ export * from './wave-money/index.js';
6
+ export * from './yoma-mmqr/index.js';
7
+ export { MyanmarPayments, type MyanmarPaymentsConfig, type MyanmarPaymentsOptions, } from './myanmar-payments.js';
@@ -0,0 +1,7 @@
1
+ export * from './core/index.js';
2
+ export * from './aya-pay/index.js';
3
+ export * from './cyber-source/index.js';
4
+ export * from './kbz-pay/index.js';
5
+ export * from './wave-money/index.js';
6
+ export * from './yoma-mmqr/index.js';
7
+ export { MyanmarPayments, } from './myanmar-payments.js';
@@ -0,0 +1,2 @@
1
+ export { KbzPay, KbzPayConfig, type KbzPayConfigOptions, type KbzPayPaymentData, } from './kbz-pay.js';
2
+ export { KbzPaySigner } from './signer.js';
@@ -0,0 +1,2 @@
1
+ export { KbzPay, KbzPayConfig, } from './kbz-pay.js';
2
+ export { KbzPaySigner } from './signer.js';
@@ -0,0 +1,102 @@
1
+ import { type AmountInput } from '../core/amount.js';
2
+ import { CallbackRequest, PaymentCallback, PaymentStatusResult } from '../core/callback.js';
3
+ import { type EnvSource } from '../core/env.js';
4
+ import { type GatewayOptions, type RequestOptions } from '../core/http.js';
5
+ import { AppPayment, QrPayment, RedirectPayment } from '../core/results.js';
6
+ import { KbzPaySigner } from './signer.js';
7
+ /** The settings of {@link KbzPayConfig}. */
8
+ export interface KbzPayConfigOptions {
9
+ /** The `appid` KBZ issued for your merchant app. */
10
+ appId: string;
11
+ /** The secret key used to sign requests. */
12
+ appKey: string;
13
+ /** The `merch_code` KBZ issued. */
14
+ merchantCode: string;
15
+ /** Use the UAT endpoints (default `true`). UAT and production issue separate credentials. */
16
+ sandbox?: boolean | undefined;
17
+ /** Overrides the API base URL, e.g. to go through a proxy. */
18
+ apiUrl?: string | undefined;
19
+ /** Overrides the PWA checkout URL. A trailing `#` or `#/` is normalized to `#/`. */
20
+ pwaUrl?: string | undefined;
21
+ }
22
+ /**
23
+ * KBZ Pay credentials and endpoints. A missing credential throws a `ConfigurationError`.
24
+ */
25
+ export declare class KbzPayConfig {
26
+ static readonly SANDBOX_API_URL = "http://api-uat.kbzpay.com/payment/gateway/uat";
27
+ static readonly PRODUCTION_API_URL = "https://api.kbzpay.com/payment/gateway";
28
+ static readonly SANDBOX_PWA_URL = "https://static.kbzpay.com/pgw/uat/pwa/#/";
29
+ static readonly PRODUCTION_PWA_URL = "https://wap.kbzpay.com/pgw/pwa/#/";
30
+ readonly appId: string;
31
+ readonly appKey: string;
32
+ readonly merchantCode: string;
33
+ readonly sandbox: boolean;
34
+ /** The API base URL in use. */
35
+ readonly apiUrl: string;
36
+ /** The PWA checkout URL in use, always ending in `/`. */
37
+ readonly pwaUrl: string;
38
+ constructor(options: KbzPayConfigOptions);
39
+ /**
40
+ * Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`, `KBZ_PAY_SANDBOX`,
41
+ * `KBZ_PAY_BASE_URL` and `KBZ_PAY_PWA_BASE_REDIRECT_URL`.
42
+ */
43
+ static fromEnv(env?: EnvSource): KbzPayConfig;
44
+ }
45
+ /**
46
+ * A KBZ Pay order, used for the PWA, QR and in-app flows alike. KBZ Pay only accepts MMK.
47
+ */
48
+ export interface KbzPayPaymentData {
49
+ /** Your unique order id (`merch_order_id`): letters, digits and `_` only, at most 40. */
50
+ orderId: string;
51
+ /** Kyat, greater than 0 with at most 2 decimal places, e.g. `Amount.kyat(1000)` or `Amount.parse('1000.50')`. */
52
+ amount: AmountInput;
53
+ /** The public URL KBZ posts the result to (`notify_url`): at most 512 characters, no query string. */
54
+ callbackUrl: string;
55
+ /** The product name shown to the customer. */
56
+ title?: string | undefined;
57
+ /** How long the order stays payable, 1 to 120 minutes. KBZ defaults to 120. */
58
+ timeoutMinutes?: number | undefined;
59
+ /** Free text KBZ sends back unchanged in the callback, at most 512 characters once URL-encoded. */
60
+ callbackInfo?: string | undefined;
61
+ }
62
+ /**
63
+ * KBZ Pay: PWA (redirect), QR and in-app payments, order status queries and payment
64
+ * notifications.
65
+ */
66
+ export declare class KbzPay {
67
+ /** The configuration in use. */
68
+ readonly config: KbzPayConfig;
69
+ /** KBZ Pay's request signer, for custom calls. */
70
+ readonly signer: KbzPaySigner;
71
+ private readonly transport;
72
+ constructor(config: KbzPayConfig | KbzPayConfigOptions, options?: GatewayOptions);
73
+ /** A gateway configured from the `KBZ_PAY_*` environment variables. */
74
+ static fromEnv(env?: EnvSource, options?: GatewayOptions): KbzPay;
75
+ /** Checks the order against KBZ's documented limits; throws an `InvalidPaymentDataError`. */
76
+ static validate(data: KbzPayPaymentData): void;
77
+ /**
78
+ * Creates an order and returns the KBZ Pay PWA checkout URL to redirect the customer to.
79
+ *
80
+ * The redirect only works on a phone with the KBZ Pay app installed, and its Referer must match
81
+ * the URL registered with KBZ.
82
+ */
83
+ pwa(data: KbzPayPaymentData, options?: RequestOptions): Promise<RedirectPayment>;
84
+ /** Creates an order and returns a QR payload for the customer to scan with the KBZ Pay app. */
85
+ qr(data: KbzPayPaymentData, options?: RequestOptions): Promise<QrPayment>;
86
+ /**
87
+ * Creates an order and returns the signed values your mobile app passes to `KBZPay.startPay()`.
88
+ * The SDK's own result only means the payment screen closed; rely on the callback or `status()`.
89
+ */
90
+ app(data: KbzPayPaymentData, options?: RequestOptions): Promise<AppPayment>;
91
+ /** Asks KBZ Pay for the current state of an order (`queryorder`). */
92
+ status(orderId: string, options?: RequestOptions): Promise<PaymentStatusResult>;
93
+ /**
94
+ * Verifies KBZ Pay's payment notification. Reply with the callback's `acknowledgement` (plain
95
+ * `success`) or KBZ retries.
96
+ */
97
+ handleCallback(request: CallbackRequest): PaymentCallback;
98
+ private precreate;
99
+ /** Sends a signed request and returns the `Response` object, throwing when KBZ reports an error. */
100
+ private call;
101
+ private orderInfoFields;
102
+ }
@@ -0,0 +1,262 @@
1
+ import {} from '../core/amount.js';
2
+ import { Acknowledgement, CallbackRequest, losslessBody, PaymentCallback, PaymentStatusResult, } from '../core/callback.js';
3
+ import { queryEscape, randomHex } from '../core/crypto.js';
4
+ import { defaultEnv, envFirst, envSandbox, optionalSetting, requireSetting, trimUrl, } from '../core/env.js';
5
+ import { ApiError, SignatureVerificationError } from '../core/errors.js';
6
+ import { httpClientFrom, Transport, } from '../core/http.js';
7
+ import { toPlainObject } from '../core/json.js';
8
+ import { AppPayment, QrPayment, RedirectPayment } from '../core/results.js';
9
+ import { resolveStatus } from '../core/status.js';
10
+ import { toAmount, Validator } from '../core/validate.js';
11
+ import { get, object, optional, trimmed } from '../core/values.js';
12
+ import { KbzPaySigner } from './signer.js';
13
+ /**
14
+ * KBZ Pay credentials and endpoints. A missing credential throws a `ConfigurationError`.
15
+ */
16
+ export class KbzPayConfig {
17
+ static SANDBOX_API_URL = 'http://api-uat.kbzpay.com/payment/gateway/uat';
18
+ static PRODUCTION_API_URL = 'https://api.kbzpay.com/payment/gateway';
19
+ static SANDBOX_PWA_URL = 'https://static.kbzpay.com/pgw/uat/pwa/#/';
20
+ static PRODUCTION_PWA_URL = 'https://wap.kbzpay.com/pgw/pwa/#/';
21
+ appId;
22
+ appKey;
23
+ merchantCode;
24
+ sandbox;
25
+ /** The API base URL in use. */
26
+ apiUrl;
27
+ /** The PWA checkout URL in use, always ending in `/`. */
28
+ pwaUrl;
29
+ constructor(options) {
30
+ this.appId = requireSetting('kbz_pay', 'app_id', options.appId);
31
+ this.appKey = requireSetting('kbz_pay', 'app_key', options.appKey);
32
+ this.merchantCode = requireSetting('kbz_pay', 'merchant_code', options.merchantCode);
33
+ this.sandbox = options.sandbox ?? true;
34
+ this.apiUrl = trimUrl(optionalSetting(options.apiUrl) ??
35
+ (this.sandbox ? KbzPayConfig.SANDBOX_API_URL : KbzPayConfig.PRODUCTION_API_URL));
36
+ this.pwaUrl = `${trimUrl(optionalSetting(options.pwaUrl) ??
37
+ (this.sandbox ? KbzPayConfig.SANDBOX_PWA_URL : KbzPayConfig.PRODUCTION_PWA_URL))}/`;
38
+ }
39
+ /**
40
+ * Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`, `KBZ_PAY_SANDBOX`,
41
+ * `KBZ_PAY_BASE_URL` and `KBZ_PAY_PWA_BASE_REDIRECT_URL`.
42
+ */
43
+ static fromEnv(env = defaultEnv()) {
44
+ return new KbzPayConfig({
45
+ appId: envFirst(env, 'KBZ_PAY_APP_ID'),
46
+ appKey: envFirst(env, 'KBZ_PAY_APP_KEY'),
47
+ merchantCode: envFirst(env, 'KBZ_PAY_MERCHANT_CODE'),
48
+ sandbox: envSandbox(env, 'KBZ_PAY_SANDBOX'),
49
+ apiUrl: envFirst(env, 'KBZ_PAY_BASE_URL'),
50
+ pwaUrl: envFirst(env, 'KBZ_PAY_PWA_BASE_REDIRECT_URL'),
51
+ });
52
+ }
53
+ }
54
+ const ORDER_ID = /^[A-Za-z0-9_]+$/;
55
+ const STATUSES = {
56
+ PAY_SUCCESS: 'successful',
57
+ WAIT_PAY: 'pending',
58
+ PAYING: 'pending',
59
+ PAY_FAILED: 'failed',
60
+ ORDER_CLOSED: 'cancelled',
61
+ ORDER_EXPIRED: 'expired',
62
+ };
63
+ /**
64
+ * KBZ Pay: PWA (redirect), QR and in-app payments, order status queries and payment
65
+ * notifications.
66
+ */
67
+ export class KbzPay {
68
+ /** The configuration in use. */
69
+ config;
70
+ /** KBZ Pay's request signer, for custom calls. */
71
+ signer;
72
+ transport;
73
+ constructor(config, options = {}) {
74
+ this.config = config instanceof KbzPayConfig ? config : new KbzPayConfig(config);
75
+ this.signer = new KbzPaySigner(this.config.appKey);
76
+ this.transport = new Transport(httpClientFrom(options));
77
+ }
78
+ /** A gateway configured from the `KBZ_PAY_*` environment variables. */
79
+ static fromEnv(env = defaultEnv(), options = {}) {
80
+ return new KbzPay(KbzPayConfig.fromEnv(env), options);
81
+ }
82
+ /** Checks the order against KBZ's documented limits; throws an `InvalidPaymentDataError`. */
83
+ static validate(data) {
84
+ const callbackUrl = typeof data.callbackUrl === 'string' ? data.callbackUrl : '';
85
+ new Validator()
86
+ .required('orderId', data.orderId)
87
+ .max('orderId', data.orderId, 40)
88
+ .pattern('orderId', data.orderId, ORDER_ID, 'letters, numbers and underscores')
89
+ .amount('amount', data.amount, { gateway: 'KBZ Pay', maxDecimals: 2 })
90
+ .required('callbackUrl', data.callbackUrl)
91
+ .url('callbackUrl', callbackUrl)
92
+ .max('callbackUrl', callbackUrl, 512)
93
+ .when(callbackUrl.includes('?'), 'callbackUrl', 'The callbackUrl field must not contain a query string.')
94
+ .string('title', data.title)
95
+ .between('timeoutMinutes', data.timeoutMinutes, 1, 120)
96
+ .string('callbackInfo', data.callbackInfo)
97
+ .max('callbackInfo', typeof data.callbackInfo === 'string' ? queryEscape(data.callbackInfo) : undefined, 512)
98
+ .validate();
99
+ }
100
+ /**
101
+ * Creates an order and returns the KBZ Pay PWA checkout URL to redirect the customer to.
102
+ *
103
+ * The redirect only works on a phone with the KBZ Pay app installed, and its Referer must match
104
+ * the URL registered with KBZ.
105
+ */
106
+ async pwa(data, options = {}) {
107
+ const order = await this.precreate(data, 'PWAAPP', options);
108
+ const fields = this.orderInfoFields(order);
109
+ return new RedirectPayment({
110
+ orderId: data.orderId,
111
+ url: `${this.config.pwaUrl}?${this.signer.signString(fields)}&sign=${this.signer.sign(fields)}`,
112
+ gatewayReference: order.prepayId,
113
+ raw: toPlainObject(order.response),
114
+ });
115
+ }
116
+ /** Creates an order and returns a QR payload for the customer to scan with the KBZ Pay app. */
117
+ async qr(data, options = {}) {
118
+ const order = await this.precreate(data, 'PAY_BY_QRCODE', options);
119
+ const qrCode = get(order.response, 'qrCode');
120
+ if (qrCode === '') {
121
+ throw new ApiError('KBZ Pay did not return a QR code.', {
122
+ raw: toPlainObject(order.response),
123
+ });
124
+ }
125
+ return new QrPayment({
126
+ orderId: data.orderId,
127
+ qrString: qrCode,
128
+ expiresAt: data.timeoutMinutes === undefined
129
+ ? undefined
130
+ : new Date(Date.now() + data.timeoutMinutes * 60_000),
131
+ reference: order.prepayId,
132
+ raw: toPlainObject(order.response),
133
+ });
134
+ }
135
+ /**
136
+ * Creates an order and returns the signed values your mobile app passes to `KBZPay.startPay()`.
137
+ * The SDK's own result only means the payment screen closed; rely on the callback or `status()`.
138
+ */
139
+ async app(data, options = {}) {
140
+ const order = await this.precreate(data, 'APP', options);
141
+ const fields = this.orderInfoFields(order);
142
+ return new AppPayment({
143
+ orderId: data.orderId,
144
+ orderInfo: this.signer.signString(fields),
145
+ sign: this.signer.sign(fields),
146
+ signType: 'SHA256',
147
+ raw: toPlainObject(order.response),
148
+ });
149
+ }
150
+ /** Asks KBZ Pay for the current state of an order (`queryorder`). */
151
+ async status(orderId, options = {}) {
152
+ const response = await this.call('queryorder', 'kbz.payment.queryorder', '3.0', {
153
+ appid: this.config.appId,
154
+ merch_code: this.config.merchantCode,
155
+ merch_order_id: orderId,
156
+ }, {}, randomHex(), timestamp(), options.signal);
157
+ const tradeStatus = trimmed(response, 'trade_status');
158
+ return new PaymentStatusResult({
159
+ orderId: optional(response, 'merch_order_id') || orderId,
160
+ status: resolveStatus(STATUSES, tradeStatus),
161
+ gatewayStatus: tradeStatus,
162
+ gatewayReference: optional(response, 'mm_order_id'),
163
+ amount: optional(response, 'total_amount'),
164
+ raw: toPlainObject(response),
165
+ });
166
+ }
167
+ /**
168
+ * Verifies KBZ Pay's payment notification. Reply with the callback's `acknowledgement` (plain
169
+ * `success`) or KBZ retries.
170
+ */
171
+ handleCallback(request) {
172
+ const input = losslessBody(request);
173
+ const fields = object(input, 'Request') ?? input;
174
+ if (!this.signer.verify(fields)) {
175
+ throw new SignatureVerificationError('KBZ Pay callback signature verification failed.', toPlainObject(input));
176
+ }
177
+ const tradeStatus = trimmed(fields, 'trade_status');
178
+ return new PaymentCallback({
179
+ orderId: get(fields, 'merch_order_id'),
180
+ status: resolveStatus(STATUSES, tradeStatus),
181
+ gatewayStatus: tradeStatus,
182
+ gatewayReference: optional(fields, 'mm_order_id'),
183
+ amount: optional(fields, 'total_amount'),
184
+ raw: toPlainObject(fields),
185
+ acknowledgement: new Acknowledgement({ body: 'success' }),
186
+ });
187
+ }
188
+ async precreate(data, tradeType, options) {
189
+ KbzPay.validate(data);
190
+ const biz = {
191
+ appid: this.config.appId,
192
+ merch_code: this.config.merchantCode,
193
+ merch_order_id: data.orderId,
194
+ trade_type: tradeType,
195
+ total_amount: String(toAmount(data.amount)),
196
+ trans_currency: 'MMK',
197
+ };
198
+ if (data.title) {
199
+ biz.title = data.title;
200
+ }
201
+ if (data.timeoutMinutes !== undefined) {
202
+ biz.timeout_express = `${data.timeoutMinutes}m`;
203
+ }
204
+ if (data.callbackInfo) {
205
+ biz.callback_info = queryEscape(data.callbackInfo);
206
+ }
207
+ const nonce = randomHex();
208
+ const time = timestamp();
209
+ const response = await this.call('precreate', 'kbz.payment.precreate', '1.0', biz, { notify_url: data.callbackUrl }, nonce, time, options.signal);
210
+ const prepayId = get(response, 'prepay_id');
211
+ if (prepayId === '') {
212
+ throw new ApiError('KBZ Pay did not return a prepay_id.', { raw: toPlainObject(response) });
213
+ }
214
+ return { prepayId, nonce, timestamp: time, response };
215
+ }
216
+ /** Sends a signed request and returns the `Response` object, throwing when KBZ reports an error. */
217
+ async call(endpoint, method, version, biz, extra, nonce, time, signal) {
218
+ const common = {
219
+ timestamp: time,
220
+ method,
221
+ nonce_str: nonce,
222
+ version,
223
+ ...extra,
224
+ };
225
+ const request = {
226
+ ...common,
227
+ sign_type: 'SHA256',
228
+ sign: this.signer.sign({ ...common, ...biz }),
229
+ biz_content: biz,
230
+ };
231
+ const response = await this.transport.postJson(`${this.config.apiUrl}/${endpoint}`, { Request: request }, {}, signal);
232
+ const body = response.json();
233
+ const result = object(body, 'Response') ?? {};
234
+ if (!response.successful() ||
235
+ get(result, 'result') !== 'SUCCESS' ||
236
+ get(result, 'code') !== '0') {
237
+ const code = optional(result, 'code');
238
+ const message = optional(result, 'msg');
239
+ throw new ApiError(code
240
+ ? `KBZ Pay ${endpoint} failed: [${code}] ${message ?? ''}`.trimEnd()
241
+ : `KBZ Pay ${endpoint} failed with HTTP ${response.status}.`, {
242
+ gatewayCode: code,
243
+ gatewayMessage: message,
244
+ httpStatus: response.status,
245
+ raw: toPlainObject(body),
246
+ });
247
+ }
248
+ return result;
249
+ }
250
+ orderInfoFields(order) {
251
+ return {
252
+ appid: this.config.appId,
253
+ merch_code: this.config.merchantCode,
254
+ nonce_str: order.nonce,
255
+ prepay_id: order.prepayId,
256
+ timestamp: order.timestamp,
257
+ };
258
+ }
259
+ }
260
+ function timestamp() {
261
+ return String(Math.floor(Date.now() / 1000));
262
+ }