@mmpay/browser 0.0.0-stage → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ {
2
+ "editor.rulers": [
3
+ 80
4
+ ],
5
+ "editor.tabCompletion": "on",
6
+ "editor.tabSize": 2,
7
+ "editor.trimAutoWhitespace": true,
8
+ "editor.formatOnSave": true,
9
+ "editor.codeActionsOnSave": {
10
+ "source.organizeImports": "explicit",
11
+ "source.fixAll.eslint": "explicit"
12
+ },
13
+ "files.exclude": {
14
+ "**/.DS_Store": true,
15
+ "**/.git": true,
16
+ "**/.angular": true,
17
+ "**/.hg": true,
18
+ "**/.svn": true,
19
+ "**/CVS": true,
20
+ "**/node_modules": true,
21
+ "**/keys": true,
22
+ },
23
+ "files.insertFinalNewline": true,
24
+ "files.trimTrailingWhitespace": true,
25
+ "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
26
+ "typescript.preferences.quoteStyle": "single",
27
+ "eslint.run": "onSave",
28
+ "eslint.nodePath": "./node_modules",
29
+ "eslint.validate": [
30
+ "javascript",
31
+ "typescript"
32
+ ]
33
+ }
package/README.md CHANGED
@@ -1,3 +1,472 @@
1
- # Temporary Holding Version
1
+ # MyanMyanPay Browser Plugin Or No-code SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Welcome to the **MyanMyanPay Browser Plugin No Code SDK**! This library provides a secure and seamless way to integrate MMQR right into your webpages without any redirects.
4
+ Developed using **TypeScript**, the SDK offers a clean, type-safe interface and handles complex tasks like API communication, UI rendering, and asynchronous payment status polling automatically.
5
+
6
+ ---
7
+
8
+
9
+ ## 1: 🛠️ Plugin or SDK Installation
10
+ The MyanMyanPay SDK is distributed as a single JavaScript file, ready for direct inclusion.
11
+ Embed the following `<script>` tag into the `<head>` or before the closing `</body>` tag of your checkout page.
12
+
13
+ **Implementation**
14
+
15
+ ```html
16
+ <script src="https://cdn.jsdelivr.net/npm/mmpay-browser-sdk@latest/dist/mmpay-sdk.js"></script>
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 2: 🚀 Initialize Your App
22
+ The `MMPaySDK` class provides two distinct methods to suit different integration needs.
23
+
24
+ **Implementation**
25
+ ```javascript
26
+ const MMPayApp = new MMPaySDK('pk_live_YOUR_KEY', {
27
+ merchantName: 'Your Shop Name',
28
+ design: {
29
+ mode: 'dark', // dark | dark-translucent | light | light-translucent
30
+ color: '#DE3163' // #color code
31
+ }
32
+ });
33
+ ```
34
+
35
+ ---
36
+
37
+ **All options**
38
+
39
+ | Option | Default | Description |
40
+ | :--- | :--- | :--- |
41
+ | `merchantName` | `'MyanMyanPay'` | Name shown above the amount. |
42
+ | `language` | browser language | `'en'` or `'mm'`. Language the modal opens in. If the shopper switches language in the modal, their choice is remembered and wins on the next visit. |
43
+ | `transport` | `'auto'` | How payment status reaches the browser. `'auto'` uses a WebSocket and falls back to HTTP polling if a socket cannot be opened. `'polling'` never opens a socket. `'websocket'` keeps retrying the socket instead of falling back after connection drops. |
44
+ | `timeoutSeconds` | `900` | Seconds the shopper has to pay before the modal shows Expired. 15 minutes by default, which is also the maximum; minimum 60. |
45
+ | `pollInterval` | `5000` | Interval of the polling fallback in milliseconds (minimum 2000). |
46
+ | `design.mode` | `'light'` | `light`, `dark`, `light-translucent`, `dark-translucent`, or `auto` to follow the shopper's system theme. |
47
+ | `design.color` | none | Colour of the main button, as a hex code, a CSS colour name, or `rgb()` / `hsl()`. |
48
+ | `baseUrl` | MyanMyanPay API | API base URL. The WebSocket URL is derived from it (`https` becomes `wss`). |
49
+
50
+ **Changing the language from your page**
51
+
52
+ ```javascript
53
+ MMPayApp.setLanguage('mm'); // 'en' | 'mm', works while the modal is open
54
+ MMPayApp.getLanguage();
55
+ ```
56
+
57
+ **Realtime status (1.3.0+)**
58
+
59
+ Payment status is pushed over a WebSocket, so the success screen appears about a second after the shopper pays. Nothing changes in how you call `pay()` or `showPaymentModal()`. When the shopper comes back from their banking app, the status is re-checked immediately, and a dropped connection reconnects on its own.
60
+
61
+ **One unfinished order at a time (1.3.0+)**
62
+
63
+ While the shopper has an order that is not paid, cancelled or expired, `showPaymentModal()` does not create another one. It shows the unfinished order again, also after a page reload or when called with a different `orderId`, and the `created` event carries the `orderId` of the order that is actually on screen. A new order is created once the previous one is finished or the shopper stops it.
64
+
65
+ A manual test page is in `test/index.html` (run `npm run build` first). It has a built-in mock API, so it works without a server.
66
+
67
+ If your site sends a Content-Security-Policy header, allow:
68
+
69
+ | Directive | Value |
70
+ | :--- | :--- |
71
+ | `connect-src` | `https://bbapi.myanmyanpay.com wss://bbapi.myanmyanpay.com` |
72
+ | `script-src` | `https://cdnjs.cloudflare.com https://cdn.jsdelivr.net` (QR renderer, loaded with Subresource Integrity) |
73
+ | `img-src` | `https://motephoe.com` |
74
+ | `style-src` / `font-src` | `https://fonts.googleapis.com` / `https://fonts.gstatic.com` (Burmese font) |
75
+
76
+ The modal renders inside a shadow root and no longer uses inline event handlers or global functions on `window`.
77
+
78
+ ---
79
+
80
+ ## 3: 🚀 Making Payments
81
+
82
+ ##### Approach 1 [Browser Site Order Creation] `showPaymentModal()`
83
+
84
+ This function is a simple way where an MMQR Reference No and QR to be created on the browser site, but you must verify the source of truth in our webhooks to avoid payload manipulation.
85
+
86
+ **Method Signature**
87
+ ```typescript
88
+ showPaymentModal({orderId: string, amount: number}, onComplete: Function ): Promise<void>
89
+ ```
90
+
91
+ **Browser Plugin Implementation**
92
+ ```javascript
93
+ const MMPayApp = new MMPaySDK('pk_live_YOUR_KEY', {
94
+ merchantName: 'Your Shop Name',
95
+ design: {
96
+ mode: 'dark', // dark | dark-translucent | light | light-translucent
97
+ color: '#DE3163' // #color code
98
+ }
99
+ });
100
+
101
+ MMPayApp.showPaymentModal({
102
+ orderId: 'Order-ID-111111',
103
+ amount: 2800
104
+ }, (result) => {
105
+ if (result.success) {
106
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
107
+ }
108
+ })
109
+ ```
110
+
111
+ **Verifying Source of Truth**
112
+
113
+ Do this at your backend to verify source of truth. If there are any payload manipulation that does not match your amount, the order can be 'CANCELLED' instanly via our api.
114
+
115
+ ```typescript
116
+ // Example of callback handling in the backend
117
+ import { MMPaySDK } from 'mmpay-node-sdk';
118
+
119
+ const MMPay = new MMPaySDK({
120
+ appId: "MMxxxxxxx",
121
+ publishableKey: "pk_live_abcxxxxx",
122
+ secretKey: "sk_live_abcxxxxx",
123
+ apiBaseUrl: "https://xxxxxx"
124
+ })
125
+
126
+ MMPay
127
+ .onTxCreate((tx) => {
128
+ const { amount } = await DB.getOrderId(tx.orderId) // get your source of truth from your database
129
+ if (tx.amount !== amount ) {
130
+ await MMPay.cancel(tx.orderId) // cancel order
131
+ }
132
+ })
133
+ .onTxSuccess((tx) => console.log('Success:', tx.orderId))
134
+ .onTxFail((tx) => console.log('Failed:', tx.orderId))
135
+ .onTxRefund((tx) => console.log('Refunded:', tx.orderId))
136
+ .onTxCancel((tx) => console.log('Cancelled:', tx.orderId))
137
+ .onTxExpire((tx) => console.log('Expired:', tx.orderId))
138
+ .onHeartbeat((tx) => console.log('Heartbeat:', tx.orderId))
139
+ .on('error', (err) => console.error(err));
140
+
141
+ // Listening Callback
142
+ app.post('/webhooks/mmpay-callback', async (req: Request, res: Response) => {
143
+ const payload = JSON.stringify(req.body);
144
+ const nonce = req.headers['x-mmpay-nonce'] as string;
145
+ const signature = req.headers['x-mmpay-signature'] as string;
146
+ await MMPaySandbox.listen(payload, nonce, signature);
147
+ res.json({ received: true }); // please respond with 200 status
148
+ });
149
+ ```
150
+
151
+
152
+ ##### Approach 2 [Backend Site Order Creation] `pay()`
153
+
154
+ This function is a simple way where an MMQR Reference No and QR to be created at backend with secret key securely, and putting the order ID in our plugin to make POP UP appear in your browser.
155
+
156
+ **Method Signature**
157
+ ```typescript
158
+ pay(orderId: string, onComplete: Function ): Promise<void>
159
+ ```
160
+
161
+ **Establishing a Source of Truth**
162
+
163
+ Do this at your backend server, it is important to create a source of truth in this manner.
164
+
165
+ ```bash
166
+ npm install mmpay-node-sdk --save
167
+ ```
168
+
169
+ **Backend Integration**
170
+ ```typescript
171
+ import { MMPaySDK } from 'mmpay-node-sdk';
172
+
173
+ const MMPay = new MMPaySDK({
174
+ appId: "MMxxxxxxx",
175
+ publishableKey: "pk_live_abcxxxxx",
176
+ secretKey: "sk_live_abcxxxxx",
177
+ apiBaseUrl: "https://xxxxxx"
178
+ })
179
+
180
+ const { qr, orderId } = await MMPay.pay({ amount, orderId });
181
+ // get your order Id to your browser
182
+ ```
183
+
184
+ **Browser Plugin Implementation**
185
+ ```javascript
186
+ const MMPayApp = new MMPaySDK('pk_live_YOUR_KEY', {
187
+ merchantName: 'Your Shop Name',
188
+ design: {
189
+ mode: 'dark', // dark | dark-translucent | light | light-translucent
190
+ color: '#DE3163' // #color code
191
+ }
192
+ });
193
+
194
+ MMPayApp.pay('Order-ID-111111', (result) => {
195
+ if (result.success) {
196
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
197
+ }
198
+ })
199
+ ```
200
+
201
+ ---
202
+
203
+
204
+ ## 4: All Supported Events
205
+
206
+ **Browser Plugin Implementation Display All Event Types**
207
+
208
+ ```javascript
209
+ MMPayApp.pay('Order-ID-111111', (result) => {
210
+ if (result.success) {
211
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
212
+ }
213
+ if (result.created) {
214
+ console.log('Created: ' + result.orderId);
215
+ }
216
+ if (result.cancelled) {
217
+ console.log('Cancelled: ' + result.orderId);
218
+ }
219
+ if (result.expired) {
220
+ console.log('Expoired: ' + result.orderId);
221
+ }
222
+ })
223
+
224
+ MMPayApp.showPaymentModal({ orderId, amount }, (result) => {
225
+ if (result.success) {
226
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
227
+ }
228
+ if (result.created) {
229
+ console.log('Created: ' + result.orderId);
230
+ }
231
+ if (result.cancelled) {
232
+ console.log('Cancelled: ' + result.orderId);
233
+ }
234
+ if (result.expired) {
235
+ console.log('Expoired: ' + result.orderId);
236
+ }
237
+ })
238
+ ```
239
+
240
+ ---
241
+
242
+ ## 5: Error Codes
243
+
244
+ | Code | Description |
245
+ | :--- | :--- |
246
+ | **`R000`** | Internal Server Error ( Talk to our support immediately fot this ) |
247
+ | **`R001`** | Wrong publishable Key |
248
+ | **`R002`** | Key Not Live Yet |
249
+ | **`R003`** | Origin Whitelist Not Allowed |
250
+ | **`R004`** | Origin Requires SSL |
251
+ | **`429`** | Ratelimit hit only 1000 request / minute allowed |
252
+
253
+ ---
254
+
255
+ ## 6: Angular Framework Implementation
256
+
257
+ **Example Implementation**
258
+
259
+ ```typescript
260
+ import {Injectable} from '@angular/core';
261
+
262
+ interface ModalResponse {
263
+ success: boolean,
264
+ created: boolean,
265
+ cancelled: boolean,
266
+ expired: boolean,
267
+ orderId: string;
268
+ transactionRefId: string;
269
+ }
270
+
271
+ @Injectable({
272
+ providedIn: 'root'
273
+ })
274
+ export class MMPayService {
275
+ mmpay: any;
276
+ constructor() {
277
+ const MMPaySDK = (window as any).MMPaySDK;
278
+ if (!MMPaySDK) {
279
+ console.error('SDK not loaded attached to window');
280
+ return;
281
+ }
282
+ this.mmpay = new MMPaySDK('pk_test_123', {
283
+ baseUrl: 'https://xxx.myanmyanpay.com', // Sign up with us and ask our tea
284
+ merchantName: 'Test Shop',
285
+ design: {
286
+ mode: 'light-translucent', // dark | dark-translucent | light | light-translucent
287
+ color: '#9CA6EB' // #color code
288
+ }
289
+ });
290
+ }
291
+ /**
292
+ * @param {number} amount
293
+ * @param {string} orderId
294
+ * @param {string} callbackUrl
295
+ * @param {string} customMessage
296
+ */
297
+ pay(orderId: string ) {
298
+ this.mmpay.pay(orderId, (result: ModalResponse) => {
299
+ if (result.success) {
300
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
301
+ }
302
+ if (result.created) {
303
+ console.log('Created: ' + result.orderId);
304
+ }
305
+ if (result.cancelled) {
306
+ console.log('Cancelled: ' + result.orderId);
307
+ }
308
+ if (result.expired) {
309
+ console.log('Expoired: ' + result.orderId);
310
+ }
311
+ });
312
+ }
313
+ }
314
+ ```
315
+
316
+ ---
317
+
318
+
319
+ ## 7. React Framework Implementation
320
+
321
+ **Example Implementation**
322
+
323
+ ```tsx
324
+ import React, { useEffect, useRef } from 'react';
325
+
326
+ interface ModalResponse {
327
+ success: boolean,
328
+ created: boolean,
329
+ cancelled: boolean,
330
+ expired: boolean,
331
+ orderId: string;
332
+ transactionRefId: string;
333
+ }
334
+
335
+ export const useMMPay = () => {
336
+ const mmpayRef = useRef(null);
337
+
338
+ useEffect(() => {
339
+ const MMPaySDK = window.MMPaySDK;
340
+
341
+ if (!MMPaySDK) {
342
+ console.error('SDK not loaded attached to window');
343
+ return;
344
+ }
345
+
346
+ mmpayRef.current = new MMPaySDK('pk_test_123', {
347
+ baseUrl: 'https://xxx.myanmyanpay.com',
348
+ merchantName: 'Test Shop',
349
+ design: {
350
+ mode: 'dark-translucent', // dark | dark-translucent | light | light-translucent
351
+ color: '#E1A284' // #color code
352
+ }
353
+ });
354
+ }, []);
355
+
356
+ const pay = (orderId: string) => {
357
+ if (!mmpayRef.current) return;
358
+
359
+ mmpayRef.current.pay(orderId, (result: ModalResponse) => {
360
+ if (result.success) {
361
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
362
+ // Redirect Somewhere
363
+ }
364
+ if (result.created) {
365
+ console.log('Created: ' + result.orderId);
366
+ }
367
+ if (result.cancelled) {
368
+ console.log('Cancelled: ' + result.orderId);
369
+ }
370
+ if (result.expired) {
371
+ console.log('Expoired: ' + result.orderId);
372
+ }
373
+ });
374
+ };
375
+
376
+ return { pay };
377
+ };
378
+
379
+ export const Checkout = () => {
380
+ const { pay } = useMMPay();
381
+
382
+ const handlePayment = () => {
383
+ pay(`ORD-${Date.now()}`);
384
+ };
385
+
386
+ return (
387
+ <button onClick={handlePayment}>
388
+ Pay with MyanMyanPay
389
+ </button>
390
+ );
391
+ };
392
+ ```
393
+
394
+ ---
395
+
396
+
397
+ ## 8. Vue Framework Implementation
398
+
399
+ **Example Implementation**
400
+
401
+ ```vue
402
+
403
+ <script setup>
404
+ import { onMounted, ref } from 'vue';
405
+
406
+ interface ModalResponse {
407
+ success: boolean,
408
+ created: boolean,
409
+ cancelled: boolean,
410
+ expired: boolean,
411
+ orderId: string;
412
+ transactionRefId: string;
413
+ }
414
+
415
+ const mmpay = ref(null);
416
+
417
+ onMounted(() => {
418
+ const MMPaySDK = window.MMPaySDK;
419
+
420
+ if (!MMPaySDK) {
421
+ console.error('SDK not loaded attached to window');
422
+ return;
423
+ }
424
+
425
+ mmpay.value = new MMPaySDK('pk_test_123', {
426
+ baseUrl: 'https://xxx.myanmyanpay.com',
427
+ merchantName: 'Test Shop',
428
+ design: {
429
+ mode: 'light', // dark | dark-translucent | light | light-translucent
430
+ color: '#BF40BF' // #color code
431
+ }
432
+ });
433
+ });
434
+
435
+ const pay = ( orderId: string ) => {
436
+ if (!mmpay.value) return;
437
+
438
+ mmpay.value.pay(orderId, (result: ModalResponse) => {
439
+ if (result.success) {
440
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
441
+ // Redirect Somewhere
442
+ }
443
+ if (result.created) {
444
+ console.log('Created: ' + result.orderId);
445
+ }
446
+ if (result.cancelled) {
447
+ console.log('Cancelled: ' + result.orderId);
448
+ }
449
+ if (result.expired) {
450
+ console.log('Expoired: ' + result.orderId);
451
+ }
452
+ });
453
+ };
454
+
455
+ const handlePayment = () => {
456
+ pay(`ORD-${Date.now()}`);
457
+ };
458
+ </script>
459
+
460
+ <template>
461
+ <button @click="handlePayment">
462
+ Pay with MyanMyanPay
463
+ </button>
464
+ </template>
465
+ ```
466
+
467
+ ---
468
+
469
+
470
+ ## License
471
+
472
+ MIT