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