mmpay-browser-sdk 1.0.5 → 1.0.8

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,15 +1,26 @@
1
- export interface ICorePayParams {
1
+ export interface SDKOptions {
2
+ pollInterval?: number;
3
+ environment?: 'sandbox' | 'production';
4
+ baseUrl?: string;
5
+ merchantName?: string;
6
+ }
7
+
8
+ export interface ICreateTokenRequestParams {
2
9
  amount: number;
3
10
  orderId: string;
4
- callbackUrl?: string;
11
+ nonce?: string;
12
+ }
13
+ export interface ICreateTokenResponse {
14
+ orderId: string;
15
+ token: string;
5
16
  }
6
17
 
7
18
  export interface ICreatePaymentRequestParams {
8
19
  amount: number;
9
- currency?: string;
10
20
  orderId: string;
11
21
  callbackUrl?: string;
12
22
  nonce?: string;
23
+ customMessage?: string;
13
24
  }
14
25
  export interface ICreatePaymentResponse {
15
26
  _id: string;
@@ -19,17 +30,7 @@ export interface ICreatePaymentResponse {
19
30
  transactionRefId: string;
20
31
  qr: string;
21
32
  }
22
- export interface ICreateTokenRequestParams {
23
- amount: number;
24
- currency?: string;
25
- orderId: string;
26
- callbackUrl?: string;
27
- nonce?: string;
28
- }
29
- export interface ICreateTokenResponse {
30
- orderId: string;
31
- token: string;
32
- }
33
+
33
34
  export interface IPollingRequest {
34
35
  amount: number;
35
36
  currency?: string;
@@ -46,12 +47,6 @@ export interface PolliongResult {
46
47
  success: boolean;
47
48
  transaction: IPollingResponse;
48
49
  }
49
- export interface SDKOptions {
50
- pollInterval?: number;
51
- environment?: 'sandbox' | 'production';
52
- baseUrl?: string;
53
- merchantName?: string;
54
- }
55
50
 
56
51
  declare const QRious: any;
57
52
  declare const window: Window & {
@@ -71,6 +66,7 @@ export class MMPaySDK {
71
66
  private merchantName: string;
72
67
  private environment: 'sandbox' | 'production';
73
68
  private pollIntervalId: number | undefined = undefined;
69
+ private countdownIntervalId: number | undefined = undefined;
74
70
  private onCompleteCallback: ((result: PolliongResult) => void) | null = null;
75
71
  private overlayElement: HTMLDivElement | null = null;
76
72
 
@@ -78,12 +74,8 @@ export class MMPaySDK {
78
74
  private pendingPaymentPayload: ICreatePaymentRequestParams | null = null;
79
75
 
80
76
  private readonly QR_SIZE: number = 290;
77
+ private readonly TIMEOUT_SECONDS: number = 300;
81
78
 
82
- /**
83
- * constructor
84
- * @param publishableKey
85
- * @param options
86
- */
87
79
  constructor(publishableKey: string, options: SDKOptions = {}) {
88
80
  if (!publishableKey) {
89
81
  throw new Error("A Publishable Key is required to initialize [MMPaySDK].");
@@ -94,12 +86,7 @@ export class MMPaySDK {
94
86
  this.merchantName = options.merchantName || 'Your Merchant';
95
87
  this.POLL_INTERVAL_MS = options.pollInterval || 5000;
96
88
  }
97
- /**
98
- * _callApi
99
- * @param endpoint
100
- * @param data
101
- * @returns
102
- */
89
+
103
90
  private async _callApi<T>(endpoint: string, data: object = {}): Promise<T> {
104
91
  let config: any = {
105
92
  'Content-Type': 'application/json',
@@ -123,16 +110,7 @@ export class MMPaySDK {
123
110
  }
124
111
  return response.json() as Promise<T>;
125
112
  }
126
- /**
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
- */
113
+
136
114
  private async _callApiTokenRequest(payload: ICreateTokenRequestParams): Promise<ICreateTokenResponse> {
137
115
  try {
138
116
  const endpoint = this.environment === 'sandbox'
@@ -144,16 +122,7 @@ export class MMPaySDK {
144
122
  throw error;
145
123
  }
146
124
  }
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>}
156
- */
125
+
157
126
  private async _callApiPaymentRequest(payload: ICreatePaymentRequestParams): Promise<ICreatePaymentResponse> {
158
127
  try {
159
128
  const endpoint = this.environment === 'sandbox'
@@ -166,85 +135,69 @@ export class MMPaySDK {
166
135
  }
167
136
  }
168
137
 
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 = {
138
+ public async createPayment(params: ICreatePaymentRequestParams): Promise<ICreatePaymentResponse> {
139
+ const tokenPayload: ICreateTokenRequestParams = {
140
+ amount: params.amount,
141
+ orderId: params.orderId,
142
+ nonce: new Date().getTime().toString() + '_mmp'
143
+ }
144
+ const paymentPayload: ICreatePaymentRequestParams = {
181
145
  amount: params.amount,
182
146
  orderId: params.orderId,
183
147
  callbackUrl: params.callbackUrl,
184
- currency: 'MMK',
148
+ customMessage: params.customMessage,
185
149
  nonce: new Date().getTime().toString() + '_mmp'
186
150
  }
187
151
  try {
188
- const tokenResponse = await this._callApiTokenRequest(payload);
152
+ const tokenResponse = await this._callApiTokenRequest(tokenPayload);
189
153
  this.tokenKey = tokenResponse.token as string;
190
- const apiResponse = await this._callApiPaymentRequest(payload);
191
- return apiResponse
154
+ return await this._callApiPaymentRequest(paymentPayload);
192
155
  } catch (error) {
193
156
  console.error("Payment request failed:", error);
194
157
  throw error;
195
158
  }
196
159
  }
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
- */
160
+
205
161
  public async showPaymentModal(
206
- params: ICorePayParams,
162
+ params: ICreatePaymentRequestParams,
207
163
  onComplete: (result: PolliongResult) => void
208
164
  ): Promise<void> {
209
165
  const initialContent = `<div class="mmpay-overlay-content"><div style="text-align: center; color: #fff;">ငွေပေးချေမှု စတင်နေသည်...</div></div>`;
210
166
  this._createAndRenderModal(initialContent, false);
211
167
  this.onCompleteCallback = onComplete;
212
- const payload: ICreatePaymentRequestParams = {
168
+
169
+ const tokenPayload: ICreateTokenRequestParams = {
170
+ amount: params.amount,
171
+ orderId: params.orderId,
172
+ nonce: new Date().getTime().toString() + '_mmp'
173
+ }
174
+ const paymentPayload: ICreatePaymentRequestParams = {
213
175
  amount: params.amount,
214
176
  orderId: params.orderId,
215
177
  callbackUrl: params.callbackUrl,
216
- currency: 'MMK',
178
+ customMessage: params.customMessage,
217
179
  nonce: new Date().getTime().toString() + '_mmp'
218
180
  }
219
181
 
220
182
  try {
221
- const tokenResponse = await this._callApiTokenRequest(payload);
183
+ const tokenResponse = await this._callApiTokenRequest(tokenPayload);
222
184
  this.tokenKey = tokenResponse.token as string;
223
- const apiResponse = await this._callApiPaymentRequest(payload);
185
+ const apiResponse = await this._callApiPaymentRequest(paymentPayload);
224
186
  if (apiResponse && apiResponse.qr && apiResponse.transactionRefId) {
225
187
  this.pendingApiResponse = apiResponse;
226
- this.pendingPaymentPayload = payload;
227
- this._renderQrModalContent(apiResponse, payload, this.merchantName);
228
- this._startPolling(payload, onComplete);
188
+ this.pendingPaymentPayload = paymentPayload;
189
+ this._renderQrModalContent(apiResponse, paymentPayload, this.merchantName);
190
+ this._startPolling(paymentPayload, onComplete);
191
+ this._startCountdown(paymentPayload.orderId);
229
192
  } else {
230
193
  this._showTerminalMessage(apiResponse.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်ရန် မအောင်မြင်ပါ။ QR ဒေတာ မရရှိပါ။');
231
194
  }
232
195
  } catch (error) {
233
196
  this.tokenKey = null;
234
- this._showTerminalMessage(payload.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်စဉ် အမှားအယွင်း ဖြစ်ပွားသည်။ ကွန်ဆိုးလ်တွင် ကြည့်ပါ။');
197
+ this._showTerminalMessage(paymentPayload.orderId || 'N/A', 'FAILED', 'ငွေပေးချေမှု စတင်စဉ် အမှားအယွင်း ဖြစ်ပွားသည်။ ကွန်ဆိုးလ်တွင် ကြည့်ပါ။');
235
198
  }
236
199
  }
237
200
 
238
-
239
-
240
-
241
-
242
- /**
243
- * _createAndRenderModal
244
- * @param {string} contentHtml
245
- * @param {boolean} isTerminal
246
- * @returns
247
- */
248
201
  private _createAndRenderModal(contentHtml: string, isTerminal: boolean = false): HTMLDivElement {
249
202
  this._cleanupModal(false);
250
203
  const overlay = document.createElement('div');
@@ -278,7 +231,6 @@ export class MMPaySDK {
278
231
  width: 100%;
279
232
  padding: 20px 0;
280
233
  }
281
- /* Card Base Styles */
282
234
  .mmpay-card {
283
235
  background: #ffffff;
284
236
  border-radius: 16px;
@@ -349,15 +301,10 @@ export class MMPaySDK {
349
301
  };
350
302
  window.MMPayReRenderModal = () => this._reRenderPendingModalInstance();
351
303
  overlay.innerHTML += `<div class="mmpay-overlay-content">${contentHtml}</div>`;
352
- document.body.style.overflow = 'hidden'; // FIX: Prevent body scroll when modal is open
304
+ document.body.style.overflow = 'hidden';
353
305
  return overlay;
354
306
  }
355
- /**
356
- * _renderQrModalContent
357
- * @param {ICreatePaymentResponse} apiResponse
358
- * @param {CreatePaymentRequest} payload
359
- * @param {string} merchantName
360
- */
307
+
361
308
  private _renderQrModalContent(apiResponse: ICreatePaymentResponse, payload: ICreatePaymentRequestParams, merchantName: string): void {
362
309
  const qrData = apiResponse.qr;
363
310
  const amountDisplay = `${apiResponse.amount.toFixed(2)} MMK`;
@@ -382,7 +329,7 @@ export class MMPaySDK {
382
329
  <style>
383
330
  .mmpay-card { max-width: 350px; padding: 16px; }
384
331
  .mmpay-header { color: #1f2937; font-size: 1rem; font-weight: bold; margin-bottom: 8px; }
385
- .mmpay-qr-container { padding: 0; margin: 10px auto; display: inline-block; line-height: 0; width: 300px; height: 300px; }
332
+ .mmpay-qr-container { padding: 0; margin: 5px auto 10px auto; display: inline-block; line-height: 0; width: 300px; height: 300px; }
386
333
  #${qrCanvasId} { display: block; background: white; border-radius: 8px; width: 100%; height: 100%; }
387
334
  .mmpay-amount { font-size: 1.2rem; font-weight: 800; color: #1f2937; margin: 0; }
388
335
  .mmpay-separator { border-top: 1px solid #f3f4f6; margin: 12px 0; }
@@ -391,10 +338,28 @@ export class MMPaySDK {
391
338
  .mmpay-detail span { text-align: left; }
392
339
  .mmpay-secure-text { color: #757575; border-radius: 9999px; font-size: 0.8rem; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; }
393
340
  .mmpay-warning { font-size: 0.75rem; color: #9ca3af; font-weight: 500; margin-top: 12px; line-height: 1.5; }
341
+
342
+ .mmpay-timer-badge {
343
+ background-color: #fef2f2;
344
+ color: #b91c1c;
345
+ padding: 4px 10px;
346
+ border-radius: 12px;
347
+ font-weight: 700;
348
+ font-size: 0.85rem;
349
+ display: inline-flex;
350
+ align-items: center;
351
+ gap: 5px;
352
+ margin: 8px 0;
353
+ border: 1px solid #fee2e2;
354
+ }
355
+ .mmpay-timer-icon {
356
+ width: 14px;
357
+ height: 14px;
358
+ fill: currentColor;
359
+ }
394
360
  </style>
395
361
 
396
362
  <div class="mmpay-card">
397
- <!-- Close Button - Triggers Confirmation Modal -->
398
363
  <button class="mmpay-close-btn" onclick="MMPayCloseModal(false)">
399
364
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
400
365
  <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
@@ -409,6 +374,14 @@ export class MMPaySDK {
409
374
  ${merchantName} သို့ပေးချေပါ
410
375
  </div>
411
376
 
377
+ <div class="mmpay-timer-badge" id="mmpay-timer-badge">
378
+ <svg class="mmpay-timer-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
379
+ <path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/>
380
+ <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"/>
381
+ </svg>
382
+ <span id="mmpay-countdown-text">05:00</span>
383
+ </div>
384
+
412
385
  <div class="mmpay-amount">${amountDisplay}</div>
413
386
 
414
387
  <div class="mmpay-qr-container">
@@ -441,17 +414,12 @@ export class MMPaySDK {
441
414
  this._createAndRenderModal(qrContentHtml, false);
442
415
  this._injectQrScript(qrData, qrCanvasId);
443
416
  }
444
- /**
445
- * _showTerminalMessage
446
- * @param {string} orderId
447
- * @param {string} status
448
- * @param {string} message
449
- */
417
+
450
418
  private _showTerminalMessage(orderId: string, status: 'SUCCESS' | 'FAILED' | 'EXPIRED', message: string): void {
451
419
  this._cleanupModal(true);
452
- const successColor = '#10b981'; // Tailwind Green 500
453
- const failureColor = '#ef4444'; // Tailwind Red 500
454
- const expiredColor = '#f59e0b'; // Tailwind Amber 500
420
+ const successColor = '#10b981';
421
+ const failureColor = '#ef4444';
422
+ const expiredColor = '#f59e0b';
455
423
  let color: string;
456
424
  let iconSvg: string;
457
425
  let statusText: string;
@@ -462,7 +430,6 @@ export class MMPaySDK {
462
430
  <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022l-3.473 4.425-2.094-2.094a.75.75 0 0 0-1.06 1.06L6.92 10.865l.764.764a.75.75 0 0 0 1.06 0l4.5-5.5a.75.75 0 0 0-.01-1.05z"/>
463
431
  </svg>`;
464
432
  } else {
465
- // Shared icon for FAILED and EXPIRED (X mark)
466
433
  color = status === 'FAILED' ? failureColor : expiredColor;
467
434
  statusText = status === 'FAILED' ? 'မအောင်မြင်' : 'သက်တမ်းကုန်';
468
435
  iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="${color}" viewBox="0 0 16 16">
@@ -487,16 +454,20 @@ export class MMPaySDK {
487
454
  </button>
488
455
  </div>
489
456
  `;
490
- this._createAndRenderModal(content, true); // Set isTerminal=true so the close button always forces cleanup
457
+ this._createAndRenderModal(content, true);
491
458
  }
492
- /**
493
- * _showCancelConfirmationModal
494
- */
459
+
495
460
  private _showCancelConfirmationModal(): void {
496
461
  if (this.pollIntervalId !== undefined) {
497
462
  window.clearInterval(this.pollIntervalId);
498
463
  this.pollIntervalId = undefined;
499
464
  }
465
+
466
+ if (this.countdownIntervalId !== undefined) {
467
+ window.clearInterval(this.countdownIntervalId);
468
+ this.countdownIntervalId = undefined;
469
+ }
470
+
500
471
  this._cleanupModal(false);
501
472
  const content = `
502
473
  <div class="mmpay-card mmpay-terminal-card" style="
@@ -518,11 +489,9 @@ export class MMPaySDK {
518
489
  </div>
519
490
  </div>
520
491
  `;
521
- this._createAndRenderModal(content, false); // Set isTerminal=false so the close button calls MMPayCloseModal(true)
492
+ this._createAndRenderModal(content, false);
522
493
  }
523
- /**
524
- * _reRenderPendingModalInstance
525
- */
494
+
526
495
  private _reRenderPendingModalInstance(): void {
527
496
  if (this.pendingApiResponse && this.pendingPaymentPayload && this.onCompleteCallback) {
528
497
  this._cleanupModal(true);
@@ -531,15 +500,16 @@ export class MMPaySDK {
531
500
  this._cleanupModal(true);
532
501
  }
533
502
  }
534
- /**
535
- * Cleans up the modal and stops polling.
536
- * @param {boolean} restoreBodyScroll
537
- */
503
+
538
504
  private _cleanupModal(restoreBodyScroll: boolean): void {
539
505
  if (this.pollIntervalId !== undefined) {
540
506
  window.clearInterval(this.pollIntervalId);
541
507
  this.pollIntervalId = undefined;
542
508
  }
509
+ if (this.countdownIntervalId !== undefined) {
510
+ window.clearInterval(this.countdownIntervalId);
511
+ this.countdownIntervalId = undefined;
512
+ }
543
513
  if (this.overlayElement && this.overlayElement.parentNode) {
544
514
  this.overlayElement.parentNode.removeChild(this.overlayElement);
545
515
  this.overlayElement = null;
@@ -550,11 +520,7 @@ export class MMPaySDK {
550
520
  delete window.MMPayCloseModal;
551
521
  delete window.MMPayReRenderModal;
552
522
  }
553
- /**
554
- * _injectQrScript
555
- * @param {string} qrData
556
- * @param {string} qrCanvasId
557
- */
523
+
558
524
  private _injectQrScript(qrData: string, qrCanvasId: string): void {
559
525
  const script = document.createElement('script');
560
526
  script.src = "https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js";
@@ -564,7 +530,7 @@ export class MMPaySDK {
564
530
  if (typeof QRious !== 'undefined' && canvas) {
565
531
  new QRious({
566
532
  element: canvas,
567
- value: qrData,
533
+ value: unescape(encodeURIComponent(qrData)),
568
534
  size: this.QR_SIZE,
569
535
  padding: 15,
570
536
  level: 'H'
@@ -576,16 +542,7 @@ export class MMPaySDK {
576
542
  };
577
543
  document.head.appendChild(script);
578
544
  }
579
- /**
580
- * _startPolling
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
587
- * @param {Function} onComplete
588
- */
545
+
589
546
  private async _startPolling(payload: IPollingRequest, onComplete: (result: PolliongResult) => void): Promise<void> {
590
547
  if (this.pollIntervalId !== undefined) {
591
548
  window.clearInterval(this.pollIntervalId);
@@ -621,7 +578,33 @@ export class MMPaySDK {
621
578
  checkStatus();
622
579
  this.pollIntervalId = window.setInterval(checkStatus, this.POLL_INTERVAL_MS);
623
580
  }
581
+
582
+ private _startCountdown(orderId: string): void {
583
+ if (this.countdownIntervalId !== undefined) {
584
+ window.clearInterval(this.countdownIntervalId);
585
+ }
586
+
587
+ let remaining = this.TIMEOUT_SECONDS;
588
+ const timerElement = document.getElementById('mmpay-countdown-text');
589
+
590
+ const updateDisplay = () => {
591
+ if (!timerElement) return;
592
+ const minutes = Math.floor(remaining / 60);
593
+ const seconds = remaining % 60;
594
+ timerElement.innerText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
595
+ }
596
+
597
+ this.countdownIntervalId = window.setInterval(() => {
598
+ remaining--;
599
+ updateDisplay();
600
+
601
+ if (remaining <= 0) {
602
+ window.clearInterval(this.countdownIntervalId);
603
+ this.countdownIntervalId = undefined;
604
+ this._showTerminalMessage(orderId, 'EXPIRED', 'သတ်မှတ်ချိန်ကုန်သွားပါပြီ။');
605
+ }
606
+ }, 1000);
607
+ }
624
608
  }
625
609
 
626
- // Make the SDK class and its instance methods accessible globally
627
610
  (window as any).MMPaySDK = MMPaySDK;
package/test/home.html CHANGED
@@ -150,7 +150,7 @@
150
150
  <!-- Client-Side Application Logic -->
151
151
  <script>
152
152
  // --- Configuration ---
153
- const PUBLISHABLE_KEY = 'pk_test_504013d7294ea1f6ecf4a2a0d38c31e0788286ef1b8ccacecf2a9b2809e23341'; // <<< REMEMBER TO REPLACE THIS KEY!
153
+ const PUBLISHABLE_KEY = 'pk_test_75cbca18e22481d4dec91865a23fca8984d893660c7eb3c151186adbbc0315db'; // <<< REMEMBER TO REPLACE THIS KEY!
154
154
  const CONTAINER_ID = 'payment-container';
155
155
  const RESULT_CONTAINER_ID = 'result-container';
156
156
  const PAY_BUTTON_ID = 'pay-button';
@@ -235,7 +235,8 @@
235
235
  await sdk.showPaymentModal({
236
236
  amount: parseFloat(finalAmount.toFixed(2)),
237
237
  orderId: 'FSTY' + Date.now(),
238
- callbackUrl: 'https://your-fastify-backend.com/api/mmpay/webhook'
238
+ callbackUrl: 'https://your-fastify-backend.com/api/mmpay/webhook',
239
+ customMessage: "Myan Myan Pay Is Good"
239
240
  }, (result) => {
240
241
 
241
242
  });