mmpay-browser-sdk 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,42 +1,50 @@
1
- export interface PaymentData {
1
+ export interface ICorePayParams {
2
2
  amount: number;
3
- currency: string;
4
3
  orderId: string;
5
4
  callbackUrl?: string;
6
5
  }
7
- export interface CreatePaymentResponse {
6
+
7
+ export interface ICreatePaymentRequestParams {
8
+ amount: number;
9
+ currency?: string;
10
+ orderId: string;
11
+ callbackUrl?: string;
12
+ nonce?: string;
13
+ }
14
+ export interface ICreatePaymentResponse {
8
15
  _id: string;
9
16
  amount: number;
10
17
  orderId: string;
11
- currency: string;
12
- transactionId: string;
18
+ currency?: string;
19
+ transactionRefId: string;
13
20
  qr: string;
14
- url: string;
15
21
  }
16
- export interface PollingResponse {
17
- _id: string;
18
- appId: string;
22
+ export interface ICreateTokenRequestParams {
23
+ amount: number;
24
+ currency?: string;
25
+ orderId: string;
26
+ callbackUrl?: string;
27
+ nonce?: string;
28
+ }
29
+ export interface ICreateTokenResponse {
19
30
  orderId: string;
31
+ token: string;
32
+ }
33
+ export interface IPollingRequest {
20
34
  amount: number;
21
- currency: string;
22
- method?: string;
23
- vendor?: string;
35
+ currency?: string;
36
+ orderId: string;
24
37
  callbackUrl?: string;
25
- callbackUrlStatus?: 'PENDING' | 'SUCCESS' | 'FAILED';
26
- callbackAt?: Date;
27
- disbursementStatus?: 'NONE' | 'PENDING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
28
- disburseAt?: Date;
29
- items: {name: string, amount: number, quantity: number}[];
30
- merchantId: string;
38
+ nonce?: string;
39
+ }
40
+ export interface IPollingResponse {
41
+ orderId: string;
42
+ transactionRefId: string;
31
43
  status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'EXPIRED';
32
- createdAt: Date;
33
- transactionRefId?: string;
34
- qr?: string;
35
- redirectUrl?: string;
36
44
  }
37
45
  export interface PolliongResult {
38
46
  success: boolean;
39
- transaction: PollingResponse;
47
+ transaction: IPollingResponse;
40
48
  }
41
49
  export interface SDKOptions {
42
50
  pollInterval?: number;
@@ -57,6 +65,7 @@ declare const window: Window & {
57
65
  export class MMPaySDK {
58
66
 
59
67
  private POLL_INTERVAL_MS: number;
68
+ private tokenKey: string;
60
69
  private publishableKey: string;
61
70
  private baseUrl: string;
62
71
  private merchantName: string;
@@ -65,12 +74,16 @@ export class MMPaySDK {
65
74
  private onCompleteCallback: ((result: PolliongResult) => void) | null = null;
66
75
  private overlayElement: HTMLDivElement | null = null;
67
76
 
68
- // Properties to store pending data for re-rendering after cancel attempt
69
- private pendingApiResponse: CreatePaymentResponse | null = null;
70
- private pendingPaymentPayload: PaymentData | null = null;
77
+ private pendingApiResponse: ICreatePaymentResponse | null = null;
78
+ private pendingPaymentPayload: ICreatePaymentRequestParams | null = null;
71
79
 
72
- private readonly QR_SIZE: number = 300;
80
+ private readonly QR_SIZE: number = 290;
73
81
 
82
+ /**
83
+ * constructor
84
+ * @param publishableKey
85
+ * @param options
86
+ */
74
87
  constructor(publishableKey: string, options: SDKOptions = {}) {
75
88
  if (!publishableKey) {
76
89
  throw new Error("A Publishable Key is required to initialize [MMPaySDK].");
@@ -79,7 +92,7 @@ export class MMPaySDK {
79
92
  this.environment = options.environment || 'production';
80
93
  this.baseUrl = options.baseUrl || 'https://api.mm-pay.com';
81
94
  this.merchantName = options.merchantName || 'Your Merchant';
82
- this.POLL_INTERVAL_MS = options.pollInterval || 3000;
95
+ this.POLL_INTERVAL_MS = options.pollInterval || 5000;
83
96
  }
84
97
  /**
85
98
  * _callApi
@@ -88,12 +101,20 @@ export class MMPaySDK {
88
101
  * @returns
89
102
  */
90
103
  private async _callApi<T>(endpoint: string, data: object = {}): Promise<T> {
104
+ let config: any = {
105
+ 'Content-Type': 'application/json',
106
+ 'Authorization': `Bearer ${this.publishableKey}`
107
+ }
108
+ if (this.tokenKey) {
109
+ config = {
110
+ 'Content-Type': 'application/json',
111
+ 'Authorization': `Bearer ${this.publishableKey}`,
112
+ 'X-MMPay-Btoken': `${this.tokenKey}`
113
+ }
114
+ }
91
115
  const response = await fetch(`${this.baseUrl}${endpoint}`, {
92
116
  method: 'POST',
93
- headers: {
94
- 'Content-Type': 'application/json',
95
- 'Authorization': `Bearer ${this.publishableKey}`
96
- },
117
+ headers: config,
97
118
  body: JSON.stringify(data)
98
119
  });
99
120
  if (!response.ok) {
@@ -103,26 +124,125 @@ export class MMPaySDK {
103
124
  return response.json() as Promise<T>;
104
125
  }
105
126
  /**
106
- * createPaymentRequest
107
- * @param {PaymentData} payload
108
- * @returns
127
+ * _callApiTokenRequest
128
+ * @param {ICreateTokenRequestParams} payload
129
+ * @param {number} payload.amount
130
+ * @param {string} payload.currency
131
+ * @param {string} payload.orderId
132
+ * @param {string} payload.nonce
133
+ * @param {string} payload.callbackUrl
134
+ * @returns {Promise<ICreateTokenResponse>}
135
+ */
136
+ private async _callApiTokenRequest(payload: ICreateTokenRequestParams): Promise<ICreateTokenResponse> {
137
+ try {
138
+ const endpoint = this.environment === 'sandbox'
139
+ ? '/xpayments/sandbox-token-request'
140
+ : '/xpayments/production-token-request';
141
+ return await this._callApi<ICreateTokenResponse>(endpoint, payload);
142
+ } catch (error) {
143
+ console.error("Token request failed:", error);
144
+ throw error;
145
+ }
146
+ }
147
+ /**
148
+ * _callApiPaymentRequest
149
+ * @param {ICreatePaymentRequestParams} payload
150
+ * @param {number} payload.amount
151
+ * @param {string} payload.currency
152
+ * @param {string} payload.orderId
153
+ * @param {string} payload.nonce
154
+ * @param {string} payload.callbackUrl
155
+ * @returns {Promise<ICreatePaymentResponse>}
109
156
  */
110
- async createPaymentRequest(payload: PaymentData): Promise<CreatePaymentResponse> {
157
+ private async _callApiPaymentRequest(payload: ICreatePaymentRequestParams): Promise<ICreatePaymentResponse> {
111
158
  try {
112
159
  const endpoint = this.environment === 'sandbox'
113
160
  ? '/xpayments/sandbox-payment-create'
114
161
  : '/xpayments/production-payment-create';
162
+ return await this._callApi<ICreatePaymentResponse>(endpoint, payload);
163
+ } catch (error) {
164
+ console.error("Payment request failed:", error);
165
+ throw error;
166
+ }
167
+ }
115
168
 
116
- return await this._callApi<CreatePaymentResponse>(endpoint, payload);
169
+
170
+
171
+ /**
172
+ * createPayment
173
+ * @param {ICorePayParams} params
174
+ * @param {number} params.amount
175
+ * @param {string} params.orderId
176
+ * @param {string} params.callbackUrl
177
+ * @returns {Promise<ICreatePaymentResponse>}
178
+ */
179
+ public async createPayment(params: ICorePayParams): Promise<ICreatePaymentResponse> {
180
+ const payload: ICreatePaymentRequestParams = {
181
+ amount: params.amount,
182
+ orderId: params.orderId,
183
+ callbackUrl: params.callbackUrl,
184
+ currency: 'MMK',
185
+ nonce: new Date().getTime().toString() + '_mmp'
186
+ }
187
+ try {
188
+ const tokenResponse = await this._callApiTokenRequest(payload);
189
+ this.tokenKey = tokenResponse.token as string;
190
+ const apiResponse = await this._callApiPaymentRequest(payload);
191
+ return apiResponse
117
192
  } catch (error) {
118
193
  console.error("Payment request failed:", error);
119
194
  throw error;
120
195
  }
121
196
  }
197
+ /**
198
+ * showPaymentModal
199
+ * @param {ICorePayParams} params
200
+ * @param {number} params.amount
201
+ * @param {string} params.orderId
202
+ * @param {string} params.callbackUrl
203
+ * @param {Function} onComplete
204
+ */
205
+ public async showPaymentModal(
206
+ params: ICorePayParams,
207
+ onComplete: (result: PolliongResult) => void
208
+ ): Promise<void> {
209
+ const initialContent = `<div class="mmpay-overlay-content"><div style="text-align: center; color: #fff;">ငွေပေးချေမှု စတင်နေသည်...</div></div>`;
210
+ this._createAndRenderModal(initialContent, false);
211
+ this.onCompleteCallback = onComplete;
212
+ const payload: ICreatePaymentRequestParams = {
213
+ amount: params.amount,
214
+ orderId: params.orderId,
215
+ callbackUrl: params.callbackUrl,
216
+ currency: 'MMK',
217
+ nonce: new Date().getTime().toString() + '_mmp'
218
+ }
219
+
220
+ try {
221
+ const tokenResponse = await this._callApiTokenRequest(payload);
222
+ this.tokenKey = tokenResponse.token as string;
223
+ const apiResponse = await this._callApiPaymentRequest(payload);
224
+ if (apiResponse && apiResponse.qr && apiResponse.transactionRefId) {
225
+ this.pendingApiResponse = apiResponse;
226
+ this.pendingPaymentPayload = payload;
227
+ this._renderQrModalContent(apiResponse, payload, this.merchantName);
228
+ this._startPolling(payload, onComplete);
229
+ } else {
230
+ this._showTerminalMessage(apiResponse.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်ရန် မအောင်မြင်ပါ။ QR ဒေတာ မရရှိပါ။');
231
+ }
232
+ } catch (error) {
233
+ this.tokenKey = null;
234
+ this._showTerminalMessage(payload.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်စဉ် အမှားအယွင်း ဖြစ်ပွားသည်။ ကွန်ဆိုးလ်တွင် ကြည့်ပါ။');
235
+ }
236
+ }
237
+
238
+
239
+
240
+
241
+
122
242
  /**
123
243
  * _createAndRenderModal
124
244
  * @param {string} contentHtml
125
- * @param isTerminal
245
+ * @param {boolean} isTerminal
126
246
  * @returns
127
247
  */
128
248
  private _createAndRenderModal(contentHtml: string, isTerminal: boolean = false): HTMLDivElement {
@@ -134,7 +254,6 @@ export class MMPaySDK {
134
254
  const style = document.createElement('style');
135
255
  style.innerHTML = `
136
256
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=Padauk:wght@400;700&display=swap');
137
-
138
257
  #mmpay-full-modal {
139
258
  position: fixed;
140
259
  top: 0;
@@ -233,42 +352,15 @@ export class MMPaySDK {
233
352
  document.body.style.overflow = 'hidden'; // FIX: Prevent body scroll when modal is open
234
353
  return overlay;
235
354
  }
236
- /**
237
- * showPaymentModal
238
- * @param {PaymentData} payload
239
- * @param {Function} onComplete
240
- */
241
- public async showPaymentModal(
242
- payload: PaymentData,
243
- onComplete: (result: PolliongResult) => void
244
- ): Promise<void> {
245
- const initialContent = `<div class="mmpay-overlay-content"><div style="text-align: center; color: #fff;">ငွေပေးချေမှု စတင်နေသည်...</div></div>`;
246
- this._createAndRenderModal(initialContent, false);
247
- this.onCompleteCallback = onComplete;
248
- try {
249
- const apiResponse = await this.createPaymentRequest(payload);
250
- if (apiResponse && apiResponse.qr && apiResponse.transactionId) {
251
- this.pendingApiResponse = apiResponse;
252
- this.pendingPaymentPayload = payload;
253
- this._renderQrModalContent(apiResponse, payload, this.merchantName);
254
- this._startPolling(apiResponse._id, onComplete);
255
- } else {
256
- this._showTerminalMessage(apiResponse.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်ရန် မအောင်မြင်ပါ။ QR ဒေတာ မရရှိပါ။');
257
- }
258
- } catch (error) {
259
- // Myanmar translation for "Error during payment initiation. See console."
260
- this._showTerminalMessage(payload.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်စဉ် အမှားအယွင်း ဖြစ်ပွားသည်။ ကွန်ဆိုးလ်တွင် ကြည့်ပါ။');
261
- }
262
- }
263
355
  /**
264
356
  * _renderQrModalContent
265
- * @param {CreatePaymentResponse} apiResponse
266
- * @param {PaymentData} payload
357
+ * @param {ICreatePaymentResponse} apiResponse
358
+ * @param {CreatePaymentRequest} payload
267
359
  * @param {string} merchantName
268
360
  */
269
- private _renderQrModalContent(apiResponse: CreatePaymentResponse, payload: PaymentData, merchantName: string): void {
361
+ private _renderQrModalContent(apiResponse: ICreatePaymentResponse, payload: ICreatePaymentRequestParams, merchantName: string): void {
270
362
  const qrData = apiResponse.qr;
271
- const amountDisplay = `${apiResponse.amount.toFixed(2)} ${apiResponse.currency}`;
363
+ const amountDisplay = `${apiResponse.amount.toFixed(2)} MMK`;
272
364
  const qrCanvasId = 'mmpayQrCanvas';
273
365
  const orderId = payload.orderId;
274
366
  window.MMPayDownloadQR = function () {
@@ -333,7 +425,7 @@ export class MMPaySDK {
333
425
  <span class="mmpay-text-myanmar">မှာယူမှုနံပါတ်:</span> <strong>${apiResponse.orderId}</strong>
334
426
  </div>
335
427
  <div class="mmpay-detail">
336
- <span class="mmpay-text-myanmar">ငွေပေးငွေယူနံပါတ်:</span> <strong>${apiResponse.transactionId}</strong>
428
+ <span class="mmpay-text-myanmar">ငွေပေးငွေယူနံပါတ်:</span> <strong>${apiResponse.transactionRefId}</strong>
337
429
  </div>
338
430
 
339
431
  <p class="mmpay-warning mmpay-text-myanmar">
@@ -441,7 +533,7 @@ export class MMPaySDK {
441
533
  }
442
534
  /**
443
535
  * Cleans up the modal and stops polling.
444
- * @param restoreBodyScroll
536
+ * @param {boolean} restoreBodyScroll
445
537
  */
446
538
  private _cleanupModal(restoreBodyScroll: boolean): void {
447
539
  if (this.pollIntervalId !== undefined) {
@@ -486,10 +578,15 @@ export class MMPaySDK {
486
578
  }
487
579
  /**
488
580
  * _startPolling
489
- * @param {string} _id
581
+ * @param {IPollingRequest} payload
582
+ * @param {number} payload.amount
583
+ * @param {string} payload.currency
584
+ * @param {string} payload.orderId
585
+ * @param {string} payload.nonce
586
+ * @param {string} payload.callbackUrl
490
587
  * @param {Function} onComplete
491
588
  */
492
- private async _startPolling(_id: string, onComplete: (result: PolliongResult) => void): Promise<void> {
589
+ private async _startPolling(payload: IPollingRequest, onComplete: (result: PolliongResult) => void): Promise<void> {
493
590
  if (this.pollIntervalId !== undefined) {
494
591
  window.clearInterval(this.pollIntervalId);
495
592
  }
@@ -499,7 +596,7 @@ export class MMPaySDK {
499
596
  ? '/xpayments/sandbox-payment-polling'
500
597
  : '/xpayments/production-payment-polling';
501
598
 
502
- const response = await this._callApi<PollingResponse>(endpoint, {_id: _id});
599
+ const response = await this._callApi<IPollingResponse>(endpoint, payload);
503
600
  const status = (response.status || '').toUpperCase();
504
601
  if (status === 'SUCCESS' || status === 'FAILED' || status === 'EXPIRED') {
505
602
  window.clearInterval(this.pollIntervalId);
@@ -512,7 +609,8 @@ export class MMPaySDK {
512
609
  this._showTerminalMessage(response.orderId || 'N/A', status as 'SUCCESS' | 'FAILED' | 'EXPIRED', message);
513
610
 
514
611
  if (onComplete) {
515
- onComplete({success: success, transaction: response as PollingResponse});
612
+ this.tokenKey = null;
613
+ onComplete({success: success, transaction: response as IPollingResponse});
516
614
  }
517
615
  return;
518
616
  }
package/test/home.html CHANGED
@@ -1,4 +1,5 @@
1
1
  <html lang="en">
2
+
2
3
  <head>
3
4
  <meta charset="UTF-8">
4
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -12,6 +13,7 @@
12
13
  display: flex;
13
14
  justify-content: center;
14
15
  }
16
+
15
17
  .container {
16
18
  display: flex;
17
19
  flex-direction: column;
@@ -19,12 +21,15 @@
19
21
  max-width: 1000px;
20
22
  width: 100%;
21
23
  }
24
+
22
25
  @media (min-width: 768px) {
23
26
  .container {
24
27
  flex-direction: row;
25
28
  }
26
29
  }
27
- .order-summary, .payment-area {
30
+
31
+ .order-summary,
32
+ .payment-area {
28
33
  background-color: #ffffff;
29
34
  padding: 25px;
30
35
  border-radius: 10px;
@@ -32,6 +37,7 @@
32
37
  flex: 1;
33
38
  min-height: 450px;
34
39
  }
40
+
35
41
  h2 {
36
42
  color: #2c3e50;
37
43
  border-bottom: 2px solid #ecf0f1;
@@ -40,6 +46,7 @@
40
46
  font-size: 1.5rem;
41
47
  font-weight: 700;
42
48
  }
49
+
43
50
  .item-list div {
44
51
  display: flex;
45
52
  justify-content: space-between;
@@ -47,9 +54,11 @@
47
54
  border-bottom: 1px dashed #ecf0f1;
48
55
  color: #7f8c8d;
49
56
  }
57
+
50
58
  .item-list div:last-child {
51
59
  border-bottom: none;
52
60
  }
61
+
53
62
  .total {
54
63
  margin-top: 20px;
55
64
  padding-top: 15px;
@@ -60,6 +69,7 @@
60
69
  display: flex;
61
70
  justify-content: space-between;
62
71
  }
72
+
63
73
  .checkout-button {
64
74
  width: 100%;
65
75
  background-color: #2ecc71;
@@ -74,10 +84,12 @@
74
84
  margin-top: 20px;
75
85
  box-shadow: 0 2px 5px rgba(46, 204, 113, 0.4);
76
86
  }
87
+
77
88
  .checkout-button:hover {
78
89
  background-color: #27ae60;
79
90
  box-shadow: 0 4px 8px rgba(39, 174, 96, 0.6);
80
91
  }
92
+
81
93
  .payment-status {
82
94
  margin-top: 20px;
83
95
  padding: 15px;
@@ -89,10 +101,19 @@
89
101
  max-height: 200px;
90
102
  overflow-y: auto;
91
103
  }
92
- .success { color: #28a745; font-weight: bold; }
93
- .failure { color: #dc3545; font-weight: bold; }
104
+
105
+ .success {
106
+ color: #28a745;
107
+ font-weight: bold;
108
+ }
109
+
110
+ .failure {
111
+ color: #dc3545;
112
+ font-weight: bold;
113
+ }
94
114
  </style>
95
115
  </head>
116
+
96
117
  <body>
97
118
 
98
119
  <div class="container">
@@ -115,7 +136,8 @@
115
136
  <div class="payment-area">
116
137
  <h2>💳 Payment Details</h2>
117
138
  <div id="payment-container">
118
- <p style="color: #6c757d; padding: 20px;">Click the "Pay Securely" button to initialize the transaction and display the MMPay QR code.</p>
139
+ <p style="color: #6c757d; padding: 20px;">Click the "Pay Securely" button to initialize the transaction
140
+ and display the MMPay QR code.</p>
119
141
  </div>
120
142
  <div class="payment-status" id="result-container">
121
143
  Awaiting transaction polling result...
@@ -123,9 +145,7 @@
123
145
  </div>
124
146
  </div>
125
147
 
126
- <!-- The external SDK file is loaded here -->
127
- <!-- <script src="../dist/mmpay-sdk.js"></script> -->
128
- <script src="https://cdn.jsdelivr.net/npm/mmpay-browser-sdk@latest/mmpay-browser-sdk/master/dist/mmpay-sdk.js"></script>
148
+ <script src="../dist/mmpay-sdk.js"></script>
129
149
 
130
150
  <!-- Client-Side Application Logic -->
131
151
  <script>
@@ -140,11 +160,11 @@
140
160
  const mockOrder = {
141
161
  currency: 'MMK',
142
162
  items: [
143
- { name: "Fastify T-Shirt", price: 25000, qty: 1 },
144
- { name: "Node.js Dev Sticker Pack", price: 10000, qty: 3 },
145
- { name: "TypeScript Handbook (Digital)", price: 18000, qty: 1 }
163
+ { name: "Fastify T-Shirt", price: 500, qty: 1 },
164
+ { name: "Node.js Dev Sticker Pack", price: 500, qty: 3 },
165
+ { name: "TypeScript Handbook (Digital)", price: 500, qty: 1 }
146
166
  ],
147
- shipping: 2500,
167
+ shipping: 500,
148
168
  taxRate: 0.08
149
169
  };
150
170
 
@@ -207,26 +227,17 @@
207
227
  try {
208
228
 
209
229
  const sdk = new MMPaySDK(PUBLISHABLE_KEY, {
210
- baseUrl: 'https://supapay.ecgedms.com',
230
+ baseUrl: 'https://ezapi.myanmyanpay.com',
211
231
  environment: 'sandbox',
212
232
  merchantName: 'Fastify E-Shop',
213
233
  pollInterval: 5000
214
234
  });
215
235
  await sdk.showPaymentModal({
216
236
  amount: parseFloat(finalAmount.toFixed(2)),
217
- currency: mockOrder.currency,
218
237
  orderId: 'FSTY' + Date.now(),
219
238
  callbackUrl: 'https://your-fastify-backend.com/api/mmpay/webhook'
220
- },(result) => {
221
- const resultContainer = document.getElementById(RESULT_CONTAINER_ID);
222
- document.getElementById(PAY_BUTTON_ID).disabled = false;
223
- if (result.success) {
224
- resultContainer.innerHTML = '<span class="success">✅ PAYMENT SUCCESSFUL!</span><br><br>' + JSON.stringify(result.transaction, null, 2);
225
- document.getElementById(CONTAINER_ID).innerHTML = '<div style="color: #2ecc71; font-size: 1.5rem; font-weight: bold; padding: 50px 0;">Transaction Complete! Thank you for your order!</div>';
226
- } else {
227
- resultContainer.innerHTML = '<span class="failure">❌ PAYMENT FAILED / EXPIRED.</span><br><br>' + JSON.stringify(result.transaction, null, 2);
228
- document.getElementById(CONTAINER_ID).innerHTML = '<div style="color: #e74c3c; font-size: 1.5rem; font-weight: bold; padding: 50px 0;">Transaction Failed. Please try again.</div>';
229
- }
239
+ }, (result) => {
240
+
230
241
  });
231
242
 
232
243
  } catch (error) {
@@ -240,4 +251,5 @@
240
251
  window.onload = renderOrderSummary;
241
252
  </script>
242
253
  </body>
254
+
243
255
  </html>