@mmpay/browser 0.0.0-stage → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1768 @@
1
+ /******************************************************************************
2
+ Copyright (c) Microsoft Corporation.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
14
+ ***************************************************************************** */
15
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
16
+
17
+ var extendStatics = function(d, b) {
18
+ extendStatics = Object.setPrototypeOf ||
19
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
20
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
21
+ return extendStatics(d, b);
22
+ };
23
+
24
+ function __extends(d, b) {
25
+ if (typeof b !== "function" && b !== null)
26
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
27
+ extendStatics(d, b);
28
+ function __() { this.constructor = d; }
29
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
30
+ }
31
+
32
+ var __assign = function() {
33
+ __assign = Object.assign || function __assign(t) {
34
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
35
+ s = arguments[i];
36
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
37
+ }
38
+ return t;
39
+ };
40
+ return __assign.apply(this, arguments);
41
+ };
42
+
43
+ function __awaiter(thisArg, _arguments, P, generator) {
44
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
45
+ return new (P || (P = Promise))(function (resolve, reject) {
46
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
47
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
48
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
49
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
50
+ });
51
+ }
52
+
53
+ function __generator(thisArg, body) {
54
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
55
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
56
+ function verb(n) { return function (v) { return step([n, v]); }; }
57
+ function step(op) {
58
+ if (f) throw new TypeError("Generator is already executing.");
59
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
60
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
61
+ if (y = 0, t) op = [op[0] & 2, t.value];
62
+ switch (op[0]) {
63
+ case 0: case 1: t = op; break;
64
+ case 4: _.label++; return { value: op[1], done: false };
65
+ case 5: _.label++; y = op[1]; op = [0]; continue;
66
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
67
+ default:
68
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
69
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
70
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
71
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
72
+ if (t[2]) _.ops.pop();
73
+ _.trys.pop(); continue;
74
+ }
75
+ op = body.call(thisArg, _);
76
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
77
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
78
+ }
79
+ }
80
+
81
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
82
+ var e = new Error(message);
83
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
84
+ };
85
+
86
+ var REQUEST_TIMEOUT_MS = 20000;
87
+ var MMPayApiError = /** @class */ (function (_super) {
88
+ __extends(MMPayApiError, _super);
89
+ function MMPayApiError(message, status) {
90
+ var _this = _super.call(this, message) || this;
91
+ _this.name = 'MMPayApiError';
92
+ _this.status = status;
93
+ Object.setPrototypeOf(_this, MMPayApiError.prototype);
94
+ return _this;
95
+ }
96
+ return MMPayApiError;
97
+ }(Error));
98
+ var MMPayAPI = /** @class */ (function () {
99
+ function MMPayAPI(baseUrl, environment, publishableKey) {
100
+ this.tokenKey = null;
101
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
102
+ this.environment = environment;
103
+ this.publishableKey = publishableKey;
104
+ }
105
+ MMPayAPI.prototype.setToken = function (token) {
106
+ this.tokenKey = token;
107
+ };
108
+ MMPayAPI.prototype.getToken = function () {
109
+ return this.tokenKey;
110
+ };
111
+ MMPayAPI.prototype.call = function (endpoint_1) {
112
+ return __awaiter(this, arguments, void 0, function (endpoint, data) {
113
+ var headers, controller, timeoutId, response, errorText, error_1;
114
+ if (data === void 0) { data = {}; }
115
+ return __generator(this, function (_a) {
116
+ switch (_a.label) {
117
+ case 0:
118
+ headers = {
119
+ 'Content-Type': 'application/json',
120
+ 'Authorization': "Bearer ".concat(this.publishableKey)
121
+ };
122
+ if (this.tokenKey) {
123
+ headers['X-MMPay-Btoken'] = this.tokenKey;
124
+ }
125
+ controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
126
+ timeoutId = controller ? setTimeout(function () { return controller.abort(); }, REQUEST_TIMEOUT_MS) : undefined;
127
+ _a.label = 1;
128
+ case 1:
129
+ _a.trys.push([1, 6, 7, 8]);
130
+ return [4 /*yield*/, fetch("".concat(this.baseUrl).concat(endpoint), {
131
+ method: 'POST',
132
+ headers: headers,
133
+ body: JSON.stringify(data),
134
+ signal: controller ? controller.signal : undefined
135
+ })];
136
+ case 2:
137
+ response = _a.sent();
138
+ if (!!response.ok) return [3 /*break*/, 4];
139
+ return [4 /*yield*/, response.text()];
140
+ case 3:
141
+ errorText = _a.sent();
142
+ throw new MMPayApiError("API Error (".concat(response.status, "): ").concat(response.statusText, ". Details: ").concat(errorText), response.status);
143
+ case 4: return [4 /*yield*/, response.json()];
144
+ case 5: return [2 /*return*/, _a.sent()];
145
+ case 6:
146
+ error_1 = _a.sent();
147
+ if (error_1 && error_1.name === 'AbortError') {
148
+ throw new MMPayApiError('Request timed out. Please check your connection.', 0);
149
+ }
150
+ throw error_1;
151
+ case 7:
152
+ if (timeoutId !== undefined)
153
+ clearTimeout(timeoutId);
154
+ return [7 /*endfinally*/];
155
+ case 8: return [2 /*return*/];
156
+ }
157
+ });
158
+ });
159
+ };
160
+ MMPayAPI.prototype.createToken = function (payload) {
161
+ return __awaiter(this, void 0, void 0, function () {
162
+ var endpoint;
163
+ return __generator(this, function (_a) {
164
+ endpoint = "/xpayments/".concat(this.environment, "-token-request");
165
+ return [2 /*return*/, this.call(endpoint, payload)];
166
+ });
167
+ });
168
+ };
169
+ MMPayAPI.prototype.showPayment = function (payload) {
170
+ return __awaiter(this, void 0, void 0, function () {
171
+ var endpoint;
172
+ return __generator(this, function (_a) {
173
+ endpoint = "/xpayments/".concat(this.environment, "-payment-show");
174
+ return [2 /*return*/, this.call(endpoint, payload)];
175
+ });
176
+ });
177
+ };
178
+ MMPayAPI.prototype.cancelPayment = function (payload) {
179
+ return __awaiter(this, void 0, void 0, function () {
180
+ var endpoint;
181
+ return __generator(this, function (_a) {
182
+ endpoint = "/xpayments/".concat(this.environment, "-payment-cancel");
183
+ return [2 /*return*/, this.call(endpoint, payload)];
184
+ });
185
+ });
186
+ };
187
+ MMPayAPI.prototype.createPayment = function (payload) {
188
+ return __awaiter(this, void 0, void 0, function () {
189
+ var endpoint;
190
+ return __generator(this, function (_a) {
191
+ endpoint = "/xpayments/".concat(this.environment, "-payment-create");
192
+ return [2 /*return*/, this.call(endpoint, payload)];
193
+ });
194
+ });
195
+ };
196
+ MMPayAPI.prototype.pollPayment = function (payload) {
197
+ return __awaiter(this, void 0, void 0, function () {
198
+ var endpoint;
199
+ return __generator(this, function (_a) {
200
+ endpoint = "/xpayments/".concat(this.environment, "-payment-polling");
201
+ return [2 /*return*/, this.call(endpoint, payload)];
202
+ });
203
+ });
204
+ };
205
+ /**
206
+ * A WebSocket handshake cannot carry the publishable key or session token,
207
+ * so the authenticated API issues a short-lived, single-use ticket for it.
208
+ */
209
+ MMPayAPI.prototype.createStreamTicket = function (payload) {
210
+ return __awaiter(this, void 0, void 0, function () {
211
+ var endpoint;
212
+ return __generator(this, function (_a) {
213
+ endpoint = "/xpayments/".concat(this.environment, "-payment-stream-ticket");
214
+ return [2 /*return*/, this.call(endpoint, payload)];
215
+ });
216
+ });
217
+ };
218
+ MMPayAPI.prototype.getStreamUrl = function (ticket) {
219
+ var socketBase = this.baseUrl.replace(/^http/i, 'ws');
220
+ return "".concat(socketBase, "/xpayments/").concat(this.environment, "-payment-stream?ticket=").concat(encodeURIComponent(ticket));
221
+ };
222
+ return MMPayAPI;
223
+ }());
224
+
225
+ var MAX_SOCKET_FAILURES = 3;
226
+ var SILENCE_TIMEOUT_MS = 45000;
227
+ var MAX_RECONNECT_DELAY_MS = 10000;
228
+ var MIN_POLL_INTERVAL_MS = 2000;
229
+ var CLOSE_SESSION_EXPIRED = 4408;
230
+ var CLOSE_TOO_MANY_CONNECTIONS = 4429;
231
+ /**
232
+ * Watches one payment until the owner calls `stop()`.
233
+ *
234
+ * Status is pushed over a WebSocket. If a socket cannot be established or keeps
235
+ * dropping (old server, blocking proxy), the watcher quietly degrades to the
236
+ * HTTP polling endpoint, so a payment is never left unobserved.
237
+ */
238
+ var PaymentStatusWatcher = /** @class */ (function () {
239
+ function PaymentStatusWatcher(api, payload, options) {
240
+ var _this = this;
241
+ this.api = api;
242
+ this.payload = payload;
243
+ this.options = options;
244
+ this.socket = null;
245
+ this.stopped = false;
246
+ this.connecting = false;
247
+ this.usingPolling = false;
248
+ this.socketFailures = 0;
249
+ this.pollIntervalId = undefined;
250
+ this.reconnectTimeoutId = undefined;
251
+ this.silenceTimeoutId = undefined;
252
+ this.onWake = function () { return _this.wake(); };
253
+ this.onVisibility = function () {
254
+ if (document.visibilityState === 'visible')
255
+ _this.wake();
256
+ };
257
+ }
258
+ PaymentStatusWatcher.prototype.start = function () {
259
+ // Shoppers leave to pay in their banking app; a backgrounded tab may have
260
+ // its socket frozen, so re-check the moment they come back.
261
+ document.addEventListener('visibilitychange', this.onVisibility);
262
+ window.addEventListener('online', this.onWake);
263
+ window.addEventListener('pageshow', this.onWake);
264
+ this.checkNow();
265
+ if (this.options.transport !== 'polling' && typeof WebSocket !== 'undefined') {
266
+ this.connect();
267
+ }
268
+ else {
269
+ this.startPolling();
270
+ }
271
+ };
272
+ PaymentStatusWatcher.prototype.stop = function () {
273
+ this.stopped = true;
274
+ document.removeEventListener('visibilitychange', this.onVisibility);
275
+ window.removeEventListener('online', this.onWake);
276
+ window.removeEventListener('pageshow', this.onWake);
277
+ window.clearInterval(this.pollIntervalId);
278
+ window.clearTimeout(this.reconnectTimeoutId);
279
+ window.clearTimeout(this.silenceTimeoutId);
280
+ this.closeSocket();
281
+ };
282
+ PaymentStatusWatcher.prototype.checkNow = function () {
283
+ return __awaiter(this, void 0, void 0, function () {
284
+ var response;
285
+ return __generator(this, function (_a) {
286
+ switch (_a.label) {
287
+ case 0:
288
+ _a.trys.push([0, 2, , 3]);
289
+ return [4 /*yield*/, this.api.pollPayment(this.payload)];
290
+ case 1:
291
+ response = _a.sent();
292
+ if (!this.stopped)
293
+ this.options.onStatus(response);
294
+ return [3 /*break*/, 3];
295
+ case 2:
296
+ _a.sent();
297
+ return [3 /*break*/, 3];
298
+ case 3: return [2 /*return*/];
299
+ }
300
+ });
301
+ });
302
+ };
303
+ PaymentStatusWatcher.prototype.wake = function () {
304
+ if (this.stopped)
305
+ return;
306
+ this.checkNow();
307
+ if (this.usingPolling || this.connecting)
308
+ return;
309
+ if (!this.socket || this.socket.readyState > 1) {
310
+ window.clearTimeout(this.reconnectTimeoutId);
311
+ this.connect();
312
+ }
313
+ };
314
+ PaymentStatusWatcher.prototype.connect = function () {
315
+ return __awaiter(this, void 0, void 0, function () {
316
+ var ticket, error_2, status_1, socket;
317
+ var _this = this;
318
+ return __generator(this, function (_a) {
319
+ switch (_a.label) {
320
+ case 0:
321
+ if (this.stopped || this.usingPolling || this.connecting)
322
+ return [2 /*return*/];
323
+ this.connecting = true;
324
+ _a.label = 1;
325
+ case 1:
326
+ _a.trys.push([1, 3, , 4]);
327
+ return [4 /*yield*/, this.api.createStreamTicket(this.payload)];
328
+ case 2:
329
+ ticket = (_a.sent()).ticket;
330
+ return [3 /*break*/, 4];
331
+ case 3:
332
+ error_2 = _a.sent();
333
+ this.connecting = false;
334
+ status_1 = error_2 instanceof MMPayApiError ? error_2.status : 0;
335
+ // The server does not offer the stream, or refuses this session.
336
+ if (status_1 >= 400 && status_1 < 500 && status_1 !== 429) {
337
+ this.startPolling();
338
+ }
339
+ else {
340
+ this.handleSocketFailure();
341
+ }
342
+ return [2 /*return*/];
343
+ case 4:
344
+ this.connecting = false;
345
+ if (this.stopped)
346
+ return [2 /*return*/];
347
+ try {
348
+ socket = new WebSocket(this.api.getStreamUrl(ticket));
349
+ }
350
+ catch (error) {
351
+ this.handleSocketFailure();
352
+ return [2 /*return*/];
353
+ }
354
+ this.socket = socket;
355
+ this.armSilenceTimer();
356
+ socket.onmessage = function (event) {
357
+ if (socket !== _this.socket || _this.stopped)
358
+ return;
359
+ // Only a message proves the stream works end to end.
360
+ _this.socketFailures = 0;
361
+ _this.armSilenceTimer();
362
+ _this.notifyConnection('live');
363
+ var message = _this.parseMessage(event.data);
364
+ if (message && message.type === 'status' && _this.isOwnStatus(message.data)) {
365
+ _this.options.onStatus(message.data);
366
+ }
367
+ };
368
+ socket.onclose = function (event) {
369
+ if (socket !== _this.socket || _this.stopped)
370
+ return;
371
+ _this.socket = null;
372
+ window.clearTimeout(_this.silenceTimeoutId);
373
+ if (event.code === CLOSE_SESSION_EXPIRED)
374
+ return;
375
+ if (event.code === CLOSE_TOO_MANY_CONNECTIONS) {
376
+ _this.startPolling();
377
+ return;
378
+ }
379
+ _this.handleSocketFailure();
380
+ };
381
+ socket.onerror = function () { };
382
+ return [2 /*return*/];
383
+ }
384
+ });
385
+ });
386
+ };
387
+ PaymentStatusWatcher.prototype.parseMessage = function (data) {
388
+ if (typeof data !== 'string')
389
+ return null;
390
+ try {
391
+ return JSON.parse(data);
392
+ }
393
+ catch (error) {
394
+ return null;
395
+ }
396
+ };
397
+ PaymentStatusWatcher.prototype.isOwnStatus = function (data) {
398
+ return !!data && typeof data.status === 'string' && data.orderId === this.payload.orderId;
399
+ };
400
+ PaymentStatusWatcher.prototype.handleSocketFailure = function () {
401
+ var _this = this;
402
+ if (this.stopped || this.usingPolling)
403
+ return;
404
+ this.socketFailures++;
405
+ this.checkNow();
406
+ if (this.socketFailures >= MAX_SOCKET_FAILURES && this.options.transport !== 'websocket') {
407
+ this.startPolling();
408
+ return;
409
+ }
410
+ this.notifyConnection('reconnecting');
411
+ var backoff = Math.min(MAX_RECONNECT_DELAY_MS, 1000 * Math.pow(2, this.socketFailures - 1));
412
+ var delay = backoff / 2 + Math.random() * (backoff / 2);
413
+ window.clearTimeout(this.reconnectTimeoutId);
414
+ this.reconnectTimeoutId = window.setTimeout(function () { return _this.connect(); }, delay);
415
+ };
416
+ PaymentStatusWatcher.prototype.armSilenceTimer = function () {
417
+ var _this = this;
418
+ window.clearTimeout(this.silenceTimeoutId);
419
+ this.silenceTimeoutId = window.setTimeout(function () {
420
+ // The server sends a heartbeat every 20 seconds. Silence means the
421
+ // connection is dead even if the browser has not noticed yet.
422
+ var socket = _this.socket;
423
+ _this.socket = null;
424
+ if (socket)
425
+ _this.discard(socket);
426
+ _this.handleSocketFailure();
427
+ }, SILENCE_TIMEOUT_MS);
428
+ };
429
+ PaymentStatusWatcher.prototype.startPolling = function () {
430
+ var _this = this;
431
+ if (this.stopped || this.usingPolling)
432
+ return;
433
+ this.usingPolling = true;
434
+ window.clearTimeout(this.reconnectTimeoutId);
435
+ window.clearTimeout(this.silenceTimeoutId);
436
+ this.closeSocket();
437
+ this.notifyConnection('live');
438
+ var interval = Math.max(MIN_POLL_INTERVAL_MS, this.options.pollInterval);
439
+ this.pollIntervalId = window.setInterval(function () { return _this.checkNow(); }, interval);
440
+ };
441
+ PaymentStatusWatcher.prototype.closeSocket = function () {
442
+ var socket = this.socket;
443
+ this.socket = null;
444
+ if (socket)
445
+ this.discard(socket);
446
+ };
447
+ PaymentStatusWatcher.prototype.discard = function (socket) {
448
+ socket.onmessage = null;
449
+ socket.onclose = null;
450
+ socket.onerror = null;
451
+ try {
452
+ socket.close();
453
+ }
454
+ catch (error) { }
455
+ };
456
+ PaymentStatusWatcher.prototype.notifyConnection = function (state) {
457
+ if (this.options.onConnectionChange)
458
+ this.options.onConnectionChange(state);
459
+ };
460
+ return PaymentStatusWatcher;
461
+ }());
462
+
463
+ var LANGUAGE_STORAGE_KEY = 'mmpay_lang';
464
+ var en = {
465
+ 'loading.title': 'Connecting',
466
+ 'loading.subtitle': 'Setting up a secure connection...',
467
+ 'qr.scanHint': 'Scan with any MMQR banking app',
468
+ 'qr.poweredBy': 'Payment powered by MyanMyanPay',
469
+ 'qr.orderId': 'Order ID',
470
+ 'qr.refNo': 'MMQR Ref No',
471
+ 'qr.download': 'Download QR Code',
472
+ 'qr.waiting': 'Waiting for payment',
473
+ 'qr.reconnecting': 'Reconnecting...',
474
+ 'qr.loadFailed': 'The QR code could not be displayed.',
475
+ 'qr.retry': 'Try again',
476
+ 'cancel.title': 'Cancel Transaction?',
477
+ 'cancel.body': 'If you haven\'t paid yet, you can safely cancel this process.',
478
+ 'cancel.confirm': 'Stop Process',
479
+ 'cancel.back': 'Go Back',
480
+ 'status.SUCCESS': 'Success',
481
+ 'status.CANCELLED': 'Cancelled',
482
+ 'status.EXPIRED': 'Expired',
483
+ 'status.FAILED': 'Failed',
484
+ 'message.SUCCESS': 'Payment successful.',
485
+ 'message.CANCELLED': 'Payment cancelled.',
486
+ 'message.EXPIRED': 'Payment expired.',
487
+ 'message.FAILED': 'Payment failed.',
488
+ 'message.TIMEOUT': 'Time expired.',
489
+ 'message.ERROR': 'Something went wrong while starting the payment.',
490
+ 'message.ref': 'Ref: {ref}',
491
+ 'action.close': 'Close',
492
+ 'action.language': 'Language'
493
+ };
494
+ var mm = {
495
+ 'loading.title': 'ချိတ်ဆက်နေပါသည်',
496
+ 'loading.subtitle': 'လုံခြုံရေး စနစ်ဖြင့် ချိတ်ဆက်နေပါသည်...',
497
+ 'qr.poweredBy': 'MyanMyanPay ဖြင့် ပေးချေပါ',
498
+ 'qr.orderId': 'အော်ဒါနံပါတ်',
499
+ 'qr.refNo': 'ရည်ညွှန်းနံပါတ်',
500
+ 'qr.download': 'QR ဒေါင်းလုဒ်လုပ်မည်',
501
+ 'cancel.title': 'လုပ်ငန်းစဉ်ကို ပယ်ဖျက်မည်လား?',
502
+ 'cancel.body': 'ငွေမပေးချေရသေးပါက လုပ်ငန်းစဉ်ကို ဘေးကင်းစွာ ပယ်ဖျက်နိုင်ပါသည်။',
503
+ 'cancel.confirm': 'ရပ်တန့်မည်',
504
+ 'cancel.back': 'နောက်သို့',
505
+ 'status.SUCCESS': 'အောင်မြင်ပါသည်',
506
+ 'status.CANCELLED': 'ပယ်ဖျက်လိုက်ပါသည်',
507
+ 'status.EXPIRED': 'သက်တမ်းကုန်သွားပါပြီ',
508
+ 'status.FAILED': 'မအောင်မြင်ပါ',
509
+ 'message.SUCCESS': 'ငွေပေးချေမှု အောင်မြင်ပါပြီ။',
510
+ 'message.CANCELLED': 'ငွေပေးချေမှုကို ပယ်ဖျက်လိုက်ပါသည်။',
511
+ 'message.EXPIRED': 'ငွေပေးချေမှု သက်တမ်းကုန်သွားပါပြီ။',
512
+ 'message.FAILED': 'ငွေပေးချေမှု မအောင်မြင်ပါ။',
513
+ 'message.TIMEOUT': 'သတ်မှတ်ချိန်ကုန်သွားပါပြီ။',
514
+ 'message.ERROR': 'ငွေပေးချေမှု စတင်စဉ် အမှားအယွင်း ဖြစ်ပွားသည်။',
515
+ 'message.ref': 'ရည်ညွှန်းနံပါတ်: {ref}',
516
+ 'action.close': 'ပိတ်မည်',
517
+ 'action.language': 'ဘာသာစကား',
518
+ // Added in 1.3.0 for the new screens. Everything above was already shipped.
519
+ 'qr.scanHint': 'MMQR လက်ခံသော မည်သည့်ဘဏ်အက်ပ်ဖြင့်မဆို စကင်ဖတ်ပါ',
520
+ 'qr.waiting': 'ငွေပေးချေမှုကို စောင့်ဆိုင်းနေပါသည်',
521
+ 'qr.reconnecting': 'ပြန်လည်ချိတ်ဆက်နေပါသည်...',
522
+ 'qr.loadFailed': 'QR ကုဒ်ကို ပြသ၍မရပါ။',
523
+ 'qr.retry': 'ထပ်မံကြိုးစားမည်'
524
+ };
525
+ var dictionaries = { en: en, mm: mm };
526
+ function normalizeLanguage(value) {
527
+ if (typeof value !== 'string')
528
+ return null;
529
+ var code = value.toLowerCase();
530
+ if (code === 'mm' || code === 'my' || code.indexOf('my-') === 0)
531
+ return 'mm';
532
+ if (code === 'en' || code.indexOf('en-') === 0)
533
+ return 'en';
534
+ return null;
535
+ }
536
+ /**
537
+ * A missing translation falls back to English so a screen can never render
538
+ * blank text, whichever language is selected.
539
+ */
540
+ function translate(language, key, params) {
541
+ var text = dictionaries[language][key] || en[key] || key;
542
+ if (params) {
543
+ Object.keys(params).forEach(function (name) {
544
+ text = text.split('{' + name + '}').join(params[name]);
545
+ });
546
+ }
547
+ return text;
548
+ }
549
+ /**
550
+ * The shopper's own choice wins, then the merchant's configured language, then
551
+ * the browser language.
552
+ */
553
+ function resolveInitialLanguage(preferred) {
554
+ var stored = null;
555
+ try {
556
+ stored = window.localStorage.getItem(LANGUAGE_STORAGE_KEY);
557
+ }
558
+ catch (e) { }
559
+ var browser = typeof navigator !== 'undefined' ? navigator.language : undefined;
560
+ return normalizeLanguage(stored) || normalizeLanguage(preferred) || normalizeLanguage(browser) || 'en';
561
+ }
562
+ function rememberLanguage(language) {
563
+ try {
564
+ window.localStorage.setItem(LANGUAGE_STORAGE_KEY, language);
565
+ }
566
+ catch (e) { }
567
+ }
568
+
569
+ /**
570
+ * MyanMyanPay logo (256px PNG from myanmyanpay.com/mmp-logo.png), embedded so
571
+ * the loading screen shows it instantly instead of waiting on another request.
572
+ */
573
+ var MMPAY_LOGO_DATA_URI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAHlBMVEX///8AAAADAwNFRUVjY2NTU1MuLi7n5+e9vb2BgYFUuDMdAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE90lEQVR4nO2d2XbCMAxE5yokhf//4R5atrCEYIckMLoPfeiJVTPIrm3JijBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHle2jlLcZmlWAqPjzvaY1dmI5AYLomjK6uPzQkxmaV4Cg2aqUbXdpqqsyFMsIEDQ/xd2Wfk4dD7ppDM0sAFH+te3Zni1NZmhOAYKuqtunb67SASTVuIAqBGjquq3mKMBEhlKAEkQp6QHkEFDOAQd249dx3W5oEqwytOAc0IzuQdAOCfCKoZJWbxRgLMMCtC8YWpkAuHsAh98+/MJeFOC5oTUKEIPPvyDAGEMrFCCIpr1Pc9i2jxNglKEVCsDm8a7mp2W0ADFoaHN4ZnUCBBsN0f4/9FyAZ4b+FFifAEEM72p/YrQHjDK0PgHawW7fPvVAgOebjOun1iLA5km/N2MFGGcoBVB6wIEcAsTCh6Kd4xzA+Vj871z6st9td6atEWDI0OICxKkLN8uX8zn1pZ8UCDBkaHEBOISGtqf162W/T5u3WgEeGlpeAPYxzfYYnTT0AHp7V0sB2HvoqYeWApxJAUgPUA6BAzkH4LEO6JFzAPeX8JsaD9iseS9wheFusE8KQHqA/IZAxClX19IDovez8YoL7InNdrttjwKYRYY4HQpubw8EBkJ6XxMbJDimKe2OHRgT1B2VHzBs6FGreQWIc4rvn0/+n440m/u8lh8AIwwtL0Bzs/tzzxDBPkfIOUtsLJkn2H6jB+wepHTdy/IazhV+lBx2hzXlCpeRN0aISZVMDyhBlJJDAPs5oPO+N8iEFz4/8uYolWPg4u5w1N0drvknwEpuj9cZik+sH0C/25MZmlUAav50//Mv1AmyhogwR5gjzBHmqLL96KOwm6Oxdxl6EVW1dv83GOYLoZhqKRyfuRSOyTZDlRWZltsMba23wzHhgUidoSyk1OSpcCwyBJpKz824ABkY4UsiQ7tm/uDoraEFBWjcw+NNJkgE7h7A4bdPtu1ZSIk3pMktX1O0OX6U+RIlLzKKj3maKxBgMyLDdYJCSo8ydRcWIGZNlr7J1V5cgJg3Xf6vZXvO1rcrpLTn+YT5xYWUrm/seAowASpumQKQHqAcAiPqH23eU0hp3XNA9/4iKqsX4N0FFFYvAO4ewP1+pwCkByiHwHRzwJIpMrH8HLDe3WA3iwC9an6G64D4TyypSxDhcz3gqqLnugRo319U9bqm67oEmKWISr+qr6EAzbWhFKAEUUp6ADkE1B+6ZoWUorD+0dcUUop8yUrz4mt24stfs0O+aIm/r+YeJRkizwyt0AOwzxFyzhIbSxZSar/RA3bN/C9dXVWucBmZLs/XpMuXkQKQHsBHD4GurttZSInPvjlK7ZXfi7vDdYWUqm5Pq7zpvuMTFVKKOkPLhMb4j04WXfpvun5Ms6YQwWLB0T0V0fl+04rPUJkhoKrW9jVEvgBhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc4Q5whxhjjBHmCPMEeYIc1IAzPkF8CT7lTTnMu0AAAAASUVORK5CYII=';
574
+
575
+ /**
576
+ * Styles live inside the modal's shadow root, so the merchant page's CSS
577
+ * cannot restyle the payment modal and these rules cannot leak out.
578
+ */
579
+ var MMPAY_STYLES = "\n.mmp-root, .mmp-root *, .mmp-root *::before, .mmp-root *::after { box-sizing: border-box; }\n.mmp-root {\n --mmp-card: #ffffff;\n --mmp-text: #1d1d1f;\n --mmp-sub: #6e6e73;\n --mmp-border: rgba(0, 0, 0, 0.08);\n --mmp-chip: #f2f2f5;\n --mmp-chip-active: #ffffff;\n --mmp-chip-hover: #e5e5ea;\n --mmp-panel: #f7f7f9;\n --mmp-skeleton: #e9e9ee;\n --mmp-skeleton-shine: #f6f6f9;\n --mmp-accent: #2c2c2e;\n --mmp-accent-text: #ffffff;\n --mmp-success: #1f9d55;\n --mmp-success-bg: #e6f6ec;\n --mmp-danger: #d93025;\n --mmp-danger-bg: #fce8e6;\n --mmp-warn: #c26a00;\n --mmp-warn-bg: #fff3e0;\n --mmp-card-blur: none;\n position: absolute; top: 0; right: 0; bottom: 0; left: 0;\n display: flex; align-items: center; justify-content: center;\n padding: 16px; overflow-y: auto;\n background: rgba(0, 0, 0, 0.45);\n -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);\n font-family: -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Segoe UI\", Roboto, Helvetica, Arial, \"Padauk\", \"Myanmar Text\", \"Noto Sans Myanmar\", sans-serif;\n font-size: 16px; line-height: 1.4; font-weight: 400; font-style: normal;\n letter-spacing: normal; text-align: center; text-transform: none; white-space: normal;\n color: var(--mmp-text); direction: ltr;\n -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: 100%;\n animation: mmp-fade 0.2s ease;\n}\n.mmp-root.mmp-dark {\n --mmp-card: #1c1c1e;\n --mmp-text: #f5f5f7;\n --mmp-sub: rgba(235, 235, 245, 0.62);\n --mmp-border: rgba(255, 255, 255, 0.12);\n --mmp-chip: #2c2c2e;\n --mmp-chip-active: #636366;\n --mmp-chip-hover: #3a3a3c;\n --mmp-panel: #2c2c2e;\n --mmp-skeleton: #2c2c2e;\n --mmp-skeleton-shine: #3a3a3c;\n --mmp-accent: #ffffff;\n --mmp-accent-text: #000000;\n --mmp-success: #34c759;\n --mmp-success-bg: rgba(52, 199, 89, 0.18);\n --mmp-danger: #ff6259;\n --mmp-danger-bg: rgba(255, 69, 58, 0.18);\n --mmp-warn: #ffa733;\n --mmp-warn-bg: rgba(255, 149, 0, 0.18);\n}\n.mmp-root.mmp-translucent { --mmp-card: rgba(255, 255, 255, 0.78); --mmp-card-blur: blur(20px); }\n.mmp-root.mmp-dark.mmp-translucent { --mmp-card: rgba(28, 28, 30, 0.78); }\n.mmp-root[data-lang=\"mm\"] { line-height: 1.75; }\n\n.mmp-card {\n position: relative; display: flex; flex-direction: column;\n width: 100%; max-width: 372px; max-height: 100%; margin: auto;\n overflow-x: hidden; overflow-y: auto;\n background: var(--mmp-card);\n -webkit-backdrop-filter: var(--mmp-card-blur); backdrop-filter: var(--mmp-card-blur);\n border-radius: 24px;\n box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22), 0 0 0 1px var(--mmp-border);\n outline: none;\n animation: mmp-rise 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);\n}\n.mmp-topbar { display: flex; align-items: center; justify-content: space-between; padding: 14px 14px 0 14px; flex-shrink: 0; }\n.mmp-lang { display: inline-flex; gap: 2px; padding: 3px; background: var(--mmp-chip); border: 1px solid var(--mmp-border); border-radius: 11px; }\n.mmp-lang button {\n margin: 0; padding: 5px 11px; min-width: 40px; border: none; border-radius: 8px; background: transparent;\n font: inherit; font-size: 12px; font-weight: 600; line-height: 1.5; color: var(--mmp-sub); cursor: pointer;\n transition: background 0.2s ease, color 0.2s ease;\n}\n.mmp-lang button[aria-pressed=\"true\"] { background: var(--mmp-chip-active); color: var(--mmp-text); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12); }\n.mmp-close {\n display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; margin: 0; padding: 0;\n border: none; border-radius: 50%; background: var(--mmp-chip); color: var(--mmp-sub); cursor: pointer;\n transition: background 0.2s ease, color 0.2s ease;\n}\n.mmp-close:hover { background: var(--mmp-chip-hover); color: var(--mmp-text); }\n.mmp-close[hidden] { display: flex; visibility: hidden; }\n.mmp-root button:focus-visible { outline: 2px solid var(--mmp-text); outline-offset: 2px; }\n\n.mmp-screen { display: flex; flex-direction: column; align-items: center; padding: 6px 22px 22px 22px; animation: mmp-screen-in 0.28s ease; }\n.mmp-brand { display: flex; align-items: center; justify-content: center; height: 36px; margin-bottom: 10px; }\n.mmp-brand img { display: block; height: 36px; width: auto; max-width: 160px; object-fit: contain; }\n.mmp-merchant { max-width: 100%; font-size: 15px; font-weight: 500; color: var(--mmp-sub); overflow-wrap: anywhere; }\n.mmp-amount { display: flex; align-items: baseline; justify-content: center; gap: 6px; min-height: 36px; margin: 2px 0 14px 0; }\n.mmp-amount-value { font-size: 30px; font-weight: 700; line-height: 1.2; letter-spacing: -0.5px; font-variant-numeric: tabular-nums; }\n.mmp-amount-currency { font-size: 14px; font-weight: 600; letter-spacing: 0.5px; color: var(--mmp-sub); }\n\n.mmp-qr {\n position: relative; display: flex; align-items: center; justify-content: center; flex-shrink: 0;\n width: 228px; height: 228px; padding: 12px; background: #ffffff; border-radius: 18px;\n border: 1px solid var(--mmp-border); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06); overflow: hidden;\n}\n.mmp-qr canvas { display: block; width: 100% !important; height: 100% !important; animation: mmp-qr-in 0.4s cubic-bezier(0.2, 0.8, 0.2, 1); }\n.mmp-qr-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px; width: 100%; height: 100%; }\n.mmp-loader-logo { display: block; width: 88px; height: 88px; border-radius: 22px; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18); animation: mmp-pulse 2.2s ease-in-out infinite; }\n.mmp-loader-track { position: relative; width: 120px; height: 3px; border-radius: 3px; background: #ececf1; overflow: hidden; }\n.mmp-loader-bar { position: absolute; top: 0; bottom: 0; left: 0; width: 40%; border-radius: 3px; background: #1d1d1f; animation: mmp-sweep 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite; }\n.mmp-qr-error { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 8px; font-size: 13px; color: #6e6e73; }\n.mmp-qr-error button {\n margin: 0; padding: 7px 14px; border: 1px solid #d2d2d7; border-radius: 10px; background: #ffffff;\n font: inherit; font-size: 13px; font-weight: 600; color: #1d1d1f; cursor: pointer;\n}\n\n.mmp-caption { min-height: 44px; margin: 14px 0 0 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }\n.mmp-caption-title { font-size: 15px; font-weight: 600; }\n.mmp-caption-title::after { content: \"\"; display: inline-block; width: 1.2em; text-align: left; animation: mmp-ellipsis 1.4s steps(4, end) infinite; }\n.mmp-caption-sub { font-size: 13px; color: var(--mmp-sub); }\n.mmp-hint { font-size: 13px; color: var(--mmp-sub); }\n\n.mmp-status-row { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 8px; margin: 10px 0 14px 0; }\n.mmp-waiting, .mmp-timer {\n display: inline-flex; align-items: center; gap: 7px; padding: 5px 11px; border-radius: 999px;\n font-size: 13px; font-weight: 600; background: var(--mmp-panel); border: 1px solid var(--mmp-border); color: var(--mmp-sub);\n}\n.mmp-dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--mmp-success); flex-shrink: 0; }\n.mmp-dot::after { content: \"\"; position: absolute; top: 0; right: 0; bottom: 0; left: 0; border-radius: 50%; background: inherit; animation: mmp-ping 1.6s ease-out infinite; }\n.mmp-waiting[data-state=\"reconnecting\"] .mmp-dot { background: var(--mmp-warn); }\n.mmp-timer { font-variant-numeric: tabular-nums; color: var(--mmp-text); }\n.mmp-timer svg { width: 13px; height: 13px; fill: currentColor; }\n.mmp-timer[data-urgent=\"true\"] { color: var(--mmp-danger); background: var(--mmp-danger-bg); border-color: transparent; }\n\n.mmp-details { width: 100%; padding: 12px 14px; margin-bottom: 14px; background: var(--mmp-panel); border: 1px solid var(--mmp-border); border-radius: 14px; }\n.mmp-detail { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 24px; font-size: 13px; color: var(--mmp-sub); }\n.mmp-detail + .mmp-detail { margin-top: 6px; }\n.mmp-detail > span:first-child { flex-shrink: 0; text-align: left; }\n.mmp-detail strong { font-weight: 600; color: var(--mmp-text); text-align: right; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }\n.mmp-skeleton {\n display: inline-block; height: 14px; border-radius: 7px;\n background: linear-gradient(90deg, var(--mmp-skeleton) 25%, var(--mmp-skeleton-shine) 50%, var(--mmp-skeleton) 75%);\n background-size: 200% 100%; animation: mmp-shimmer 1.4s linear infinite;\n}\n\n.mmp-btn {\n display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 50px; margin: 0; padding: 12px 20px;\n border: 1px solid transparent; border-radius: 14px; background: var(--mmp-accent); color: var(--mmp-accent-text);\n font: inherit; font-size: 16px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.14);\n transition: transform 0.15s ease, filter 0.2s ease, opacity 0.2s ease;\n}\n.mmp-btn:hover { filter: brightness(1.08); }\n.mmp-btn:active { transform: scale(0.98); }\n.mmp-btn:disabled { opacity: 0.45; cursor: default; filter: none; transform: none; box-shadow: none; }\n.mmp-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.mmp-btn-secondary { background: transparent; color: var(--mmp-text); border-color: var(--mmp-border); box-shadow: none; }\n.mmp-btn-secondary:hover { background: var(--mmp-chip); filter: none; }\n.mmp-btn-danger { background: var(--mmp-danger-bg); color: var(--mmp-danger); box-shadow: none; }\n.mmp-btn[data-busy=\"true\"]::before {\n content: \"\"; width: 16px; height: 16px; border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent;\n animation: mmp-spin 0.7s linear infinite;\n}\n.mmp-powered { margin-top: 12px; font-size: 12px; font-weight: 500; color: var(--mmp-sub); }\n\n.mmp-sheet {\n position: absolute; top: 52px; right: 0; bottom: 0; left: 0; z-index: 2;\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n padding: 12px 22px 24px 22px; background: var(--mmp-card); border-radius: 0 0 24px 24px; overflow-y: auto;\n animation: mmp-sheet-in 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);\n}\n.mmp-root.mmp-translucent .mmp-sheet { -webkit-backdrop-filter: blur(24px); backdrop-filter: blur(24px); }\n.mmp-icon { display: flex; align-items: center; justify-content: center; width: 72px; height: 72px; margin: 10px auto 18px auto; border-radius: 50%; flex-shrink: 0; animation: mmp-pop 0.45s cubic-bezier(0.2, 1.4, 0.4, 1); }\n.mmp-icon svg { width: 36px; height: 36px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }\n.mmp-icon svg path { stroke-dasharray: 60; stroke-dashoffset: 60; animation: mmp-draw 0.5s 0.2s ease forwards; }\n.mmp-icon[data-tone=\"success\"] { background: var(--mmp-success-bg); color: var(--mmp-success); }\n.mmp-icon[data-tone=\"danger\"] { background: var(--mmp-danger-bg); color: var(--mmp-danger); }\n.mmp-icon[data-tone=\"warn\"] { background: var(--mmp-warn-bg); color: var(--mmp-warn); }\n.mmp-title { margin: 0 0 10px 0; font-size: 22px; font-weight: 700; line-height: inherit; color: var(--mmp-text); }\n.mmp-pill { display: inline-block; max-width: 100%; padding: 6px 12px; margin-bottom: 14px; background: var(--mmp-panel); border: 1px solid var(--mmp-border); border-radius: 10px; font-size: 13px; font-weight: 600; color: var(--mmp-sub); overflow-wrap: anywhere; }\n.mmp-text { margin: 0 0 6px 0; font-size: 15px; color: var(--mmp-sub); overflow-wrap: anywhere; }\n.mmp-detail-text { max-width: 100%; max-height: 84px; margin: 4px 0 0 0; overflow-y: auto; font-size: 12px; line-height: 1.4; color: var(--mmp-sub); opacity: 0.85; overflow-wrap: anywhere; }\n.mmp-actions { display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 18px; }\n\n@keyframes mmp-fade { from { opacity: 0; } to { opacity: 1; } }\n@keyframes mmp-rise { from { opacity: 0; transform: translateY(18px) scale(0.97); } to { opacity: 1; transform: none; } }\n@keyframes mmp-screen-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n@keyframes mmp-sheet-in { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }\n@keyframes mmp-qr-in { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: none; } }\n@keyframes mmp-pulse { 0%, 100% { opacity: 0.88; transform: scale(1); } 50% { opacity: 1; transform: scale(1.05); } }\n@keyframes mmp-sweep { 0% { transform: translateX(-110%); } 100% { transform: translateX(260%); } }\n@keyframes mmp-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }\n@keyframes mmp-ping { 0% { transform: scale(1); opacity: 0.7; } 100% { transform: scale(2.6); opacity: 0; } }\n@keyframes mmp-spin { to { transform: rotate(360deg); } }\n@keyframes mmp-pop { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: none; } }\n@keyframes mmp-draw { to { stroke-dashoffset: 0; } }\n@keyframes mmp-ellipsis { 0% { content: \"\"; } 25% { content: \".\"; } 50% { content: \"..\"; } 75% { content: \"...\"; } }\n\n@media (max-width: 380px) {\n .mmp-root { padding: 10px; }\n .mmp-screen { padding: 6px 16px 18px 16px; }\n .mmp-qr { width: 204px; height: 204px; }\n}\n@media (prefers-reduced-motion: reduce) {\n .mmp-root, .mmp-root *, .mmp-root *::before, .mmp-root *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }\n .mmp-icon svg path { stroke-dashoffset: 0; }\n .mmp-loader-bar { width: 100%; opacity: 0.25; }\n}\n";
580
+
581
+ var HOST_ID = 'mmpay-full-modal';
582
+ var FONT_LINK_ID = 'mmpay-font-padauk';
583
+ var LOGO_URL = 'https://motephoe.com/images/mmqr.webp';
584
+ var QR_RENDER_SIZE = 480;
585
+ // Pinned builds of node-qrcode 1.5.1. The integrity hashes make the browser
586
+ // refuse a file that was altered on the CDN, which matters because the QR
587
+ // decides where the shopper's money goes.
588
+ var QR_LIBRARY_SOURCES = [
589
+ {
590
+ url: 'https://cdnjs.cloudflare.com/ajax/libs/qrcode/1.5.1/qrcode.min.js',
591
+ integrity: 'sha512-PEhlWBZBrQL7flpJPY8lXx8tIN7HWX912GzGhFTDqA3iWFrakVH3lVHomCoU9BhfKzgxfEk6EG2C3xej+9srOQ=='
592
+ },
593
+ {
594
+ url: 'https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.js',
595
+ integrity: 'sha512-hAz8pxm1kva4chZWPXQhGehvvlIj1Loka653sQXoYHNj4sgSukoR3ZKyHEOT7kFGVW17wUqD9CbKeviIJ2XFKQ=='
596
+ }
597
+ ];
598
+ var ICONS = {
599
+ close: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>',
600
+ clock: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><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"/><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"/></svg>',
601
+ download: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v11m0 0l-4-4m4 4l4-4M5 19h14"/></svg>',
602
+ success: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 13l4 4L19 7"/></svg>',
603
+ failed: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>',
604
+ expired: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 7v5l3 2M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
605
+ cancelled: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M5.6 5.6l12.8 12.8M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
606
+ warning: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 9v4m0 4h.01M10.3 4.3L2.6 17.5A2 2 0 004.3 20.5h15.4a2 2 0 001.7-3L13.7 4.3a2 2 0 00-3.4 0z"/></svg>'
607
+ };
608
+ function h(tag, attributes) {
609
+ var children = [];
610
+ for (var _i = 2; _i < arguments.length; _i++) {
611
+ children[_i - 2] = arguments[_i];
612
+ }
613
+ var element = document.createElement(tag);
614
+ if (attributes) {
615
+ Object.keys(attributes).forEach(function (name) { return element.setAttribute(name, attributes[name]); });
616
+ }
617
+ children.forEach(function (child) {
618
+ if (child === null || child === undefined || child === false)
619
+ return;
620
+ element.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
621
+ });
622
+ return element;
623
+ }
624
+ /** Static, trusted SVG markup only. Never pass merchant or server data here. */
625
+ function icon(name, className) {
626
+ var wrapper = h('span', className ? { class: className } : null);
627
+ wrapper.style.display = 'inline-flex';
628
+ wrapper.innerHTML = ICONS[name];
629
+ return wrapper;
630
+ }
631
+ function isQrLibrary(candidate) {
632
+ return !!candidate && typeof candidate.toCanvas === 'function';
633
+ }
634
+ var qrLibraryPromise = null;
635
+ function loadQrLibrary() {
636
+ if (qrLibraryPromise)
637
+ return qrLibraryPromise;
638
+ var pending = new Promise(function (resolve, reject) {
639
+ var globalScope = window;
640
+ if (isQrLibrary(globalScope.QRCode)) {
641
+ resolve(globalScope.QRCode);
642
+ return;
643
+ }
644
+ // The merchant page may have its own, different `QRCode` global. Put it
645
+ // back after loading ours instead of deleting it.
646
+ var hadGlobal = 'QRCode' in globalScope;
647
+ var previousGlobal = globalScope.QRCode;
648
+ var index = 0;
649
+ var loadNext = function () {
650
+ if (index >= QR_LIBRARY_SOURCES.length) {
651
+ reject(new Error('QR library could not be loaded.'));
652
+ return;
653
+ }
654
+ var source = QR_LIBRARY_SOURCES[index++];
655
+ var script = document.createElement('script');
656
+ script.src = source.url;
657
+ script.integrity = source.integrity;
658
+ script.crossOrigin = 'anonymous';
659
+ script.async = true;
660
+ script.onload = function () {
661
+ var library = globalScope.QRCode;
662
+ if (hadGlobal)
663
+ globalScope.QRCode = previousGlobal;
664
+ if (isQrLibrary(library)) {
665
+ resolve(library);
666
+ }
667
+ else {
668
+ loadNext();
669
+ }
670
+ };
671
+ script.onerror = function () {
672
+ if (script.parentNode)
673
+ script.parentNode.removeChild(script);
674
+ loadNext();
675
+ };
676
+ document.head.appendChild(script);
677
+ };
678
+ loadNext();
679
+ });
680
+ qrLibraryPromise = pending;
681
+ pending.catch(function () {
682
+ if (qrLibraryPromise === pending)
683
+ qrLibraryPromise = null;
684
+ });
685
+ return pending;
686
+ }
687
+ function resolveAccent(color) {
688
+ if (!color)
689
+ return null;
690
+ var value = color.trim();
691
+ var hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value);
692
+ if (hex) {
693
+ var digits = hex[1];
694
+ if (digits.length === 3)
695
+ digits = digits[0] + digits[0] + digits[1] + digits[1] + digits[2] + digits[2];
696
+ var red = parseInt(digits.substr(0, 2), 16);
697
+ var green = parseInt(digits.substr(2, 2), 16);
698
+ var blue = parseInt(digits.substr(4, 2), 16);
699
+ var brightness = (red * 299 + green * 587 + blue * 114) / 1000;
700
+ return { background: value, text: brightness > 165 ? '#000000' : '#ffffff' };
701
+ }
702
+ // Anything else must look like a plain CSS colour, so a configured value
703
+ // cannot smuggle extra CSS into the modal.
704
+ if (/^[a-z]{3,30}$/i.test(value) || /^(rgb|hsl)a?\([0-9.,%\s/]+\)$/i.test(value)) {
705
+ return { background: value, text: '#ffffff' };
706
+ }
707
+ return null;
708
+ }
709
+ var MMPayUI = /** @class */ (function () {
710
+ function MMPayUI(design, preferredLanguage) {
711
+ var _this = this;
712
+ this.handlers = null;
713
+ this.mode = 'closed';
714
+ this.host = null;
715
+ this.root = null;
716
+ this.card = null;
717
+ this.screen = null;
718
+ this.sheet = null;
719
+ this.closeButton = null;
720
+ this.frameBindings = [];
721
+ this.screenBindings = [];
722
+ this.sheetBindings = [];
723
+ this.payment = null;
724
+ this.paymentParts = null;
725
+ this.countdownText = null;
726
+ this.timerBadge = null;
727
+ this.waitingBadge = null;
728
+ this.waitingBinding = null;
729
+ this.qrCanvas = null;
730
+ this.previousBodyOverflow = null;
731
+ this.previousFocus = null;
732
+ this.onKeyDown = function (event) { return _this.handleKeyDown(event); };
733
+ this.design = design || {};
734
+ this.language = typeof window !== 'undefined' ? resolveInitialLanguage(preferredLanguage) : 'en';
735
+ }
736
+ MMPayUI.prototype.getLanguage = function () {
737
+ return this.language;
738
+ };
739
+ /**
740
+ * The language is state of the UI, not of a screen, so it survives every
741
+ * screen change and is applied to whatever is rendered next.
742
+ */
743
+ MMPayUI.prototype.setLanguage = function (language, remember) {
744
+ if (remember === void 0) { remember = true; }
745
+ var next = normalizeLanguage(language);
746
+ if (!next)
747
+ return;
748
+ this.language = next;
749
+ if (remember)
750
+ rememberLanguage(next);
751
+ this.applyLanguage();
752
+ };
753
+ MMPayUI.prototype.isOpen = function () {
754
+ return this.mode !== 'closed';
755
+ };
756
+ MMPayUI.prototype.showLoading = function (view, handlers) {
757
+ this.handlers = handlers;
758
+ this.mount();
759
+ this.mode = 'loading';
760
+ this.renderPaymentScreen(view);
761
+ this.setCloseVisible(false);
762
+ };
763
+ MMPayUI.prototype.showQr = function (view, handlers) {
764
+ this.handlers = handlers;
765
+ this.mount();
766
+ // Coming from the loading state of the same order, only the placeholders
767
+ // are filled in, so the card does not jump or flash.
768
+ if (this.mode !== 'loading' || !this.payment || this.payment.orderId !== view.orderId || !this.paymentParts) {
769
+ this.renderPaymentScreen(view);
770
+ }
771
+ this.mode = 'qr';
772
+ this.payment = view;
773
+ this.fillPayment(view);
774
+ this.setCloseVisible(true);
775
+ };
776
+ MMPayUI.prototype.showResult = function (status, view, handlers) {
777
+ this.handlers = handlers;
778
+ this.mount();
779
+ this.mode = 'result';
780
+ this.payment = null;
781
+ this.paymentParts = null;
782
+ this.removeSheet();
783
+ var tone = status === 'SUCCESS' ? 'success' : (status === 'CANCELLED' ? 'warn' : 'danger');
784
+ var iconName = status === 'SUCCESS' ? 'success' : (status === 'CANCELLED' ? 'cancelled' : (status === 'EXPIRED' ? 'expired' : 'failed'));
785
+ var bindings = [];
786
+ var badge = icon(iconName, 'mmp-icon');
787
+ badge.setAttribute('data-tone', tone);
788
+ badge.style.display = '';
789
+ var screen = h('div', { class: 'mmp-screen', 'aria-live': 'polite' }, badge, h('h2', { class: 'mmp-title', id: 'mmp-dialog-title' }, this.text(bindings, 'status.' + status)), h('div', { class: 'mmp-pill' }, 'ID: ' + view.orderId), h('p', { class: 'mmp-text' }, this.text(bindings, view.messageKey)), view.ref ? h('p', { class: 'mmp-text' }, this.text(bindings, 'message.ref', { ref: view.ref })) : null, view.detail ? h('p', { class: 'mmp-detail-text' }, view.detail) : null, h('div', { class: 'mmp-actions' }, h('button', { class: 'mmp-btn', type: 'button', 'data-action': 'dismiss' }, this.text(bindings, 'action.close'))));
790
+ this.swapScreen(screen, bindings);
791
+ this.setCloseVisible(true);
792
+ this.focusFirst(screen);
793
+ };
794
+ MMPayUI.prototype.showCancelConfirmation = function () {
795
+ if (!this.card || this.sheet || this.mode !== 'qr')
796
+ return;
797
+ var bindings = [];
798
+ var badge = icon('warning', 'mmp-icon');
799
+ badge.setAttribute('data-tone', 'warn');
800
+ badge.style.display = '';
801
+ var sheet = h('div', { class: 'mmp-sheet', role: 'alertdialog', 'aria-modal': 'true' }, badge, h('h2', { class: 'mmp-title' }, this.text(bindings, 'cancel.title')), h('p', { class: 'mmp-text' }, this.text(bindings, 'cancel.body')), h('div', { class: 'mmp-actions' }, h('button', { class: 'mmp-btn mmp-btn-danger', type: 'button', 'data-action': 'cancel-confirm' }, this.text(bindings, 'cancel.confirm')), h('button', { class: 'mmp-btn mmp-btn-secondary', type: 'button', 'data-action': 'cancel-back' }, this.text(bindings, 'cancel.back'))));
802
+ this.sheet = sheet;
803
+ this.sheetBindings = bindings;
804
+ this.card.appendChild(sheet);
805
+ this.setCloseVisible(false);
806
+ var back = sheet.querySelector('[data-action="cancel-back"]');
807
+ if (back)
808
+ back.focus();
809
+ };
810
+ MMPayUI.prototype.hideCancelConfirmation = function () {
811
+ this.removeSheet();
812
+ };
813
+ MMPayUI.prototype.setCancelBusy = function (busy) {
814
+ if (!this.sheet)
815
+ return;
816
+ var buttons = this.sheet.querySelectorAll('button');
817
+ for (var i = 0; i < buttons.length; i++) {
818
+ buttons[i].disabled = busy;
819
+ if (buttons[i].getAttribute('data-action') === 'cancel-confirm') {
820
+ buttons[i].setAttribute('data-busy', busy ? 'true' : 'false');
821
+ }
822
+ }
823
+ };
824
+ MMPayUI.prototype.setCountdown = function (remainingSeconds) {
825
+ if (!this.countdownText || !this.timerBadge)
826
+ return;
827
+ var minutes = Math.floor(remainingSeconds / 60);
828
+ var seconds = remainingSeconds % 60;
829
+ this.countdownText.textContent = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
830
+ this.timerBadge.setAttribute('data-urgent', remainingSeconds <= 60 ? 'true' : 'false');
831
+ };
832
+ MMPayUI.prototype.setConnectionState = function (state) {
833
+ if (!this.waitingBadge || !this.waitingBinding)
834
+ return;
835
+ if (this.waitingBadge.getAttribute('data-state') === state)
836
+ return;
837
+ this.waitingBadge.setAttribute('data-state', state);
838
+ this.waitingBinding.key = state === 'live' ? 'qr.waiting' : 'qr.reconnecting';
839
+ this.applyBinding(this.waitingBinding);
840
+ };
841
+ MMPayUI.prototype.close = function () {
842
+ if (this.host && this.host.parentNode) {
843
+ this.host.parentNode.removeChild(this.host);
844
+ }
845
+ if (this.mode !== 'closed') {
846
+ document.removeEventListener('keydown', this.onKeyDown, true);
847
+ if (this.previousBodyOverflow !== null) {
848
+ document.body.style.overflow = this.previousBodyOverflow;
849
+ this.previousBodyOverflow = null;
850
+ }
851
+ var previous = this.previousFocus;
852
+ if (previous && typeof previous.focus === 'function' && document.contains(previous)) {
853
+ try {
854
+ previous.focus();
855
+ }
856
+ catch (e) { }
857
+ }
858
+ }
859
+ this.mode = 'closed';
860
+ this.host = this.root = this.card = null;
861
+ this.screen = this.sheet = null;
862
+ this.closeButton = null;
863
+ this.payment = null;
864
+ this.paymentParts = null;
865
+ this.countdownText = this.timerBadge = this.waitingBadge = null;
866
+ this.waitingBinding = null;
867
+ this.qrCanvas = null;
868
+ this.previousFocus = null;
869
+ this.frameBindings = [];
870
+ this.screenBindings = [];
871
+ this.sheetBindings = [];
872
+ this.handlers = null;
873
+ };
874
+ MMPayUI.prototype.mount = function () {
875
+ var _this = this;
876
+ if (this.host && this.host.parentNode)
877
+ return;
878
+ var stale = document.getElementById(HOST_ID);
879
+ if (stale && stale.parentNode)
880
+ stale.parentNode.removeChild(stale);
881
+ var host = document.createElement('div');
882
+ host.id = HOST_ID;
883
+ host.style.cssText = 'all: initial; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 2147483000; display: block;';
884
+ var container = typeof host.attachShadow === 'function' ? host.attachShadow({ mode: 'open' }) : host;
885
+ var style = document.createElement('style');
886
+ style.textContent = MMPAY_STYLES;
887
+ container.appendChild(style);
888
+ var mode = this.design.mode || 'light';
889
+ var prefersDark = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-color-scheme: dark)').matches;
890
+ var isDark = mode === 'auto' ? prefersDark : mode.indexOf('dark') !== -1;
891
+ var root = h('div', { class: 'mmp-root' + (isDark ? ' mmp-dark' : '') + (mode.indexOf('translucent') !== -1 ? ' mmp-translucent' : '') });
892
+ var accent = resolveAccent(this.design.color);
893
+ if (accent) {
894
+ root.style.setProperty('--mmp-accent', accent.background);
895
+ root.style.setProperty('--mmp-accent-text', accent.text);
896
+ }
897
+ this.frameBindings = [];
898
+ var languageGroup = h('div', { class: 'mmp-lang', role: 'group' }, h('button', { type: 'button', 'data-action': 'language', 'data-language': 'en', lang: 'en' }, 'EN'), h('button', { type: 'button', 'data-action': 'language', 'data-language': 'mm', lang: 'my' }, 'မြန်မာ'));
899
+ this.frameBindings.push({ node: languageGroup, key: 'action.language', attribute: 'aria-label' });
900
+ var closeButton = h('button', { class: 'mmp-close', type: 'button', 'data-action': 'close' });
901
+ closeButton.appendChild(icon('close'));
902
+ this.frameBindings.push({ node: closeButton, key: 'action.close', attribute: 'aria-label' });
903
+ var card = h('div', { class: 'mmp-card', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'mmp-dialog-title', tabindex: '-1' }, h('div', { class: 'mmp-topbar' }, languageGroup, closeButton));
904
+ root.appendChild(card);
905
+ container.appendChild(root);
906
+ root.addEventListener('click', function (event) { return _this.handleClick(event); });
907
+ this.host = host;
908
+ this.root = root;
909
+ this.card = card;
910
+ this.closeButton = closeButton;
911
+ this.screen = null;
912
+ this.previousFocus = document.activeElement;
913
+ this.previousBodyOverflow = document.body.style.overflow;
914
+ document.body.style.overflow = 'hidden';
915
+ document.body.appendChild(host);
916
+ document.addEventListener('keydown', this.onKeyDown, true);
917
+ this.ensureFont();
918
+ this.applyLanguage();
919
+ card.focus();
920
+ };
921
+ MMPayUI.prototype.renderPaymentScreen = function (view) {
922
+ this.removeSheet();
923
+ var bindings = [];
924
+ var logo = h('img', { src: LOGO_URL, alt: 'MMQR', decoding: 'async' });
925
+ logo.onerror = function () {
926
+ logo.style.display = 'none';
927
+ };
928
+ var amount = h('div', { class: 'mmp-amount' });
929
+ var qrBox = h('div', { class: 'mmp-qr' });
930
+ var caption = h('div', { class: 'mmp-caption', 'aria-live': 'polite' });
931
+ var statusRow = h('div', { class: 'mmp-status-row' });
932
+ var refValue = h('strong');
933
+ var download = h('button', { class: 'mmp-btn', type: 'button', 'data-action': 'download' }, icon('download'), this.text(bindings, 'qr.download'));
934
+ download.disabled = true;
935
+ var screen = h('div', { class: 'mmp-screen' }, h('div', { class: 'mmp-brand' }, logo), h('div', { class: 'mmp-merchant', id: 'mmp-dialog-title' }, view.merchantName), amount, qrBox, caption, statusRow, h('div', { class: 'mmp-details' }, h('div', { class: 'mmp-detail' }, h('span', null, this.text(bindings, 'qr.orderId')), h('strong', null, view.orderId)), h('div', { class: 'mmp-detail' }, h('span', null, this.text(bindings, 'qr.refNo')), refValue)), download, h('div', { class: 'mmp-powered' }, this.text(bindings, 'qr.poweredBy')));
936
+ this.payment = view;
937
+ this.paymentParts = { amount: amount, qrBox: qrBox, caption: caption, statusRow: statusRow, refValue: refValue, download: download };
938
+ this.countdownText = this.timerBadge = this.waitingBadge = null;
939
+ this.waitingBinding = null;
940
+ this.qrCanvas = null;
941
+ this.swapScreen(screen, bindings);
942
+ this.setAmount(view.amount);
943
+ this.showQrPlaceholder();
944
+ refValue.appendChild(h('span', { class: 'mmp-skeleton', style: 'width: 110px;' }));
945
+ caption.appendChild(h('div', { class: 'mmp-caption-title' }, this.text(this.screenBindings, 'loading.title')));
946
+ caption.appendChild(h('div', { class: 'mmp-caption-sub' }, this.text(this.screenBindings, 'loading.subtitle')));
947
+ statusRow.style.visibility = 'hidden';
948
+ statusRow.appendChild(h('span', { class: 'mmp-timer' }, '00:00'));
949
+ };
950
+ MMPayUI.prototype.fillPayment = function (view) {
951
+ var parts = this.paymentParts;
952
+ if (!parts)
953
+ return;
954
+ this.setAmount(view.amount);
955
+ parts.refValue.textContent = view.vendorQrRefId || '-';
956
+ this.clear(parts.caption);
957
+ parts.caption.appendChild(h('div', { class: 'mmp-hint' }, this.text(this.screenBindings, 'qr.scanHint')));
958
+ this.clear(parts.statusRow);
959
+ parts.statusRow.style.visibility = '';
960
+ var waitingText = this.text(this.screenBindings, 'qr.waiting');
961
+ this.waitingBinding = this.screenBindings[this.screenBindings.length - 1];
962
+ this.waitingBadge = h('span', { class: 'mmp-waiting', 'data-state': 'live', role: 'status' }, h('span', { class: 'mmp-dot' }), waitingText);
963
+ this.countdownText = h('span', null, '--:--');
964
+ this.timerBadge = h('span', { class: 'mmp-timer', 'data-urgent': 'false' }, icon('clock'), this.countdownText);
965
+ parts.statusRow.appendChild(this.waitingBadge);
966
+ parts.statusRow.appendChild(this.timerBadge);
967
+ this.renderQr();
968
+ };
969
+ MMPayUI.prototype.setAmount = function (amount) {
970
+ var parts = this.paymentParts;
971
+ if (!parts)
972
+ return;
973
+ this.clear(parts.amount);
974
+ if (typeof amount === 'number' && isFinite(amount)) {
975
+ var formatted = amount.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
976
+ parts.amount.appendChild(h('span', { class: 'mmp-amount-value' }, formatted));
977
+ parts.amount.appendChild(h('span', { class: 'mmp-amount-currency' }, 'MMK'));
978
+ }
979
+ else {
980
+ parts.amount.appendChild(h('span', { class: 'mmp-skeleton', style: 'width: 150px; height: 26px; border-radius: 9px;' }));
981
+ }
982
+ };
983
+ MMPayUI.prototype.showQrPlaceholder = function () {
984
+ var parts = this.paymentParts;
985
+ if (!parts)
986
+ return;
987
+ this.clear(parts.qrBox);
988
+ var logo = h('img', { class: 'mmp-loader-logo', src: MMPAY_LOGO_DATA_URI, alt: 'MyanMyanPay' });
989
+ parts.qrBox.appendChild(h('div', { class: 'mmp-qr-placeholder' }, logo, h('div', { class: 'mmp-loader-track', 'aria-hidden': 'true' }, h('div', { class: 'mmp-loader-bar' }))));
990
+ };
991
+ MMPayUI.prototype.renderQr = function () {
992
+ var _this = this;
993
+ var parts = this.paymentParts;
994
+ var view = this.payment;
995
+ if (!parts || !view || !view.qr)
996
+ return;
997
+ var qrData = view.qr;
998
+ var isCurrent = function () { return _this.paymentParts === parts && _this.payment === view; };
999
+ var fail = function () {
1000
+ if (!isCurrent())
1001
+ return;
1002
+ _this.qrCanvas = null;
1003
+ parts.download.disabled = true;
1004
+ _this.clear(parts.qrBox);
1005
+ // The bindings of an earlier failed attempt point at removed nodes.
1006
+ _this.screenBindings = _this.screenBindings.filter(function (binding) { return binding.key !== 'qr.loadFailed' && binding.key !== 'qr.retry'; });
1007
+ parts.qrBox.appendChild(h('div', { class: 'mmp-qr-error', role: 'alert' }, _this.text(_this.screenBindings, 'qr.loadFailed'), h('button', { type: 'button', 'data-action': 'retry-qr' }, _this.text(_this.screenBindings, 'qr.retry'))));
1008
+ };
1009
+ loadQrLibrary().then(function (library) {
1010
+ if (!isCurrent())
1011
+ return;
1012
+ var canvas = document.createElement('canvas');
1013
+ library.toCanvas(canvas, qrData, {
1014
+ width: QR_RENDER_SIZE,
1015
+ margin: 1,
1016
+ color: { dark: '#000000', light: '#ffffff' },
1017
+ errorCorrectionLevel: 'M'
1018
+ }, function (error) {
1019
+ if (error) {
1020
+ fail();
1021
+ return;
1022
+ }
1023
+ if (!isCurrent())
1024
+ return;
1025
+ canvas.setAttribute('role', 'img');
1026
+ canvas.setAttribute('aria-label', 'MMQR');
1027
+ _this.clear(parts.qrBox);
1028
+ parts.qrBox.appendChild(canvas);
1029
+ _this.qrCanvas = canvas;
1030
+ parts.download.disabled = false;
1031
+ });
1032
+ }).catch(fail);
1033
+ };
1034
+ MMPayUI.prototype.downloadQr = function () {
1035
+ if (!this.qrCanvas || !this.payment)
1036
+ return;
1037
+ try {
1038
+ var link = document.createElement('a');
1039
+ link.href = this.qrCanvas.toDataURL('image/png');
1040
+ link.download = 'MMPay-QR-' + this.payment.orderId.replace(/[^a-zA-Z0-9_-]/g, '') + '.png';
1041
+ document.body.appendChild(link);
1042
+ link.click();
1043
+ document.body.removeChild(link);
1044
+ }
1045
+ catch (e) { }
1046
+ };
1047
+ MMPayUI.prototype.handleClick = function (event) {
1048
+ var target = event.target;
1049
+ while (target && target !== this.root && !(target.getAttribute && target.getAttribute('data-action'))) {
1050
+ target = target.parentNode;
1051
+ }
1052
+ if (!target || target === this.root || !this.handlers)
1053
+ return;
1054
+ if (target.disabled)
1055
+ return;
1056
+ var action = target.getAttribute('data-action');
1057
+ if (action === 'language') {
1058
+ this.setLanguage(target.getAttribute('data-language') || 'en');
1059
+ }
1060
+ else if (action === 'close') {
1061
+ this.requestClose();
1062
+ }
1063
+ else if (action === 'dismiss') {
1064
+ this.handlers.onDismiss();
1065
+ }
1066
+ else if (action === 'cancel-confirm') {
1067
+ this.handlers.onCancelConfirm();
1068
+ }
1069
+ else if (action === 'cancel-back') {
1070
+ this.hideCancelConfirmation();
1071
+ }
1072
+ else if (action === 'download') {
1073
+ this.downloadQr();
1074
+ }
1075
+ else if (action === 'retry-qr') {
1076
+ this.showQrPlaceholder();
1077
+ this.renderQr();
1078
+ }
1079
+ };
1080
+ MMPayUI.prototype.requestClose = function () {
1081
+ if (!this.handlers)
1082
+ return;
1083
+ if (this.mode === 'result') {
1084
+ this.handlers.onDismiss();
1085
+ }
1086
+ else if (this.mode === 'qr') {
1087
+ this.handlers.onCloseRequest();
1088
+ }
1089
+ };
1090
+ MMPayUI.prototype.handleKeyDown = function (event) {
1091
+ if (this.mode === 'closed')
1092
+ return;
1093
+ if (event.key === 'Escape' || event.key === 'Esc') {
1094
+ event.stopPropagation();
1095
+ if (this.sheet) {
1096
+ var confirm_1 = this.sheet.querySelector('[data-action="cancel-confirm"]');
1097
+ if (!confirm_1 || !confirm_1.disabled)
1098
+ this.hideCancelConfirmation();
1099
+ }
1100
+ else {
1101
+ this.requestClose();
1102
+ }
1103
+ return;
1104
+ }
1105
+ if (event.key !== 'Tab' || !this.card)
1106
+ return;
1107
+ // Keep keyboard focus inside the modal while it is open.
1108
+ var scope = this.sheet || this.card;
1109
+ var candidates = scope.querySelectorAll('button');
1110
+ var focusable = [];
1111
+ for (var i = 0; i < candidates.length; i++) {
1112
+ var button = candidates[i];
1113
+ if (!button.disabled && !button.hidden)
1114
+ focusable.push(button);
1115
+ }
1116
+ if (focusable.length === 0) {
1117
+ event.preventDefault();
1118
+ return;
1119
+ }
1120
+ var shadow = this.host && this.host.shadowRoot;
1121
+ var active = (shadow ? shadow.activeElement : document.activeElement);
1122
+ var index = active ? focusable.indexOf(active) : -1;
1123
+ event.preventDefault();
1124
+ var nextIndex = event.shiftKey
1125
+ ? (index <= 0 ? focusable.length - 1 : index - 1)
1126
+ : (index === -1 || index === focusable.length - 1 ? 0 : index + 1);
1127
+ focusable[nextIndex].focus();
1128
+ };
1129
+ MMPayUI.prototype.swapScreen = function (screen, bindings) {
1130
+ if (!this.card)
1131
+ return;
1132
+ if (this.screen && this.screen.parentNode)
1133
+ this.screen.parentNode.removeChild(this.screen);
1134
+ this.card.appendChild(screen);
1135
+ this.screen = screen;
1136
+ this.screenBindings = bindings;
1137
+ };
1138
+ MMPayUI.prototype.removeSheet = function () {
1139
+ if (this.sheet && this.sheet.parentNode)
1140
+ this.sheet.parentNode.removeChild(this.sheet);
1141
+ var hadSheet = !!this.sheet;
1142
+ this.sheet = null;
1143
+ this.sheetBindings = [];
1144
+ if (hadSheet && this.card && this.mode !== 'closed') {
1145
+ this.setCloseVisible(true);
1146
+ this.card.focus();
1147
+ }
1148
+ };
1149
+ MMPayUI.prototype.setCloseVisible = function (visible) {
1150
+ if (this.closeButton)
1151
+ this.closeButton.hidden = !visible;
1152
+ };
1153
+ MMPayUI.prototype.focusFirst = function (scope) {
1154
+ var button = scope.querySelector('button');
1155
+ if (button)
1156
+ button.focus();
1157
+ };
1158
+ MMPayUI.prototype.clear = function (element) {
1159
+ while (element.firstChild)
1160
+ element.removeChild(element.firstChild);
1161
+ };
1162
+ /** Creates a text node holder that is re-translated whenever the language changes. */
1163
+ MMPayUI.prototype.text = function (bindings, key, params) {
1164
+ var node = h('span');
1165
+ var binding = { node: node, key: key, params: params };
1166
+ bindings.push(binding);
1167
+ this.applyBinding(binding);
1168
+ return node;
1169
+ };
1170
+ MMPayUI.prototype.applyBinding = function (binding) {
1171
+ var value = translate(this.language, binding.key, binding.params);
1172
+ if (binding.attribute) {
1173
+ binding.node.setAttribute(binding.attribute, value);
1174
+ }
1175
+ else {
1176
+ binding.node.textContent = value;
1177
+ }
1178
+ };
1179
+ MMPayUI.prototype.applyLanguage = function () {
1180
+ var _this = this;
1181
+ if (!this.root)
1182
+ return;
1183
+ this.root.setAttribute('data-lang', this.language);
1184
+ this.root.setAttribute('lang', this.language === 'mm' ? 'my' : 'en');
1185
+ var toggles = this.root.querySelectorAll('[data-action="language"]');
1186
+ for (var i = 0; i < toggles.length; i++) {
1187
+ toggles[i].setAttribute('aria-pressed', toggles[i].getAttribute('data-language') === this.language ? 'true' : 'false');
1188
+ }
1189
+ this.frameBindings.forEach(function (binding) { return _this.applyBinding(binding); });
1190
+ this.screenBindings.forEach(function (binding) { return _this.applyBinding(binding); });
1191
+ this.sheetBindings.forEach(function (binding) { return _this.applyBinding(binding); });
1192
+ };
1193
+ /** Web fonts must be declared in the page itself to be usable inside a shadow root. */
1194
+ MMPayUI.prototype.ensureFont = function () {
1195
+ if (document.getElementById(FONT_LINK_ID))
1196
+ return;
1197
+ var link = document.createElement('link');
1198
+ link.id = FONT_LINK_ID;
1199
+ link.rel = 'stylesheet';
1200
+ link.href = 'https://fonts.googleapis.com/css2?family=Padauk:wght@400;700&display=swap';
1201
+ document.head.appendChild(link);
1202
+ };
1203
+ return MMPayUI;
1204
+ }());
1205
+
1206
+ var MIN_LOADING_MS = 400;
1207
+ var MMPaySDK = /** @class */ (function () {
1208
+ function MMPaySDK(publishableKey, options) {
1209
+ if (options === void 0) { options = {}; }
1210
+ var _a, _b;
1211
+ this.TIMEOUT_SECONDS = 900;
1212
+ this.CACHE_KEY = 'mmpay_pending_tx';
1213
+ this.onCompleteCallback = null;
1214
+ this.statusWatcher = null;
1215
+ this.countdownIntervalId = undefined;
1216
+ this.pendingApiResponse = null;
1217
+ this.pendingPaymentPayload = null;
1218
+ this.flowId = 0;
1219
+ this.settled = true;
1220
+ this.cancelling = false;
1221
+ this.creating = false;
1222
+ if (!publishableKey) {
1223
+ throw new Error("A Publishable Key is required to initialize [MMPaySDK].");
1224
+ }
1225
+ if (publishableKey.includes('pk_test')) {
1226
+ this.environment = 'sandbox';
1227
+ }
1228
+ else if (publishableKey.includes('pk_live')) {
1229
+ this.environment = 'production';
1230
+ }
1231
+ else {
1232
+ this.environment = options.environment || 'production';
1233
+ }
1234
+ var baseUrl = options.baseUrl || 'https://bbapi.myanmyanpay.com';
1235
+ this.merchantName = options.merchantName || 'MyanMyanPay';
1236
+ this.POLL_INTERVAL_MS = options.pollInterval || 5000;
1237
+ this.transport = options.transport || 'auto';
1238
+ if (typeof options.timeoutSeconds === 'number' && isFinite(options.timeoutSeconds)) {
1239
+ // The server expires an unpaid order after 15 minutes, so the countdown
1240
+ // can be shortened but not extended.
1241
+ this.TIMEOUT_SECONDS = Math.min(900, Math.max(60, Math.floor(options.timeoutSeconds)));
1242
+ }
1243
+ this.api = new MMPayAPI(baseUrl, this.environment, publishableKey);
1244
+ this.ui = new MMPayUI({
1245
+ mode: ((_a = options.design) === null || _a === void 0 ? void 0 : _a.mode) || 'light',
1246
+ color: (_b = options.design) === null || _b === void 0 ? void 0 : _b.color
1247
+ }, options.language);
1248
+ if (typeof window !== 'undefined') {
1249
+ this._checkAndAutoResume();
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Switches the modal language. Works at any time, including while a payment
1254
+ * is on screen, and is remembered for the shopper's next visit.
1255
+ */
1256
+ MMPaySDK.prototype.setLanguage = function (language) {
1257
+ this.ui.setLanguage(language);
1258
+ };
1259
+ MMPaySDK.prototype.getLanguage = function () {
1260
+ return this.ui.getLanguage();
1261
+ };
1262
+ MMPaySDK.prototype.showPaymentModal = function (params, onComplete) {
1263
+ return __awaiter(this, void 0, void 0, function () {
1264
+ var flowId, startTime, tokenNonce, tokenResponse, apiResponse, actualRefId, expireAt, error_1;
1265
+ return __generator(this, function (_a) {
1266
+ switch (_a.label) {
1267
+ case 0:
1268
+ // A second call while an order is still being created (double click) must
1269
+ // not create a second order.
1270
+ if (this.creating) {
1271
+ this.onCompleteCallback = onComplete;
1272
+ return [2 /*return*/];
1273
+ }
1274
+ flowId = this._beginFlow(onComplete);
1275
+ this.creating = true;
1276
+ this.ui.showLoading({
1277
+ merchantName: this.merchantName,
1278
+ orderId: params.orderId,
1279
+ amount: params.amount
1280
+ }, this._getUiHandlers());
1281
+ _a.label = 1;
1282
+ case 1:
1283
+ _a.trys.push([1, 6, 7, 8]);
1284
+ startTime = Date.now();
1285
+ return [4 /*yield*/, this._showUnfinishedOrder(flowId, startTime)];
1286
+ case 2:
1287
+ if (_a.sent())
1288
+ return [2 /*return*/];
1289
+ if (flowId !== this.flowId)
1290
+ return [2 /*return*/];
1291
+ tokenNonce = new Date().getTime().toString() + '_create';
1292
+ return [4 /*yield*/, this.api.createToken({
1293
+ amount: params.amount,
1294
+ orderId: params.orderId,
1295
+ nonce: tokenNonce
1296
+ })];
1297
+ case 3:
1298
+ tokenResponse = _a.sent();
1299
+ if (flowId !== this.flowId)
1300
+ return [2 /*return*/];
1301
+ this.api.setToken(tokenResponse.token);
1302
+ return [4 /*yield*/, this.api.createPayment(__assign(__assign({}, params), { nonce: tokenNonce }))];
1303
+ case 4:
1304
+ apiResponse = _a.sent();
1305
+ return [4 /*yield*/, this._holdLoading(startTime)];
1306
+ case 5:
1307
+ _a.sent();
1308
+ if (flowId !== this.flowId)
1309
+ return [2 /*return*/];
1310
+ actualRefId = (apiResponse === null || apiResponse === void 0 ? void 0 : apiResponse.vendorQrRefId) || (apiResponse === null || apiResponse === void 0 ? void 0 : apiResponse.transactionRefId);
1311
+ if (apiResponse && apiResponse.qr && actualRefId) {
1312
+ apiResponse.vendorQrRefId = actualRefId;
1313
+ this.pendingPaymentPayload = __assign(__assign({}, params), { tokenNonce: tokenNonce });
1314
+ this.pendingApiResponse = apiResponse;
1315
+ expireAt = Date.now() + (this.TIMEOUT_SECONDS * 1000);
1316
+ this._writeCache({
1317
+ payload: { amount: apiResponse.amount, orderId: params.orderId, nonce: tokenNonce + '_poll' },
1318
+ apiResponse: apiResponse,
1319
+ expireAt: expireAt,
1320
+ token: this.api.getToken(),
1321
+ environment: this.environment
1322
+ });
1323
+ this._showQr(apiResponse, params.orderId);
1324
+ this._startStatusWatch({ orderId: params.orderId, nonce: tokenNonce + '_poll' });
1325
+ this._startCountdown(params.orderId, expireAt);
1326
+ this._triggerEvent({
1327
+ created: true,
1328
+ orderId: params.orderId,
1329
+ vendorQrRefId: actualRefId,
1330
+ amount: params.amount
1331
+ });
1332
+ }
1333
+ else {
1334
+ throw new Error("Invalid API Response: Missing QR Data.");
1335
+ }
1336
+ return [3 /*break*/, 8];
1337
+ case 6:
1338
+ error_1 = _a.sent();
1339
+ if (flowId !== this.flowId)
1340
+ return [2 /*return*/];
1341
+ this._failFlow(params.orderId, error_1, params.amount);
1342
+ return [3 /*break*/, 8];
1343
+ case 7:
1344
+ if (flowId === this.flowId)
1345
+ this.creating = false;
1346
+ return [7 /*endfinally*/];
1347
+ case 8: return [2 /*return*/];
1348
+ }
1349
+ });
1350
+ });
1351
+ };
1352
+ /**
1353
+ * Spam guard: while the shopper has an order that is not paid, cancelled or
1354
+ * expired, a new one is not created. The unfinished order is shown again
1355
+ * instead. The server is asked first, so an order that finished in the
1356
+ * meantime does not block a new one.
1357
+ */
1358
+ MMPaySDK.prototype._showUnfinishedOrder = function (flowId, startTime) {
1359
+ return __awaiter(this, void 0, void 0, function () {
1360
+ var cached, response;
1361
+ return __generator(this, function (_a) {
1362
+ switch (_a.label) {
1363
+ case 0:
1364
+ cached = this._readCache();
1365
+ if (!cached)
1366
+ return [2 /*return*/, false];
1367
+ if (cached.environment !== this.environment || Date.now() >= cached.expireAt) {
1368
+ this._clearCache();
1369
+ return [2 /*return*/, false];
1370
+ }
1371
+ this.api.setToken(cached.token);
1372
+ _a.label = 1;
1373
+ case 1:
1374
+ _a.trys.push([1, 4, , 5]);
1375
+ return [4 /*yield*/, this.api.pollPayment({ orderId: cached.payload.orderId, nonce: cached.payload.nonce })];
1376
+ case 2:
1377
+ response = _a.sent();
1378
+ return [4 /*yield*/, this._holdLoading(startTime)];
1379
+ case 3:
1380
+ _a.sent();
1381
+ if (flowId !== this.flowId)
1382
+ return [2 /*return*/, true];
1383
+ if ((response.status || '').toUpperCase() === 'PENDING') {
1384
+ this._resumePaymentState(cached.apiResponse, cached.payload, cached.expireAt);
1385
+ return [2 /*return*/, true];
1386
+ }
1387
+ return [3 /*break*/, 5];
1388
+ case 4:
1389
+ _a.sent();
1390
+ if (flowId !== this.flowId)
1391
+ return [2 /*return*/, true];
1392
+ return [3 /*break*/, 5];
1393
+ case 5:
1394
+ this.api.setToken(null);
1395
+ this._clearCache();
1396
+ return [2 /*return*/, false];
1397
+ }
1398
+ });
1399
+ });
1400
+ };
1401
+ MMPaySDK.prototype.pay = function (orderId, onComplete) {
1402
+ return __awaiter(this, void 0, void 0, function () {
1403
+ var flowId, cached, showPayload, expireAt, startTime, tokenNonce, tokenResponse, apiResponse, status_1, actualRefId, terminalStatus, mappedPaymentResponse, mappedPaymentPayload, error_3;
1404
+ var _a;
1405
+ return __generator(this, function (_b) {
1406
+ switch (_b.label) {
1407
+ case 0:
1408
+ flowId = this._beginFlow(onComplete);
1409
+ cached = this._readCache();
1410
+ if (cached) {
1411
+ if (cached.environment === this.environment && Date.now() < cached.expireAt && ((_a = cached.payload) === null || _a === void 0 ? void 0 : _a.orderId) === orderId) {
1412
+ this.api.setToken(cached.token);
1413
+ this._resumePaymentState(cached.apiResponse, cached.payload, cached.expireAt);
1414
+ return [2 /*return*/];
1415
+ }
1416
+ this._clearCache();
1417
+ }
1418
+ this.ui.showLoading({ merchantName: this.merchantName, orderId: orderId }, this._getUiHandlers());
1419
+ showPayload = { orderId: orderId, nonce: new Date().getTime().toString() + '_show' };
1420
+ expireAt = Date.now() + (this.TIMEOUT_SECONDS * 1000);
1421
+ _b.label = 1;
1422
+ case 1:
1423
+ _b.trys.push([1, 5, , 6]);
1424
+ startTime = Date.now();
1425
+ tokenNonce = new Date().getTime().toString() + '_read';
1426
+ return [4 /*yield*/, this.api.createToken({ orderId: orderId, nonce: tokenNonce })];
1427
+ case 2:
1428
+ tokenResponse = _b.sent();
1429
+ if (flowId !== this.flowId)
1430
+ return [2 /*return*/];
1431
+ this.api.setToken(tokenResponse.token);
1432
+ return [4 /*yield*/, this.api.showPayment(showPayload)];
1433
+ case 3:
1434
+ apiResponse = _b.sent();
1435
+ return [4 /*yield*/, this._holdLoading(startTime)];
1436
+ case 4:
1437
+ _b.sent();
1438
+ if (flowId !== this.flowId)
1439
+ return [2 /*return*/];
1440
+ if (apiResponse) {
1441
+ status_1 = (apiResponse.status || '').toUpperCase();
1442
+ actualRefId = apiResponse.vendorQrRefId || apiResponse.transactionRefId;
1443
+ if (status_1 !== 'PENDING') {
1444
+ terminalStatus = status_1 === 'SUCCESS' || status_1 === 'CANCELLED' || status_1 === 'EXPIRED' ? status_1 : 'FAILED';
1445
+ this._finish(terminalStatus, {
1446
+ orderId: apiResponse.orderId || orderId,
1447
+ amount: apiResponse.amount,
1448
+ vendorQrRefId: actualRefId
1449
+ }, {
1450
+ // Kept as before: a non-pending status the modal has no screen for
1451
+ // (REFUNDED) shows the failed screen without reporting `failed`.
1452
+ failed: status_1 === 'FAILED'
1453
+ });
1454
+ return [2 /*return*/];
1455
+ }
1456
+ if (apiResponse.qr && actualRefId) {
1457
+ mappedPaymentResponse = __assign(__assign({}, apiResponse), { vendorQrRefId: actualRefId });
1458
+ mappedPaymentPayload = { amount: apiResponse.amount, orderId: apiResponse.orderId, nonce: showPayload.nonce };
1459
+ this._writeCache({
1460
+ payload: mappedPaymentPayload,
1461
+ apiResponse: mappedPaymentResponse,
1462
+ expireAt: expireAt,
1463
+ token: this.api.getToken(),
1464
+ environment: this.environment
1465
+ });
1466
+ this._resumePaymentState(mappedPaymentResponse, mappedPaymentPayload, expireAt);
1467
+ return [2 /*return*/];
1468
+ }
1469
+ }
1470
+ throw new Error("Invalid API Response: Missing QR Data.");
1471
+ case 5:
1472
+ error_3 = _b.sent();
1473
+ if (flowId !== this.flowId)
1474
+ return [2 /*return*/];
1475
+ this._failFlow(orderId || 'N/A', error_3);
1476
+ return [3 /*break*/, 6];
1477
+ case 6: return [2 /*return*/];
1478
+ }
1479
+ });
1480
+ });
1481
+ };
1482
+ /**
1483
+ * Starts a fresh flow. Anything still running from an earlier call is
1484
+ * stopped, and its late responses are ignored through `flowId`.
1485
+ */
1486
+ MMPaySDK.prototype._beginFlow = function (onComplete) {
1487
+ this._stopTimers();
1488
+ this.onCompleteCallback = onComplete;
1489
+ this.pendingApiResponse = null;
1490
+ this.pendingPaymentPayload = null;
1491
+ this.settled = false;
1492
+ this.cancelling = false;
1493
+ this.creating = false;
1494
+ return ++this.flowId;
1495
+ };
1496
+ /** Keeps the loading state up briefly so a fast response does not flicker. */
1497
+ MMPaySDK.prototype._holdLoading = function (startTime) {
1498
+ return __awaiter(this, void 0, void 0, function () {
1499
+ var elapsed;
1500
+ return __generator(this, function (_a) {
1501
+ switch (_a.label) {
1502
+ case 0:
1503
+ elapsed = Date.now() - startTime;
1504
+ if (!(elapsed < MIN_LOADING_MS)) return [3 /*break*/, 2];
1505
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, MIN_LOADING_MS - elapsed); })];
1506
+ case 1:
1507
+ _a.sent();
1508
+ _a.label = 2;
1509
+ case 2: return [2 /*return*/];
1510
+ }
1511
+ });
1512
+ });
1513
+ };
1514
+ MMPaySDK.prototype._showQr = function (apiResponse, orderId) {
1515
+ this.ui.showQr({
1516
+ merchantName: this.merchantName,
1517
+ orderId: orderId,
1518
+ amount: apiResponse.amount,
1519
+ vendorQrRefId: apiResponse.vendorQrRefId,
1520
+ qr: apiResponse.qr
1521
+ }, this._getUiHandlers());
1522
+ };
1523
+ MMPaySDK.prototype._failFlow = function (orderId, error, amount) {
1524
+ this._stopTimers();
1525
+ this.settled = true;
1526
+ this.api.setToken(null);
1527
+ this.ui.showResult('FAILED', {
1528
+ orderId: orderId,
1529
+ messageKey: 'message.ERROR',
1530
+ detail: (error === null || error === void 0 ? void 0 : error.message) || 'Error occurred.'
1531
+ }, this._getUiHandlers());
1532
+ this._triggerEvent(amount === undefined ? { failed: true, orderId: orderId } : { failed: true, orderId: orderId, amount: amount });
1533
+ };
1534
+ /**
1535
+ * Single exit for every finished payment, whichever source reported it
1536
+ * first (socket, polling or the countdown), so the result screen and the
1537
+ * merchant callback fire exactly once.
1538
+ */
1539
+ MMPaySDK.prototype._finish = function (status, info, overrides) {
1540
+ if (overrides === void 0) { overrides = {}; }
1541
+ if (this.settled)
1542
+ return;
1543
+ this.settled = true;
1544
+ this._stopTimers();
1545
+ this._clearCache();
1546
+ this.api.setToken(null);
1547
+ this.ui.showResult(status, {
1548
+ orderId: info.orderId,
1549
+ messageKey: overrides.messageKey || 'message.' + status,
1550
+ ref: status === 'SUCCESS' ? (info.vendorQrRefId || 'N/A') : undefined
1551
+ }, this._getUiHandlers());
1552
+ this._triggerEvent({
1553
+ success: status === 'SUCCESS',
1554
+ failed: overrides.failed !== undefined ? overrides.failed : status === 'FAILED',
1555
+ expired: status === 'EXPIRED',
1556
+ cancelled: status === 'CANCELLED',
1557
+ amount: info.amount,
1558
+ orderId: info.orderId,
1559
+ vendorQrRefId: info.vendorQrRefId
1560
+ });
1561
+ };
1562
+ MMPaySDK.prototype._getUiHandlers = function () {
1563
+ var _this = this;
1564
+ return {
1565
+ onCloseRequest: function () { return _this.ui.showCancelConfirmation(); },
1566
+ onCancelConfirm: function () {
1567
+ _this._cancelPendingPayment();
1568
+ },
1569
+ onDismiss: function () {
1570
+ _this._clearCache();
1571
+ _this._cleanup();
1572
+ }
1573
+ };
1574
+ };
1575
+ MMPaySDK.prototype._cancelPendingPayment = function () {
1576
+ return __awaiter(this, void 0, void 0, function () {
1577
+ var flowId, pending;
1578
+ return __generator(this, function (_a) {
1579
+ switch (_a.label) {
1580
+ case 0:
1581
+ if (this.cancelling)
1582
+ return [2 /*return*/];
1583
+ this.cancelling = true;
1584
+ flowId = this.flowId;
1585
+ pending = this.pendingPaymentPayload;
1586
+ if (!(pending && !this.settled)) return [3 /*break*/, 4];
1587
+ this.ui.setCancelBusy(true);
1588
+ _a.label = 1;
1589
+ case 1:
1590
+ _a.trys.push([1, 3, , 4]);
1591
+ return [4 /*yield*/, this.api.cancelPayment({
1592
+ orderId: pending.orderId,
1593
+ nonce: new Date().getTime().toString() + '_cancel'
1594
+ })];
1595
+ case 2:
1596
+ _a.sent();
1597
+ if (flowId === this.flowId && !this.settled) {
1598
+ this._triggerEvent({
1599
+ cancelled: true,
1600
+ orderId: pending.orderId,
1601
+ amount: pending.amount
1602
+ });
1603
+ }
1604
+ return [3 /*break*/, 4];
1605
+ case 3:
1606
+ _a.sent();
1607
+ return [3 /*break*/, 4];
1608
+ case 4:
1609
+ // A result that arrived while the cancel request was in flight is already
1610
+ // on screen. Leave it there instead of closing over it.
1611
+ if (flowId !== this.flowId || this.settled)
1612
+ return [2 /*return*/];
1613
+ this.settled = true;
1614
+ this._clearCache();
1615
+ this._cleanup();
1616
+ return [2 /*return*/];
1617
+ }
1618
+ });
1619
+ });
1620
+ };
1621
+ MMPaySDK.prototype._triggerEvent = function (eventData) {
1622
+ if (this.onCompleteCallback) {
1623
+ try {
1624
+ this.onCompleteCallback(eventData);
1625
+ }
1626
+ catch (e) { }
1627
+ }
1628
+ };
1629
+ MMPaySDK.prototype._stopTimers = function () {
1630
+ if (this.statusWatcher) {
1631
+ this.statusWatcher.stop();
1632
+ this.statusWatcher = null;
1633
+ }
1634
+ if (this.countdownIntervalId !== undefined) {
1635
+ window.clearInterval(this.countdownIntervalId);
1636
+ this.countdownIntervalId = undefined;
1637
+ }
1638
+ };
1639
+ MMPaySDK.prototype._cleanup = function () {
1640
+ this._stopTimers();
1641
+ this.ui.close();
1642
+ };
1643
+ MMPaySDK.prototype._clearCache = function () {
1644
+ try {
1645
+ localStorage.removeItem(this.CACHE_KEY);
1646
+ }
1647
+ catch (e) { }
1648
+ };
1649
+ MMPaySDK.prototype._readCache = function () {
1650
+ try {
1651
+ var cachedData = localStorage.getItem(this.CACHE_KEY);
1652
+ if (!cachedData)
1653
+ return null;
1654
+ var parsed = JSON.parse(cachedData);
1655
+ if (parsed && parsed.payload && parsed.apiResponse && typeof parsed.expireAt === 'number') {
1656
+ return parsed;
1657
+ }
1658
+ }
1659
+ catch (e) { }
1660
+ this._clearCache();
1661
+ return null;
1662
+ };
1663
+ MMPaySDK.prototype._writeCache = function (data) {
1664
+ try {
1665
+ localStorage.setItem(this.CACHE_KEY, JSON.stringify(data));
1666
+ }
1667
+ catch (e) { }
1668
+ };
1669
+ MMPaySDK.prototype._checkAndAutoResume = function () {
1670
+ var _this = this;
1671
+ var cached = this._readCache();
1672
+ if (!cached)
1673
+ return;
1674
+ if (cached.environment !== this.environment || Date.now() >= cached.expireAt) {
1675
+ this._clearCache();
1676
+ return;
1677
+ }
1678
+ var start = function () {
1679
+ // The merchant may already have started a payment by the time the page
1680
+ // finished loading.
1681
+ if (_this.flowId !== 0)
1682
+ return;
1683
+ _this.flowId++;
1684
+ _this.settled = false;
1685
+ _this.cancelling = false;
1686
+ _this.api.setToken(cached.token);
1687
+ _this._resumePaymentState(cached.apiResponse, cached.payload, cached.expireAt);
1688
+ };
1689
+ if (document.body) {
1690
+ start();
1691
+ }
1692
+ else {
1693
+ document.addEventListener('DOMContentLoaded', start);
1694
+ }
1695
+ };
1696
+ MMPaySDK.prototype._resumePaymentState = function (apiResponse, payload, expireAt) {
1697
+ this.pendingPaymentPayload = payload;
1698
+ this.pendingApiResponse = apiResponse;
1699
+ var actualRefId = apiResponse.vendorQrRefId || apiResponse.transactionRefId;
1700
+ apiResponse.vendorQrRefId = actualRefId;
1701
+ this._showQr(apiResponse, payload.orderId);
1702
+ this._startStatusWatch({ orderId: payload.orderId, nonce: payload.nonce });
1703
+ this._startCountdown(payload.orderId, expireAt);
1704
+ this._triggerEvent({
1705
+ created: true,
1706
+ orderId: payload.orderId,
1707
+ amount: payload.amount,
1708
+ vendorQrRefId: actualRefId
1709
+ });
1710
+ };
1711
+ MMPaySDK.prototype._startStatusWatch = function (payload) {
1712
+ var _this = this;
1713
+ if (this.statusWatcher)
1714
+ this.statusWatcher.stop();
1715
+ var flowId = this.flowId;
1716
+ this.statusWatcher = new PaymentStatusWatcher(this.api, payload, {
1717
+ transport: this.transport,
1718
+ pollInterval: this.POLL_INTERVAL_MS,
1719
+ onStatus: function (response) {
1720
+ var _a;
1721
+ if (flowId !== _this.flowId)
1722
+ return;
1723
+ var status = (response.status || '').toUpperCase();
1724
+ if (status === 'SUCCESS' || status === 'FAILED' || status === 'EXPIRED' || status === 'CANCELLED') {
1725
+ _this._finish(status, {
1726
+ orderId: response.orderId || payload.orderId,
1727
+ amount: response.amount || ((_a = _this.pendingPaymentPayload) === null || _a === void 0 ? void 0 : _a.amount),
1728
+ vendorQrRefId: response.vendorQrRefId || response.transactionRefId
1729
+ });
1730
+ }
1731
+ },
1732
+ onConnectionChange: function (state) {
1733
+ if (flowId === _this.flowId)
1734
+ _this.ui.setConnectionState(state);
1735
+ }
1736
+ });
1737
+ this.statusWatcher.start();
1738
+ };
1739
+ MMPaySDK.prototype._startCountdown = function (orderId, expireAt) {
1740
+ var _this = this;
1741
+ if (this.countdownIntervalId !== undefined)
1742
+ window.clearInterval(this.countdownIntervalId);
1743
+ var flowId = this.flowId;
1744
+ var update = function () {
1745
+ var _a;
1746
+ if (flowId !== _this.flowId)
1747
+ return;
1748
+ var remaining = Math.max(0, Math.floor((expireAt - Date.now()) / 1000));
1749
+ _this.ui.setCountdown(remaining);
1750
+ if (remaining <= 0) {
1751
+ _this._finish('EXPIRED', {
1752
+ orderId: orderId,
1753
+ amount: (_a = _this.pendingPaymentPayload) === null || _a === void 0 ? void 0 : _a.amount
1754
+ }, { messageKey: 'message.TIMEOUT' });
1755
+ }
1756
+ };
1757
+ update();
1758
+ if (!this.settled) {
1759
+ this.countdownIntervalId = window.setInterval(update, 1000);
1760
+ }
1761
+ };
1762
+ return MMPaySDK;
1763
+ }());
1764
+ if (typeof window !== 'undefined') {
1765
+ window.MMPaySDK = MMPaySDK;
1766
+ }
1767
+
1768
+ export { MMPaySDK };