@mmpay/browser 0.0.0-stage → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@mmpay/browser",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.3.1",
4
+ "description": "JavaScript SDK for integrating the MMQR Merchant and Redirect payment gateway",
5
+ "keywords": [
6
+ "MMQR"
7
+ ],
8
+ "main": "./dist/cjs/index.js",
9
+ "module": "./dist/esm/index.js",
10
+ "types": "./dist/esm/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/esm/index.js",
14
+ "require": "./dist/cjs/index.js",
15
+ "types": "./dist/esm/index.d.ts"
16
+ }
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/nawing/MMPay-Browser-SDK.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/nawing/MMPay-Browser-SDK/issues"
24
+ },
25
+ "homepage": "https://github.com/nawing/MMPay-Browser-SDK#readme",
26
+ "scripts": {
27
+ "build": "tsc --emitDeclarationOnly && rollup -c rollup.config.ts --configPlugin typescript",
28
+ "dev": "rollup -c rollup.config.ts -w --configPlugin typescript",
29
+ "serve": "python3 -m http.server 5173"
30
+ },
31
+ "devDependencies": {
32
+ "dotenv": "^17.2.2",
33
+ "@rollup/plugin-commonjs": "^26.0.1",
34
+ "@rollup/plugin-node-resolve": "^15.2.3",
35
+ "@rollup/plugin-typescript": "^12.3.0",
36
+ "@types/bun": "^1.4.3",
37
+ "rollup": "^4.61.1",
38
+ "rollup-plugin-typescript2": "^0.36.0",
39
+ "tslib": "^2.8.1",
40
+ "typescript": "^7.0.2"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "author": "Naw Ing",
46
+ "license": "ISC"
47
+ }
@@ -0,0 +1,33 @@
1
+ import typescript from '@rollup/plugin-typescript';
2
+
3
+ export default {
4
+ input: 'src/index.ts',
5
+ output: [
6
+ {
7
+ file: 'dist/cjs/index.js',
8
+ format: 'cjs',
9
+ exports: 'named'
10
+ },
11
+ {
12
+ file: 'dist/esm/index.js',
13
+ format: 'es'
14
+ },
15
+ {
16
+ file: 'dist/mmpay-sdk.js',
17
+ format: 'umd',
18
+ name: 'MMPaySDK',
19
+ exports: 'named'
20
+ }
21
+ ],
22
+ plugins: [
23
+ typescript({
24
+ tsconfig: './tsconfig.json',
25
+ compilerOptions: {
26
+ outDir: 'dist',
27
+ declaration: false,
28
+ declarationMap: false,
29
+ declarationDir: undefined
30
+ }
31
+ })
32
+ ]
33
+ };
package/src/api.ts ADDED
@@ -0,0 +1,121 @@
1
+ import {
2
+ ICancelPaymentRequestParams,
3
+ ICancelPaymentResponse,
4
+ ICreatePaymentRequestParams,
5
+ ICreatePaymentResponse,
6
+ ICreateTokenRequestParams,
7
+ ICreateTokenResponse,
8
+ IPaymentShowRequestParams,
9
+ IPaymentShowResponse,
10
+ IPollingRequest,
11
+ IPollingResponse,
12
+ IStreamTicketResponse
13
+ } from './types';
14
+
15
+ const REQUEST_TIMEOUT_MS = 20000;
16
+
17
+ export class MMPayApiError extends Error {
18
+ public status: number;
19
+
20
+ constructor(message: string, status: number) {
21
+ super(message);
22
+ this.name = 'MMPayApiError';
23
+ this.status = status;
24
+ Object.setPrototypeOf(this, MMPayApiError.prototype);
25
+ }
26
+ }
27
+
28
+ export class MMPayAPI {
29
+ private baseUrl: string;
30
+ private environment: 'sandbox' | 'production';
31
+ private publishableKey: string;
32
+ private tokenKey: string | null = null;
33
+
34
+ constructor(baseUrl: string, environment: 'sandbox' | 'production', publishableKey: string) {
35
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
36
+ this.environment = environment;
37
+ this.publishableKey = publishableKey;
38
+ }
39
+
40
+ public setToken(token: string | null): void {
41
+ this.tokenKey = token;
42
+ }
43
+
44
+ public getToken(): string | null {
45
+ return this.tokenKey;
46
+ }
47
+
48
+ private async call<T>(endpoint: string, data: object = {}): Promise<T> {
49
+ let headers: any = {
50
+ 'Content-Type': 'application/json',
51
+ 'Authorization': `Bearer ${this.publishableKey}`
52
+ };
53
+ if (this.tokenKey) {
54
+ headers['X-MMPay-Btoken'] = this.tokenKey;
55
+ }
56
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
57
+ const timeoutId = controller ? setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS) : undefined;
58
+ try {
59
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
60
+ method: 'POST',
61
+ headers,
62
+ body: JSON.stringify(data),
63
+ signal: controller ? controller.signal : undefined
64
+ });
65
+ if (!response.ok) {
66
+ const errorText = await response.text();
67
+ throw new MMPayApiError(
68
+ `API Error (${response.status}): ${response.statusText}. Details: ${errorText}`,
69
+ response.status
70
+ );
71
+ }
72
+ return await response.json() as T;
73
+ } catch (error: any) {
74
+ if (error && error.name === 'AbortError') {
75
+ throw new MMPayApiError('Request timed out. Please check your connection.', 0);
76
+ }
77
+ throw error;
78
+ } finally {
79
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
80
+ }
81
+ }
82
+
83
+ public async createToken(payload: ICreateTokenRequestParams): Promise<ICreateTokenResponse> {
84
+ const endpoint = `/xpayments/${this.environment}-token-request`;
85
+ return this.call<ICreateTokenResponse>(endpoint, payload);
86
+ }
87
+
88
+ public async showPayment(payload: IPaymentShowRequestParams): Promise<IPaymentShowResponse> {
89
+ const endpoint = `/xpayments/${this.environment}-payment-show`;
90
+ return this.call<IPaymentShowResponse>(endpoint, payload);
91
+ }
92
+
93
+ public async cancelPayment(payload: ICancelPaymentRequestParams): Promise<ICancelPaymentResponse> {
94
+ const endpoint = `/xpayments/${this.environment}-payment-cancel`;
95
+ return this.call<ICancelPaymentResponse>(endpoint, payload);
96
+ }
97
+
98
+ public async createPayment(payload: ICreatePaymentRequestParams): Promise<ICreatePaymentResponse> {
99
+ const endpoint = `/xpayments/${this.environment}-payment-create`;
100
+ return this.call<ICreatePaymentResponse>(endpoint, payload);
101
+ }
102
+
103
+ public async pollPayment(payload: IPollingRequest): Promise<IPollingResponse> {
104
+ const endpoint = `/xpayments/${this.environment}-payment-polling`;
105
+ return this.call<IPollingResponse>(endpoint, payload);
106
+ }
107
+
108
+ /**
109
+ * A WebSocket handshake cannot carry the publishable key or session token,
110
+ * so the authenticated API issues a short-lived, single-use ticket for it.
111
+ */
112
+ public async createStreamTicket(payload: IPollingRequest): Promise<IStreamTicketResponse> {
113
+ const endpoint = `/xpayments/${this.environment}-payment-stream-ticket`;
114
+ return this.call<IStreamTicketResponse>(endpoint, payload);
115
+ }
116
+
117
+ public getStreamUrl(ticket: string): string {
118
+ const socketBase = this.baseUrl.replace(/^http/i, 'ws');
119
+ return `${socketBase}/xpayments/${this.environment}-payment-stream?ticket=${encodeURIComponent(ticket)}`;
120
+ }
121
+ }
package/src/i18n.ts ADDED
@@ -0,0 +1,112 @@
1
+ import {MMPayLanguage} from './types';
2
+
3
+ const LANGUAGE_STORAGE_KEY = 'mmpay_lang';
4
+
5
+ type Dictionary = Record<string, string>;
6
+
7
+ const en: Dictionary = {
8
+ 'loading.title': 'Connecting',
9
+ 'loading.subtitle': 'Setting up a secure connection...',
10
+ 'qr.scanHint': 'Scan with any MMQR banking app',
11
+ 'qr.poweredBy': 'Payment powered by MyanMyanPay',
12
+ 'qr.orderId': 'Order ID',
13
+ 'qr.refNo': 'MMQR Ref No',
14
+ 'qr.download': 'Download QR Code',
15
+ 'qr.waiting': 'Waiting for payment',
16
+ 'qr.reconnecting': 'Reconnecting...',
17
+ 'qr.loadFailed': 'The QR code could not be displayed.',
18
+ 'qr.retry': 'Try again',
19
+ 'cancel.title': 'Cancel Transaction?',
20
+ 'cancel.body': 'If you haven\'t paid yet, you can safely cancel this process.',
21
+ 'cancel.confirm': 'Stop Process',
22
+ 'cancel.back': 'Go Back',
23
+ 'status.SUCCESS': 'Success',
24
+ 'status.CANCELLED': 'Cancelled',
25
+ 'status.EXPIRED': 'Expired',
26
+ 'status.FAILED': 'Failed',
27
+ 'message.SUCCESS': 'Payment successful.',
28
+ 'message.CANCELLED': 'Payment cancelled.',
29
+ 'message.EXPIRED': 'Payment expired.',
30
+ 'message.FAILED': 'Payment failed.',
31
+ 'message.TIMEOUT': 'Time expired.',
32
+ 'message.ERROR': 'Something went wrong while starting the payment.',
33
+ 'message.ref': 'Ref: {ref}',
34
+ 'action.close': 'Close',
35
+ 'action.language': 'Language'
36
+ };
37
+
38
+ const mm: Dictionary = {
39
+ 'loading.title': 'ချိတ်ဆက်နေပါသည်',
40
+ 'loading.subtitle': 'လုံခြုံရေး စနစ်ဖြင့် ချိတ်ဆက်နေပါသည်...',
41
+ 'qr.poweredBy': 'MyanMyanPay ဖြင့် ပေးချေပါ',
42
+ 'qr.orderId': 'အော်ဒါနံပါတ်',
43
+ 'qr.refNo': 'ရည်ညွှန်းနံပါတ်',
44
+ 'qr.download': 'QR ဒေါင်းလုဒ်လုပ်မည်',
45
+ 'cancel.title': 'လုပ်ငန်းစဉ်ကို ပယ်ဖျက်မည်လား?',
46
+ 'cancel.body': 'ငွေမပေးချေရသေးပါက လုပ်ငန်းစဉ်ကို ဘေးကင်းစွာ ပယ်ဖျက်နိုင်ပါသည်။',
47
+ 'cancel.confirm': 'ရပ်တန့်မည်',
48
+ 'cancel.back': 'နောက်သို့',
49
+ 'status.SUCCESS': 'အောင်မြင်ပါသည်',
50
+ 'status.CANCELLED': 'ပယ်ဖျက်လိုက်ပါသည်',
51
+ 'status.EXPIRED': 'သက်တမ်းကုန်သွားပါပြီ',
52
+ 'status.FAILED': 'မအောင်မြင်ပါ',
53
+ 'message.SUCCESS': 'ငွေပေးချေမှု အောင်မြင်ပါပြီ။',
54
+ 'message.CANCELLED': 'ငွေပေးချေမှုကို ပယ်ဖျက်လိုက်ပါသည်။',
55
+ 'message.EXPIRED': 'ငွေပေးချေမှု သက်တမ်းကုန်သွားပါပြီ။',
56
+ 'message.FAILED': 'ငွေပေးချေမှု မအောင်မြင်ပါ။',
57
+ 'message.TIMEOUT': 'သတ်မှတ်ချိန်ကုန်သွားပါပြီ။',
58
+ 'message.ERROR': 'ငွေပေးချေမှု စတင်စဉ် အမှားအယွင်း ဖြစ်ပွားသည်။',
59
+ 'message.ref': 'ရည်ညွှန်းနံပါတ်: {ref}',
60
+ 'action.close': 'ပိတ်မည်',
61
+ 'action.language': 'ဘာသာစကား',
62
+
63
+ // Added in 1.3.0 for the new screens. Everything above was already shipped.
64
+ 'qr.scanHint': 'MMQR လက်ခံသော မည်သည့်ဘဏ်အက်ပ်ဖြင့်မဆို စကင်ဖတ်ပါ',
65
+ 'qr.waiting': 'ငွေပေးချေမှုကို စောင့်ဆိုင်းနေပါသည်',
66
+ 'qr.reconnecting': 'ပြန်လည်ချိတ်ဆက်နေပါသည်...',
67
+ 'qr.loadFailed': 'QR ကုဒ်ကို ပြသ၍မရပါ။',
68
+ 'qr.retry': 'ထပ်မံကြိုးစားမည်'
69
+ };
70
+
71
+ const dictionaries: Record<MMPayLanguage, Dictionary> = {en, mm};
72
+
73
+ export function normalizeLanguage(value: unknown): MMPayLanguage | null {
74
+ if (typeof value !== 'string') return null;
75
+ const code = value.toLowerCase();
76
+ if (code === 'mm' || code === 'my' || code.indexOf('my-') === 0) return 'mm';
77
+ if (code === 'en' || code.indexOf('en-') === 0) return 'en';
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * A missing translation falls back to English so a screen can never render
83
+ * blank text, whichever language is selected.
84
+ */
85
+ export function translate(language: MMPayLanguage, key: string, params?: Record<string, string>): string {
86
+ let text = dictionaries[language][key] || en[key] || key;
87
+ if (params) {
88
+ Object.keys(params).forEach(name => {
89
+ text = text.split('{' + name + '}').join(params[name]);
90
+ });
91
+ }
92
+ return text;
93
+ }
94
+
95
+ /**
96
+ * The shopper's own choice wins, then the merchant's configured language, then
97
+ * the browser language.
98
+ */
99
+ export function resolveInitialLanguage(preferred?: string): MMPayLanguage {
100
+ let stored: string | null = null;
101
+ try {
102
+ stored = window.localStorage.getItem(LANGUAGE_STORAGE_KEY);
103
+ } catch (e) { }
104
+ const browser = typeof navigator !== 'undefined' ? navigator.language : undefined;
105
+ return normalizeLanguage(stored) || normalizeLanguage(preferred) || normalizeLanguage(browser) || 'en';
106
+ }
107
+
108
+ export function rememberLanguage(language: MMPayLanguage): void {
109
+ try {
110
+ window.localStorage.setItem(LANGUAGE_STORAGE_KEY, language);
111
+ } catch (e) { }
112
+ }