@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.
@@ -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,490 @@
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@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
+ You can try this in the demo playground, see [Run the demo](#run-the-demo).
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
+ ## Run the demo
81
+
82
+ The repo includes a playground at `test/index.html`. In it you can pick the theme, card style, button colour and language, see the matching code for HTML, React or Vue with a Copy button, and run a real payment test against the built SDK.
83
+
84
+ ```bash
85
+ npm install
86
+ npm run build # builds ./dist/mmpay-sdk.js, which the demo loads
87
+ npm run serve # serves the repo at http://localhost:5173
88
+ ```
89
+
90
+ Open **http://localhost:5173/test/**, then:
91
+
92
+ 1. **Real test:** enter your `pk_test_` publishable key under *Connection* and press **▶ Run test**. Sandbox keys work from `http://localhost`. Live `pk_live_` keys only work on an HTTPS origin in your whitelist, so use a test key here. The log shows each HTTP call and WebSocket message, so you can see the status arrive in real time.
93
+ 2. **No server:** tick *Mock API*. Requests stay in the page, and the *Paid / Failed / Expired / Cancelled* buttons set the order status.
94
+ 3. **Spam test (5×)** calls `showPaymentModal()` five times with new order IDs. Only one order should be created. **Reload** checks that an unfinished order reopens on its own.
95
+
96
+ After changing anything in `src/`, run `npm run build` again and reload the page. Open the demo through `npm run serve`, not by double-clicking the file: from `file://` the WebSocket is refused and the SDK falls back to polling.
97
+
98
+ ---
99
+
100
+ ## 3: 🚀 Making Payments
101
+
102
+ ##### Approach 1 [Browser Site Order Creation] `showPaymentModal()`
103
+
104
+ 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.
105
+
106
+ **Method Signature**
107
+ ```typescript
108
+ showPaymentModal({orderId: string, amount: number}, onComplete: Function ): Promise<void>
109
+ ```
110
+
111
+ **Browser Plugin Implementation**
112
+ ```javascript
113
+ const MMPayApp = new MMPaySDK('pk_live_YOUR_KEY', {
114
+ merchantName: 'Your Shop Name',
115
+ design: {
116
+ mode: 'dark', // dark | dark-translucent | light | light-translucent
117
+ color: '#DE3163' // #color code
118
+ }
119
+ });
120
+
121
+ MMPayApp.showPaymentModal({
122
+ orderId: 'Order-ID-111111',
123
+ amount: 2800
124
+ }, (result) => {
125
+ if (result.success) {
126
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
127
+ }
128
+ })
129
+ ```
130
+
131
+ **Verifying Source of Truth**
132
+
133
+ 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.
134
+
135
+ ```typescript
136
+ // Example of callback handling in the backend
137
+ import { MMPaySDK } from 'mmpay-node-sdk';
138
+
139
+ const MMPay = new MMPaySDK({
140
+ appId: "MMxxxxxxx",
141
+ publishableKey: "pk_live_abcxxxxx",
142
+ secretKey: "sk_live_abcxxxxx",
143
+ apiBaseUrl: "https://xxxxxx"
144
+ })
145
+
146
+ MMPay
147
+ .onTxCreate((tx) => {
148
+ const { amount } = await DB.getOrderId(tx.orderId) // get your source of truth from your database
149
+ if (tx.amount !== amount ) {
150
+ await MMPay.cancel(tx.orderId) // cancel order
151
+ }
152
+ })
153
+ .onTxSuccess((tx) => console.log('Success:', tx.orderId))
154
+ .onTxFail((tx) => console.log('Failed:', tx.orderId))
155
+ .onTxRefund((tx) => console.log('Refunded:', tx.orderId))
156
+ .onTxCancel((tx) => console.log('Cancelled:', tx.orderId))
157
+ .onTxExpire((tx) => console.log('Expired:', tx.orderId))
158
+ .onHeartbeat((tx) => console.log('Heartbeat:', tx.orderId))
159
+ .on('error', (err) => console.error(err));
160
+
161
+ // Listening Callback
162
+ app.post('/webhooks/mmpay-callback', async (req: Request, res: Response) => {
163
+ const payload = JSON.stringify(req.body);
164
+ const nonce = req.headers['x-mmpay-nonce'] as string;
165
+ const signature = req.headers['x-mmpay-signature'] as string;
166
+ await MMPaySandbox.listen(payload, nonce, signature);
167
+ res.json({ received: true }); // please respond with 200 status
168
+ });
169
+ ```
170
+
171
+
172
+ ##### Approach 2 [Backend Site Order Creation] `pay()`
173
+
174
+ 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.
175
+
176
+ **Method Signature**
177
+ ```typescript
178
+ pay(orderId: string, onComplete: Function ): Promise<void>
179
+ ```
180
+
181
+ **Establishing a Source of Truth**
182
+
183
+ Do this at your backend server, it is important to create a source of truth in this manner.
184
+
185
+ ```bash
186
+ npm install mmpay-node-sdk --save
187
+ ```
188
+
189
+ **Backend Integration**
190
+ ```typescript
191
+ import { MMPaySDK } from 'mmpay-node-sdk';
192
+
193
+ const MMPay = new MMPaySDK({
194
+ appId: "MMxxxxxxx",
195
+ publishableKey: "pk_live_abcxxxxx",
196
+ secretKey: "sk_live_abcxxxxx",
197
+ apiBaseUrl: "https://xxxxxx"
198
+ })
199
+
200
+ const { qr, orderId } = await MMPay.pay({ amount, orderId });
201
+ // get your order Id to your browser
202
+ ```
203
+
204
+ **Browser Plugin Implementation**
205
+ ```javascript
206
+ const MMPayApp = new MMPaySDK('pk_live_YOUR_KEY', {
207
+ merchantName: 'Your Shop Name',
208
+ design: {
209
+ mode: 'dark', // dark | dark-translucent | light | light-translucent
210
+ color: '#DE3163' // #color code
211
+ }
212
+ });
213
+
214
+ MMPayApp.pay('Order-ID-111111', (result) => {
215
+ if (result.success) {
216
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
217
+ }
218
+ })
219
+ ```
220
+
221
+ ---
222
+
223
+
224
+ ## 4: All Supported Events
225
+
226
+ **Browser Plugin Implementation Display All Event Types**
227
+
228
+ ```javascript
229
+ MMPayApp.pay('Order-ID-111111', (result) => {
230
+ if (result.success) {
231
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
232
+ }
233
+ if (result.created) {
234
+ console.log('Created: ' + result.orderId);
235
+ }
236
+ if (result.cancelled) {
237
+ console.log('Cancelled: ' + result.orderId);
238
+ }
239
+ if (result.expired) {
240
+ console.log('Expoired: ' + result.orderId);
241
+ }
242
+ })
243
+
244
+ MMPayApp.showPaymentModal({ orderId, amount }, (result) => {
245
+ if (result.success) {
246
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
247
+ }
248
+ if (result.created) {
249
+ console.log('Created: ' + result.orderId);
250
+ }
251
+ if (result.cancelled) {
252
+ console.log('Cancelled: ' + result.orderId);
253
+ }
254
+ if (result.expired) {
255
+ console.log('Expoired: ' + result.orderId);
256
+ }
257
+ })
258
+ ```
259
+
260
+ ---
261
+
262
+ ## 5: Error Codes
263
+
264
+ | Code | Description |
265
+ | :--- | :--- |
266
+ | **`R000`** | Internal Server Error ( Talk to our support immediately fot this ) |
267
+ | **`R001`** | Wrong publishable Key |
268
+ | **`R002`** | Key Not Live Yet |
269
+ | **`R003`** | Origin Whitelist Not Allowed |
270
+ | **`R004`** | Origin Requires SSL |
271
+ | **`429`** | Ratelimit hit only 1000 request / minute allowed |
272
+
273
+ ---
274
+
275
+ ## 6: Angular Framework Implementation
276
+
277
+ **Example Implementation**
278
+
279
+ ```typescript
280
+ import {Injectable} from '@angular/core';
281
+
282
+ interface ModalResponse {
283
+ success: boolean,
284
+ created: boolean,
285
+ cancelled: boolean,
286
+ expired: boolean,
287
+ orderId: string;
288
+ transactionRefId: string;
289
+ }
290
+
291
+ @Injectable({
292
+ providedIn: 'root'
293
+ })
294
+ export class MMPayService {
295
+ mmpay: any;
296
+ constructor() {
297
+ const MMPaySDK = (window as any).MMPaySDK;
298
+ if (!MMPaySDK) {
299
+ console.error('SDK not loaded attached to window');
300
+ return;
301
+ }
302
+ this.mmpay = new MMPaySDK('pk_test_123', {
303
+ merchantName: 'Test Shop',
304
+ design: {
305
+ mode: 'light-translucent', // dark | dark-translucent | light | light-translucent
306
+ color: '#9CA6EB' // #color code
307
+ }
308
+ });
309
+ }
310
+ /**
311
+ * @param {number} amount
312
+ * @param {string} orderId
313
+ * @param {string} callbackUrl
314
+ * @param {string} customMessage
315
+ */
316
+ pay(orderId: string ) {
317
+ this.mmpay.pay(orderId, (result: ModalResponse) => {
318
+ if (result.success) {
319
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
320
+ }
321
+ if (result.created) {
322
+ console.log('Created: ' + result.orderId);
323
+ }
324
+ if (result.cancelled) {
325
+ console.log('Cancelled: ' + result.orderId);
326
+ }
327
+ if (result.expired) {
328
+ console.log('Expoired: ' + result.orderId);
329
+ }
330
+ });
331
+ }
332
+ }
333
+ ```
334
+
335
+ ---
336
+
337
+
338
+ ## 7. React Framework Implementation
339
+
340
+ **Example Implementation**
341
+
342
+ ```tsx
343
+ import React, { useEffect, useRef } from 'react';
344
+
345
+ interface ModalResponse {
346
+ success: boolean,
347
+ created: boolean,
348
+ cancelled: boolean,
349
+ expired: boolean,
350
+ orderId: string;
351
+ transactionRefId: string;
352
+ }
353
+
354
+ export const useMMPay = () => {
355
+ const mmpayRef = useRef(null);
356
+
357
+ useEffect(() => {
358
+ const MMPaySDK = window.MMPaySDK;
359
+
360
+ if (!MMPaySDK) {
361
+ console.error('SDK not loaded attached to window');
362
+ return;
363
+ }
364
+
365
+ mmpayRef.current = new MMPaySDK('pk_test_123', {
366
+ merchantName: 'Test Shop',
367
+ design: {
368
+ mode: 'dark-translucent', // dark | dark-translucent | light | light-translucent
369
+ color: '#E1A284' // #color code
370
+ }
371
+ });
372
+ }, []);
373
+
374
+ const pay = (orderId: string) => {
375
+ if (!mmpayRef.current) return;
376
+
377
+ mmpayRef.current.pay(orderId, (result: ModalResponse) => {
378
+ if (result.success) {
379
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
380
+ // Redirect Somewhere
381
+ }
382
+ if (result.created) {
383
+ console.log('Created: ' + result.orderId);
384
+ }
385
+ if (result.cancelled) {
386
+ console.log('Cancelled: ' + result.orderId);
387
+ }
388
+ if (result.expired) {
389
+ console.log('Expoired: ' + result.orderId);
390
+ }
391
+ });
392
+ };
393
+
394
+ return { pay };
395
+ };
396
+
397
+ export const Checkout = () => {
398
+ const { pay } = useMMPay();
399
+
400
+ const handlePayment = () => {
401
+ pay(`ORD-${Date.now()}`);
402
+ };
403
+
404
+ return (
405
+ <button onClick={handlePayment}>
406
+ Pay with MyanMyanPay
407
+ </button>
408
+ );
409
+ };
410
+ ```
411
+
412
+ ---
413
+
414
+
415
+ ## 8. Vue Framework Implementation
416
+
417
+ **Example Implementation**
418
+
419
+ ```vue
420
+
421
+ <script setup>
422
+ import { onMounted, ref } from 'vue';
423
+
424
+ interface ModalResponse {
425
+ success: boolean,
426
+ created: boolean,
427
+ cancelled: boolean,
428
+ expired: boolean,
429
+ orderId: string;
430
+ transactionRefId: string;
431
+ }
432
+
433
+ const mmpay = ref(null);
434
+
435
+ onMounted(() => {
436
+ const MMPaySDK = window.MMPaySDK;
437
+
438
+ if (!MMPaySDK) {
439
+ console.error('SDK not loaded attached to window');
440
+ return;
441
+ }
442
+
443
+ mmpay.value = new MMPaySDK('pk_test_123', {
444
+ baseUrl: 'https://xxx.myanmyanpay.com',
445
+ merchantName: 'Test Shop',
446
+ design: {
447
+ mode: 'light', // dark | dark-translucent | light | light-translucent
448
+ color: '#BF40BF' // #color code
449
+ }
450
+ });
451
+ });
452
+
453
+ const pay = ( orderId: string ) => {
454
+ if (!mmpay.value) return;
455
+
456
+ mmpay.value.pay(orderId, (result: ModalResponse) => {
457
+ if (result.success) {
458
+ console.log('Success: ' + result.orderId + ' : Transaction : ' + result.transactionId);
459
+ // Redirect Somewhere
460
+ }
461
+ if (result.created) {
462
+ console.log('Created: ' + result.orderId);
463
+ }
464
+ if (result.cancelled) {
465
+ console.log('Cancelled: ' + result.orderId);
466
+ }
467
+ if (result.expired) {
468
+ console.log('Expoired: ' + result.orderId);
469
+ }
470
+ });
471
+ };
472
+
473
+ const handlePayment = () => {
474
+ pay(`ORD-${Date.now()}`);
475
+ };
476
+ </script>
477
+
478
+ <template>
479
+ <button @click="handlePayment">
480
+ Pay with MyanMyanPay
481
+ </button>
482
+ </template>
483
+ ```
484
+
485
+ ---
486
+
487
+
488
+ ## License
489
+
490
+ MIT