mmpay-python-sdk 0.1.1__tar.gz → 0.1.2__tar.gz

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,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: mmpay-python-sdk
3
+ Version: 0.1.2
4
+ Summary: Python SDK for MyanMyanPay
5
+ Home-page: https://github.com/nawing/MMPay-Python-SDK
6
+ Author: Naw Ing
7
+ Author-email: nawing@myanmyanpay.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ # MMPay Python SDK
27
+
28
+ A Python client library for integrating with the MMPay Payment Gateway. This SDK is a direct port of the official Node.js SDK, providing utilities for payment creation, transaction retrieval, handshake authentication, and callback verification for technical architects and developers.
29
+
30
+ ## Features
31
+
32
+ - Sandbox & Production Support: Dedicated methods for both environments.
33
+ - Payment Creation & Retrieval: Endpoints to create payments and fetch transaction statuses.
34
+ - HMAC SHA256 Signing: Automatic signature generation for request integrity.
35
+ - Callback Verification: Utility to verify incoming webhooks from MMPay.
36
+ - Type Definitions: Includes TypedDict definitions for clear payload structuring.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install mmpay-python-sdk
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ To use the SDK, you need your App ID, Publishable Key, and Secret Key provided by the MyanMyanPay dashboard.
47
+
48
+ | Parameter | Type | Required | Description |
49
+ | :--- | :--- | :--- | :--- |
50
+ | appId | str | Yes | Your unique Application ID. |
51
+ | publishableKey | str | Yes | Public key for authentication. |
52
+ | secretKey | str | Yes | Private key used for signing requests (HMAC). |
53
+ | apiBaseUrl | str | Yes | The base URL for the MMPay API. |
54
+
55
+
56
+ ```python
57
+ from mmpay import MMPaySDK
58
+
59
+ options = {
60
+ "appId": "YOUR_APP_ID",
61
+ "publishableKey": "YOUR_PUBLISHABLE_KEY",
62
+ "secretKey": "YOUR_SECRET_KEY",
63
+ "apiBaseUrl": "https://xxx.myanmyanpay.com"
64
+ }
65
+
66
+ sdk = MMPaySDK(options)
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### 1. Payment Request Payload
72
+
73
+ Passed to `sdk.pay(params)` or `sdk.sandbox_pay(params)`.
74
+
75
+ | Parameter | Type | Required | Description |
76
+ | :--- | :--- | :--- | :--- |
77
+ | `orderId` | `str` | Yes | Unique identifier for the order (e.g., "ORD-001"). |
78
+ | `amount` | `float` | Yes | Total transaction amount. |
79
+ | `items` | `List[Item]` | No | A list of items included in the order. |
80
+ | `callbackUrl` | `str` | No | URL where the webhook callback will be sent. |
81
+ | `customMessage` | `str` | No | Custom message to be attached to the transaction. |
82
+
83
+ **Item Object**
84
+
85
+ Used inside the `items` list of a Payment Request.
86
+
87
+ | Parameter | Type | Required | Description |
88
+ | :--- | :--- | :--- | :--- |
89
+ | `name` | `str` | Yes | Name of the product/service. |
90
+ | `amount` | `float` | Yes | Price per unit. |
91
+ | `quantity` | `int` | Yes | Quantity of the item. |
92
+
93
+ ### 2. Create a Payment (Sandbox)
94
+
95
+
96
+ ```python
97
+ try:
98
+ payment_request = {
99
+ "orderId": "ORD-SANDBOX-001",
100
+ "amount": 5000,
101
+ "callbackUrl": "https://your-site.com/webhook/mmpay",
102
+ "customMessage": "Your Custom Message",
103
+ "items": [
104
+ {
105
+ "name": "Premium Subscription",
106
+ "amount": 5000,
107
+ "quantity": 1
108
+ }
109
+ ]
110
+ }
111
+
112
+ response = sdk.sandbox_pay(payment_request)
113
+ print(response)
114
+
115
+ except Exception as e:
116
+ print(e)
117
+ ```
118
+
119
+ ### 3. Retrieve a Payment
120
+
121
+ ```python
122
+ #(Sandbox)
123
+ try:
124
+ get_request = {
125
+ "orderId": "ORD-SANDBOX-001"
126
+ }
127
+
128
+ response = sdk.sandbox_get(get_request)
129
+ print(response)
130
+
131
+ except Exception as e:
132
+ print(e)
133
+ ```
134
+
135
+
136
+ ```python
137
+ #(Production)
138
+ try:
139
+ get_request = {
140
+ "orderId": "ORD-SANDBOX-001"
141
+ }
142
+
143
+ response = sdk.get(get_request)
144
+ print(response)
145
+ ```
146
+
147
+ ### 4. Create a Payment (Production)
148
+
149
+ ```python
150
+ try:
151
+ payment_request = {
152
+ "orderId": "ORD-LIVE-98765",
153
+ "amount": 5000,
154
+ "callbackUrl": "https://your-site.com/webhook/mmpay",
155
+ "customMessage": "Your Custom Message",
156
+ "items": [
157
+ {
158
+ "name": "Premium Subscription",
159
+ "amount": 5000,
160
+ "quantity": 1
161
+ }
162
+ ]
163
+ }
164
+
165
+ response = sdk.pay(payment_request)
166
+ print(response.get('url'))
167
+
168
+ except Exception as e:
169
+ print(e)
170
+ ```
171
+
172
+ ### 5. Verify Callback (Webhook)
173
+
174
+ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
175
+
176
+ **Incoming Headers**
177
+
178
+ | Field Name | Type | Required | Description |
179
+ | :--- | :--- | :--- | :--- |
180
+ | `Content-Type` | `str` | Yes | `application/json` |
181
+ | `X-Mmpay-Signature` | `str` | Yes | Generated HMAC signature |
182
+ | `X-Mmpay-Nonce` | `str` | Yes | Unique nonce string |
183
+
184
+ **Example Verification (Flask)**
185
+
186
+
187
+ ```python
188
+ from flask import request
189
+
190
+ @app.route('/webhook/mmpay', methods=['POST'])
191
+ def mmpay_webhook():
192
+ payload_str = request.data.decode('utf-8')
193
+ nonce = request.headers.get('X-Mmpay-Nonce')
194
+ signature = request.headers.get('X-Mmpay-Signature')
195
+
196
+ try:
197
+ is_valid = sdk.verify_cb(payload_str, nonce, signature)
198
+
199
+ if is_valid:
200
+ return "Verified", 200
201
+ else:
202
+ return "Invalid Signature", 400
203
+
204
+ except ValueError as e:
205
+ return str(e), 400
206
+ ```
207
+
208
+ ## Error Codes
209
+
210
+ ### API Key Layer (SERVER SDK)
211
+
212
+ | Code | Description |
213
+ | :--- | :--- |
214
+ | `KA0001` | Bearer Token Not Included |
215
+ | `KA0002` | API Key Not 'LIVE' |
216
+ | `KA0003` | Signature mismatch |
217
+ | `KA0004` | Internal Server Error |
218
+ | `KA0005` | IP Not whitelisted |
219
+ | `429` | Rate limit hit (1000 req/min) |
220
+
221
+ ### JWT Layer (SERVER SDK)
222
+
223
+ | Code | Description |
224
+ | :--- | :--- |
225
+ | `BA001` | `Btoken` nonce token missing |
226
+ | `BA002` | `Btoken` nonce mismatch |
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,205 @@
1
+ # MMPay Python SDK
2
+
3
+ A Python client library for integrating with the MMPay Payment Gateway. This SDK is a direct port of the official Node.js SDK, providing utilities for payment creation, transaction retrieval, handshake authentication, and callback verification for technical architects and developers.
4
+
5
+ ## Features
6
+
7
+ - Sandbox & Production Support: Dedicated methods for both environments.
8
+ - Payment Creation & Retrieval: Endpoints to create payments and fetch transaction statuses.
9
+ - HMAC SHA256 Signing: Automatic signature generation for request integrity.
10
+ - Callback Verification: Utility to verify incoming webhooks from MMPay.
11
+ - Type Definitions: Includes TypedDict definitions for clear payload structuring.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install mmpay-python-sdk
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ To use the SDK, you need your App ID, Publishable Key, and Secret Key provided by the MyanMyanPay dashboard.
22
+
23
+ | Parameter | Type | Required | Description |
24
+ | :--- | :--- | :--- | :--- |
25
+ | appId | str | Yes | Your unique Application ID. |
26
+ | publishableKey | str | Yes | Public key for authentication. |
27
+ | secretKey | str | Yes | Private key used for signing requests (HMAC). |
28
+ | apiBaseUrl | str | Yes | The base URL for the MMPay API. |
29
+
30
+
31
+ ```python
32
+ from mmpay import MMPaySDK
33
+
34
+ options = {
35
+ "appId": "YOUR_APP_ID",
36
+ "publishableKey": "YOUR_PUBLISHABLE_KEY",
37
+ "secretKey": "YOUR_SECRET_KEY",
38
+ "apiBaseUrl": "https://xxx.myanmyanpay.com"
39
+ }
40
+
41
+ sdk = MMPaySDK(options)
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### 1. Payment Request Payload
47
+
48
+ Passed to `sdk.pay(params)` or `sdk.sandbox_pay(params)`.
49
+
50
+ | Parameter | Type | Required | Description |
51
+ | :--- | :--- | :--- | :--- |
52
+ | `orderId` | `str` | Yes | Unique identifier for the order (e.g., "ORD-001"). |
53
+ | `amount` | `float` | Yes | Total transaction amount. |
54
+ | `items` | `List[Item]` | No | A list of items included in the order. |
55
+ | `callbackUrl` | `str` | No | URL where the webhook callback will be sent. |
56
+ | `customMessage` | `str` | No | Custom message to be attached to the transaction. |
57
+
58
+ **Item Object**
59
+
60
+ Used inside the `items` list of a Payment Request.
61
+
62
+ | Parameter | Type | Required | Description |
63
+ | :--- | :--- | :--- | :--- |
64
+ | `name` | `str` | Yes | Name of the product/service. |
65
+ | `amount` | `float` | Yes | Price per unit. |
66
+ | `quantity` | `int` | Yes | Quantity of the item. |
67
+
68
+ ### 2. Create a Payment (Sandbox)
69
+
70
+
71
+ ```python
72
+ try:
73
+ payment_request = {
74
+ "orderId": "ORD-SANDBOX-001",
75
+ "amount": 5000,
76
+ "callbackUrl": "https://your-site.com/webhook/mmpay",
77
+ "customMessage": "Your Custom Message",
78
+ "items": [
79
+ {
80
+ "name": "Premium Subscription",
81
+ "amount": 5000,
82
+ "quantity": 1
83
+ }
84
+ ]
85
+ }
86
+
87
+ response = sdk.sandbox_pay(payment_request)
88
+ print(response)
89
+
90
+ except Exception as e:
91
+ print(e)
92
+ ```
93
+
94
+ ### 3. Retrieve a Payment
95
+
96
+ ```python
97
+ #(Sandbox)
98
+ try:
99
+ get_request = {
100
+ "orderId": "ORD-SANDBOX-001"
101
+ }
102
+
103
+ response = sdk.sandbox_get(get_request)
104
+ print(response)
105
+
106
+ except Exception as e:
107
+ print(e)
108
+ ```
109
+
110
+
111
+ ```python
112
+ #(Production)
113
+ try:
114
+ get_request = {
115
+ "orderId": "ORD-SANDBOX-001"
116
+ }
117
+
118
+ response = sdk.get(get_request)
119
+ print(response)
120
+ ```
121
+
122
+ ### 4. Create a Payment (Production)
123
+
124
+ ```python
125
+ try:
126
+ payment_request = {
127
+ "orderId": "ORD-LIVE-98765",
128
+ "amount": 5000,
129
+ "callbackUrl": "https://your-site.com/webhook/mmpay",
130
+ "customMessage": "Your Custom Message",
131
+ "items": [
132
+ {
133
+ "name": "Premium Subscription",
134
+ "amount": 5000,
135
+ "quantity": 1
136
+ }
137
+ ]
138
+ }
139
+
140
+ response = sdk.pay(payment_request)
141
+ print(response.get('url'))
142
+
143
+ except Exception as e:
144
+ print(e)
145
+ ```
146
+
147
+ ### 5. Verify Callback (Webhook)
148
+
149
+ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
150
+
151
+ **Incoming Headers**
152
+
153
+ | Field Name | Type | Required | Description |
154
+ | :--- | :--- | :--- | :--- |
155
+ | `Content-Type` | `str` | Yes | `application/json` |
156
+ | `X-Mmpay-Signature` | `str` | Yes | Generated HMAC signature |
157
+ | `X-Mmpay-Nonce` | `str` | Yes | Unique nonce string |
158
+
159
+ **Example Verification (Flask)**
160
+
161
+
162
+ ```python
163
+ from flask import request
164
+
165
+ @app.route('/webhook/mmpay', methods=['POST'])
166
+ def mmpay_webhook():
167
+ payload_str = request.data.decode('utf-8')
168
+ nonce = request.headers.get('X-Mmpay-Nonce')
169
+ signature = request.headers.get('X-Mmpay-Signature')
170
+
171
+ try:
172
+ is_valid = sdk.verify_cb(payload_str, nonce, signature)
173
+
174
+ if is_valid:
175
+ return "Verified", 200
176
+ else:
177
+ return "Invalid Signature", 400
178
+
179
+ except ValueError as e:
180
+ return str(e), 400
181
+ ```
182
+
183
+ ## Error Codes
184
+
185
+ ### API Key Layer (SERVER SDK)
186
+
187
+ | Code | Description |
188
+ | :--- | :--- |
189
+ | `KA0001` | Bearer Token Not Included |
190
+ | `KA0002` | API Key Not 'LIVE' |
191
+ | `KA0003` | Signature mismatch |
192
+ | `KA0004` | Internal Server Error |
193
+ | `KA0005` | IP Not whitelisted |
194
+ | `429` | Rate limit hit (1000 req/min) |
195
+
196
+ ### JWT Layer (SERVER SDK)
197
+
198
+ | Code | Description |
199
+ | :--- | :--- |
200
+ | `BA001` | `Btoken` nonce token missing |
201
+ | `BA002` | `Btoken` nonce mismatch |
202
+
203
+ ## License
204
+
205
+ MIT
@@ -5,25 +5,28 @@ import hashlib
5
5
  import requests
6
6
  from typing import List, Optional, TypedDict, Dict, Any, Union
7
7
 
8
- # --- Type Definitions ---
9
-
10
8
  class Item(TypedDict):
11
9
  name: str
12
10
  amount: float
13
11
  quantity: int
14
12
 
15
- class PaymentRequest(TypedDict, total=False):
13
+ class _PaymentRequestRequired(TypedDict):
16
14
  orderId: str
17
15
  amount: float
18
- items: Optional[List[Item]]
19
- currency: Optional[str]
20
- callbackUrl: Optional[str]
21
- customMessage: Optional[str] # Added to match TS
22
16
 
23
- class XPaymentRequest(PaymentRequest, total=False):
17
+ class PaymentRequest(_PaymentRequestRequired, total=False):
18
+ items: List[Item]
19
+ currency: str
20
+ callbackUrl: str
21
+ customMessage: str
22
+
23
+ class _XPaymentRequestRequired(TypedDict):
24
24
  appId: str
25
25
  nonce: str
26
26
 
27
+ class XPaymentRequest(PaymentRequest, _XPaymentRequestRequired):
28
+ pass
29
+
27
30
  class HandShakeRequest(TypedDict):
28
31
  orderId: str
29
32
  nonce: str
@@ -49,25 +52,40 @@ class SDKOptions(TypedDict):
49
52
  secretKey: str
50
53
  apiBaseUrl: str
51
54
 
52
- # --- Main SDK Class ---
55
+ class _PayGetRequestRequired(TypedDict):
56
+ orderId: str
57
+
58
+ class PayGetRequest(_PayGetRequestRequired, total=False):
59
+ nonce: str
60
+
61
+ class PayGetResponse(TypedDict, total=False):
62
+ appId: str
63
+ orderId: str
64
+ amount: float
65
+ vendor: str
66
+ method: str
67
+ customMessage: str
68
+ callbackUrl: str
69
+ callbackUrlStatus: str
70
+ callbackAt: str
71
+ disbursementId: str
72
+ disStatus: str
73
+ status: str
74
+ condition: str
75
+ createdAt: str
76
+ transactionRefId: str
77
+ qr: str
78
+ url: str
53
79
 
54
80
  class MMPaySDK:
55
81
  def __init__(self, options: SDKOptions):
56
- """
57
- Initializes the SDK with the merchant's keys and the API endpoint.
58
- """
59
82
  self._app_id = options['appId']
60
83
  self._publishable_key = options['publishableKey']
61
84
  self._secret_key = options['secretKey']
62
- # Remove trailing slash if present to prevent double slashes in endpoints
63
85
  self._api_base_url = options['apiBaseUrl'].rstrip('/')
64
86
  self._btoken: Optional[str] = None
65
87
 
66
88
  def _generate_signature(self, body_string: str, nonce: str) -> str:
67
- """
68
- Generates an HMAC SHA256 signature for request integrity.
69
- Matches: CryptoJS.HmacSHA256(nonce + "." + bodyString, secret)
70
- """
71
89
  string_to_sign = f"{nonce}.{body_string}"
72
90
  return hmac.new(
73
91
  self._secret_key.encode('utf-8'),
@@ -76,18 +94,11 @@ class MMPaySDK:
76
94
  ).hexdigest()
77
95
 
78
96
  def _get_nonce(self) -> str:
79
- """Helper to get current timestamp as string (milliseconds)"""
80
97
  return str(int(time.time() * 1000))
81
98
 
82
99
  def _json_stringify(self, data: Any) -> str:
83
- """
84
- Mimics JS JSON.stringify exactly (no spaces).
85
- Crucial for signature verification.
86
- """
87
100
  return json.dumps(data, separators=(',', ':'))
88
101
 
89
- # --- Sandbox Methods ---
90
-
91
102
  def sandbox_handshake(self, payload: HandShakeRequest) -> Union[HandShakeResponse, Dict[str, Any]]:
92
103
  endpoint = f"{self._api_base_url}/payments/sandbox-handshake"
93
104
  nonce = self._get_nonce()
@@ -116,9 +127,6 @@ class MMPaySDK:
116
127
  endpoint = f"{self._api_base_url}/payments/sandbox-create"
117
128
  nonce = self._get_nonce()
118
129
 
119
- # Construct payload
120
- # Note: We manually build the dict to ensure we don't include None/null
121
- # for optional fields, which would break the signature vs JS stringify.
122
130
  xpayload: Dict[str, Any] = {
123
131
  "appId": self._app_id,
124
132
  "nonce": nonce,
@@ -126,17 +134,16 @@ class MMPaySDK:
126
134
  "orderId": params['orderId'],
127
135
  }
128
136
 
129
- if params.get('items'):
137
+ if 'items' in params:
130
138
  xpayload['items'] = params['items']
131
- if params.get('callbackUrl'):
139
+ if 'callbackUrl' in params:
132
140
  xpayload['callbackUrl'] = params['callbackUrl']
133
- if params.get('customMessage'):
141
+ if 'customMessage' in params:
134
142
  xpayload['customMessage'] = params['customMessage']
135
143
 
136
144
  body_string = self._json_stringify(xpayload)
137
145
  signature = self._generate_signature(body_string, nonce)
138
146
 
139
- # Perform handshake first (using the nonce from the payload logic as per TS)
140
147
  handshake_payload: HandShakeRequest = {
141
148
  'orderId': str(xpayload['orderId']),
142
149
  'nonce': str(xpayload['nonce'])
@@ -148,7 +155,7 @@ class MMPaySDK:
148
155
 
149
156
  headers = {
150
157
  'Authorization': f"Bearer {self._publishable_key}",
151
- 'X-Mmpay-Btoken': self._btoken,
158
+ 'X-Mmpay-Btoken': self._btoken or '',
152
159
  'X-Mmpay-Nonce': nonce,
153
160
  'X-Mmpay-Signature': signature,
154
161
  'Content-Type': 'application/json',
@@ -161,7 +168,41 @@ class MMPaySDK:
161
168
  except requests.exceptions.RequestException as e:
162
169
  return {"error": str(e), "details": getattr(e.response, 'text', '')}
163
170
 
164
- # --- Production Methods ---
171
+ def sandbox_get(self, params: PayGetRequest) -> Union[PayGetResponse, Dict[str, Any]]:
172
+ endpoint = f"{self._api_base_url}/payments/sandbox-get"
173
+ nonce = self._get_nonce()
174
+
175
+ xpayload: Dict[str, Any] = {
176
+ "orderId": params['orderId'],
177
+ "nonce": nonce
178
+ }
179
+
180
+ body_string = self._json_stringify(xpayload)
181
+ signature = self._generate_signature(body_string, nonce)
182
+
183
+ handshake_payload: HandShakeRequest = {
184
+ 'orderId': str(xpayload['orderId']),
185
+ 'nonce': str(xpayload['nonce'])
186
+ }
187
+
188
+ handshake_res = self.sandbox_handshake(handshake_payload)
189
+ if 'error' in handshake_res:
190
+ return handshake_res
191
+
192
+ headers = {
193
+ 'Authorization': f"Bearer {self._publishable_key}",
194
+ 'X-Mmpay-Btoken': self._btoken or '',
195
+ 'X-Mmpay-Nonce': nonce,
196
+ 'X-Mmpay-Signature': signature,
197
+ 'Content-Type': 'application/json',
198
+ }
199
+
200
+ try:
201
+ response = requests.post(endpoint, data=body_string, headers=headers)
202
+ response.raise_for_status()
203
+ return response.json()
204
+ except requests.exceptions.RequestException as e:
205
+ return {"error": str(e), "details": getattr(e.response, 'text', '')}
165
206
 
166
207
  def handshake(self, payload: HandShakeRequest) -> Union[HandShakeResponse, Dict[str, Any]]:
167
208
  endpoint = f"{self._api_base_url}/payments/handshake"
@@ -198,17 +239,16 @@ class MMPaySDK:
198
239
  "orderId": params['orderId'],
199
240
  }
200
241
 
201
- if params.get('items'):
242
+ if 'items' in params:
202
243
  xpayload['items'] = params['items']
203
- if params.get('callbackUrl'):
244
+ if 'callbackUrl' in params:
204
245
  xpayload['callbackUrl'] = params['callbackUrl']
205
- if params.get('customMessage'):
246
+ if 'customMessage' in params:
206
247
  xpayload['customMessage'] = params['customMessage']
207
248
 
208
249
  body_string = self._json_stringify(xpayload)
209
250
  signature = self._generate_signature(body_string, nonce)
210
251
 
211
- # Perform handshake
212
252
  handshake_payload: HandShakeRequest = {
213
253
  'orderId': str(xpayload['orderId']),
214
254
  'nonce': str(xpayload['nonce'])
@@ -220,7 +260,7 @@ class MMPaySDK:
220
260
 
221
261
  headers = {
222
262
  'Authorization': f"Bearer {self._publishable_key}",
223
- 'X-Mmpay-Btoken': self._btoken,
263
+ 'X-Mmpay-Btoken': self._btoken or '',
224
264
  'X-Mmpay-Nonce': nonce,
225
265
  'X-Mmpay-Signature': signature,
226
266
  'Content-Type': 'application/json',
@@ -233,12 +273,43 @@ class MMPaySDK:
233
273
  except requests.exceptions.RequestException as e:
234
274
  return {"error": str(e), "details": getattr(e.response, 'text', '')}
235
275
 
236
- # --- Verification ---
276
+ def get(self, params: PayGetRequest) -> Union[PayGetResponse, Dict[str, Any]]:
277
+ endpoint = f"{self._api_base_url}/payments/get"
278
+ nonce = self._get_nonce()
279
+
280
+ xpayload: Dict[str, Any] = {
281
+ "orderId": params['orderId'],
282
+ "nonce": nonce
283
+ }
284
+
285
+ body_string = self._json_stringify(xpayload)
286
+ signature = self._generate_signature(body_string, nonce)
287
+
288
+ handshake_payload: HandShakeRequest = {
289
+ 'orderId': str(xpayload['orderId']),
290
+ 'nonce': str(xpayload['nonce'])
291
+ }
292
+
293
+ handshake_res = self.handshake(handshake_payload)
294
+ if 'error' in handshake_res:
295
+ return handshake_res
296
+
297
+ headers = {
298
+ 'Authorization': f"Bearer {self._publishable_key}",
299
+ 'X-Mmpay-Btoken': self._btoken or '',
300
+ 'X-Mmpay-Nonce': nonce,
301
+ 'X-Mmpay-Signature': signature,
302
+ 'Content-Type': 'application/json',
303
+ }
304
+
305
+ try:
306
+ response = requests.post(endpoint, data=body_string, headers=headers)
307
+ response.raise_for_status()
308
+ return response.json()
309
+ except requests.exceptions.RequestException as e:
310
+ return {"error": str(e), "details": getattr(e.response, 'text', '')}
237
311
 
238
312
  def verify_cb(self, payload: str, nonce: str, expected_signature: str) -> bool:
239
- """
240
- Verifies the signature of a callback request.
241
- """
242
313
  if not payload or not nonce or not expected_signature:
243
314
  raise ValueError("Callback verification failed: Missing payload, nonce, or signature.")
244
315
 
@@ -250,7 +321,6 @@ class MMPaySDK:
250
321
  ).hexdigest()
251
322
 
252
323
  if generated_signature != expected_signature:
253
- # Using print for logging as per TS console.error
254
324
  print(f"Signature mismatch: gen={generated_signature}, exp={expected_signature}")
255
325
  return False
256
326
 
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: mmpay-python-sdk
3
+ Version: 0.1.2
4
+ Summary: Python SDK for MyanMyanPay
5
+ Home-page: https://github.com/nawing/MMPay-Python-SDK
6
+ Author: Naw Ing
7
+ Author-email: nawing@myanmyanpay.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ # MMPay Python SDK
27
+
28
+ A Python client library for integrating with the MMPay Payment Gateway. This SDK is a direct port of the official Node.js SDK, providing utilities for payment creation, transaction retrieval, handshake authentication, and callback verification for technical architects and developers.
29
+
30
+ ## Features
31
+
32
+ - Sandbox & Production Support: Dedicated methods for both environments.
33
+ - Payment Creation & Retrieval: Endpoints to create payments and fetch transaction statuses.
34
+ - HMAC SHA256 Signing: Automatic signature generation for request integrity.
35
+ - Callback Verification: Utility to verify incoming webhooks from MMPay.
36
+ - Type Definitions: Includes TypedDict definitions for clear payload structuring.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install mmpay-python-sdk
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ To use the SDK, you need your App ID, Publishable Key, and Secret Key provided by the MyanMyanPay dashboard.
47
+
48
+ | Parameter | Type | Required | Description |
49
+ | :--- | :--- | :--- | :--- |
50
+ | appId | str | Yes | Your unique Application ID. |
51
+ | publishableKey | str | Yes | Public key for authentication. |
52
+ | secretKey | str | Yes | Private key used for signing requests (HMAC). |
53
+ | apiBaseUrl | str | Yes | The base URL for the MMPay API. |
54
+
55
+
56
+ ```python
57
+ from mmpay import MMPaySDK
58
+
59
+ options = {
60
+ "appId": "YOUR_APP_ID",
61
+ "publishableKey": "YOUR_PUBLISHABLE_KEY",
62
+ "secretKey": "YOUR_SECRET_KEY",
63
+ "apiBaseUrl": "https://xxx.myanmyanpay.com"
64
+ }
65
+
66
+ sdk = MMPaySDK(options)
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ### 1. Payment Request Payload
72
+
73
+ Passed to `sdk.pay(params)` or `sdk.sandbox_pay(params)`.
74
+
75
+ | Parameter | Type | Required | Description |
76
+ | :--- | :--- | :--- | :--- |
77
+ | `orderId` | `str` | Yes | Unique identifier for the order (e.g., "ORD-001"). |
78
+ | `amount` | `float` | Yes | Total transaction amount. |
79
+ | `items` | `List[Item]` | No | A list of items included in the order. |
80
+ | `callbackUrl` | `str` | No | URL where the webhook callback will be sent. |
81
+ | `customMessage` | `str` | No | Custom message to be attached to the transaction. |
82
+
83
+ **Item Object**
84
+
85
+ Used inside the `items` list of a Payment Request.
86
+
87
+ | Parameter | Type | Required | Description |
88
+ | :--- | :--- | :--- | :--- |
89
+ | `name` | `str` | Yes | Name of the product/service. |
90
+ | `amount` | `float` | Yes | Price per unit. |
91
+ | `quantity` | `int` | Yes | Quantity of the item. |
92
+
93
+ ### 2. Create a Payment (Sandbox)
94
+
95
+
96
+ ```python
97
+ try:
98
+ payment_request = {
99
+ "orderId": "ORD-SANDBOX-001",
100
+ "amount": 5000,
101
+ "callbackUrl": "https://your-site.com/webhook/mmpay",
102
+ "customMessage": "Your Custom Message",
103
+ "items": [
104
+ {
105
+ "name": "Premium Subscription",
106
+ "amount": 5000,
107
+ "quantity": 1
108
+ }
109
+ ]
110
+ }
111
+
112
+ response = sdk.sandbox_pay(payment_request)
113
+ print(response)
114
+
115
+ except Exception as e:
116
+ print(e)
117
+ ```
118
+
119
+ ### 3. Retrieve a Payment
120
+
121
+ ```python
122
+ #(Sandbox)
123
+ try:
124
+ get_request = {
125
+ "orderId": "ORD-SANDBOX-001"
126
+ }
127
+
128
+ response = sdk.sandbox_get(get_request)
129
+ print(response)
130
+
131
+ except Exception as e:
132
+ print(e)
133
+ ```
134
+
135
+
136
+ ```python
137
+ #(Production)
138
+ try:
139
+ get_request = {
140
+ "orderId": "ORD-SANDBOX-001"
141
+ }
142
+
143
+ response = sdk.get(get_request)
144
+ print(response)
145
+ ```
146
+
147
+ ### 4. Create a Payment (Production)
148
+
149
+ ```python
150
+ try:
151
+ payment_request = {
152
+ "orderId": "ORD-LIVE-98765",
153
+ "amount": 5000,
154
+ "callbackUrl": "https://your-site.com/webhook/mmpay",
155
+ "customMessage": "Your Custom Message",
156
+ "items": [
157
+ {
158
+ "name": "Premium Subscription",
159
+ "amount": 5000,
160
+ "quantity": 1
161
+ }
162
+ ]
163
+ }
164
+
165
+ response = sdk.pay(payment_request)
166
+ print(response.get('url'))
167
+
168
+ except Exception as e:
169
+ print(e)
170
+ ```
171
+
172
+ ### 5. Verify Callback (Webhook)
173
+
174
+ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
175
+
176
+ **Incoming Headers**
177
+
178
+ | Field Name | Type | Required | Description |
179
+ | :--- | :--- | :--- | :--- |
180
+ | `Content-Type` | `str` | Yes | `application/json` |
181
+ | `X-Mmpay-Signature` | `str` | Yes | Generated HMAC signature |
182
+ | `X-Mmpay-Nonce` | `str` | Yes | Unique nonce string |
183
+
184
+ **Example Verification (Flask)**
185
+
186
+
187
+ ```python
188
+ from flask import request
189
+
190
+ @app.route('/webhook/mmpay', methods=['POST'])
191
+ def mmpay_webhook():
192
+ payload_str = request.data.decode('utf-8')
193
+ nonce = request.headers.get('X-Mmpay-Nonce')
194
+ signature = request.headers.get('X-Mmpay-Signature')
195
+
196
+ try:
197
+ is_valid = sdk.verify_cb(payload_str, nonce, signature)
198
+
199
+ if is_valid:
200
+ return "Verified", 200
201
+ else:
202
+ return "Invalid Signature", 400
203
+
204
+ except ValueError as e:
205
+ return str(e), 400
206
+ ```
207
+
208
+ ## Error Codes
209
+
210
+ ### API Key Layer (SERVER SDK)
211
+
212
+ | Code | Description |
213
+ | :--- | :--- |
214
+ | `KA0001` | Bearer Token Not Included |
215
+ | `KA0002` | API Key Not 'LIVE' |
216
+ | `KA0003` | Signature mismatch |
217
+ | `KA0004` | Internal Server Error |
218
+ | `KA0005` | IP Not whitelisted |
219
+ | `429` | Rate limit hit (1000 req/min) |
220
+
221
+ ### JWT Layer (SERVER SDK)
222
+
223
+ | Code | Description |
224
+ | :--- | :--- |
225
+ | `BA001` | `Btoken` nonce token missing |
226
+ | `BA002` | `Btoken` nonce mismatch |
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,29 @@
1
+ from setuptools import setup, find_packages
2
+ import pathlib
3
+
4
+ # Get the long description from the README file
5
+ here = pathlib.Path(__file__).parent.resolve()
6
+ long_description = (here / "README.md").read_text(encoding="utf-8")
7
+
8
+ setup(
9
+ name="mmpay-python-sdk",
10
+ version="0.1.2",
11
+ description="Python SDK for MyanMyanPay",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ author="Naw Ing",
15
+ author_email="nawing@myanmyanpay.com",
16
+ url="https://github.com/nawing/MMPay-Python-SDK", # Link to your repo
17
+ packages=find_packages(),
18
+ install_requires=[
19
+ "requests",
20
+ ],
21
+ classifiers=[
22
+ "Programming Language :: Python :: 3",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Office/Business :: Financial :: Point-Of-Sale",
27
+ ],
28
+ python_requires=">=3.6",
29
+ )
@@ -4,8 +4,8 @@ import random
4
4
  import string
5
5
  from dotenv import load_dotenv
6
6
 
7
- from mmpay import MMPaySDK
8
- # from mmpay_sdk import MMPaySDK # Assumes the SDK is saved in mmpay_sdk.py
7
+ # from mmpay import MMPaySDK
8
+ from mmpay import MMPaySDK
9
9
 
10
10
  # Load environment variables from .env file
11
11
  load_dotenv()
@@ -1,11 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mmpay-python-sdk
3
- Version: 0.1.1
4
- Summary: Python SDK for MMPay (Ported from JS)
5
- Author: MyanMyanPay
6
- Requires-Python: >=3.6
7
- Requires-Dist: requests
8
- Dynamic: author
9
- Dynamic: requires-dist
10
- Dynamic: requires-python
11
- Dynamic: summary
@@ -1,218 +0,0 @@
1
-
2
- # MMPay Python SDK
3
-
4
- A Python client library for integrating with the MMPay Payment Gateway. This SDK is a direct port of the official Node.js SDK, providing utilities for payment creation, handshake authentication, and callback verification.
5
-
6
- ## Features
7
-
8
- - **Sandbox & Production Support**: dedicated methods for both environments.
9
- - **HMAC SHA256 Signing**: Automatic signature generation for request integrity.
10
- - **Callback Verification**: Utility to verify incoming webhooks from MMPay.
11
- - **Type Definitions**: Includes `TypedDict` definitions for clear payload structuring.
12
-
13
- ## Installation
14
-
15
- Install the package via pip:
16
-
17
- ```bash
18
- pip install mmpay-python-sdk
19
- ```
20
-
21
- ## Configuration
22
-
23
- To use the SDK, you need your **App ID**, **Publishable Key**, and **Secret Key** provided by the MMPay dashboard.
24
-
25
- Used when instantiating `MMPaySDK(options)`.
26
-
27
- | Parameter | Type | Required | Description |
28
- | :--- | :--- | :--- | :--- |
29
- | `appId` | `str` | Yes | Your unique Application ID. |
30
- | `publishableKey` | `str` | Yes | Public key for authentication. |
31
- | `secretKey` | `str` | Yes | Private key used for signing requests (HMAC). |
32
- | `apiBaseUrl` | `str` | Yes | The base URL for the MMPay API. |
33
-
34
- ```python
35
- from mmpay import MMPaySDK
36
-
37
- # Initialize the SDK
38
- options = {
39
- "appId": "YOUR_APP_ID",
40
- "publishableKey": "YOUR_PUBLISHABLE_KEY",
41
- "secretKey": "YOUR_SECRET_KEY",
42
- "apiBaseUrl": "[https://xxx.myanmyanpay.com](https://xxx.myanmyanpay.com)" # Replace with actual API Base URL [ Register With Us]
43
- }
44
-
45
- sdk = MMPaySDK(options)
46
- ```
47
-
48
-
49
-
50
-
51
- ## Usage
52
-
53
- ### 1. Create a Payment (Sandbox)
54
-
55
- Use `sandbox_pay` to create a payment order in the Sandbox environment. This handles the handshake and signature generation automatically.
56
-
57
-
58
- ### 1. Payment Request (`pay` / `sandbox_pay`)
59
-
60
- Passed to `sdk.pay(params)` or `sdk.sandbox_pay(params)`.
61
-
62
- | Parameter | Type | Required | Description |
63
- | :--- | :--- | :--- | :--- |
64
- | `orderId` | `str` | Yes | Unique identifier for the order (e.g., "ORD-001"). |
65
- | `amount` | `number` | Yes | Total transaction amount. |
66
- | `items` | `List[Item]` | Yes | A list of items included in the order (see table below). |
67
- | `callbackUrl` | `str` | No | URL where the webhook callback will be sent. |
68
- | `customMessage` | `str` | No | URL where the webhook callback will be sent. |
69
-
70
- ### 2. Item Object
71
-
72
- Used inside the `items` list of a Payment Request.
73
-
74
- | Parameter | Type | Required | Description |
75
- | :--- | :--- | :--- | :--- |
76
- | `name` | `str` | Yes | Name of the product or service. |
77
- | `amount` | `number` | Yes | Price per unit. |
78
- | `quantity` | `int` | Yes | Quantity of the item. |
79
-
80
-
81
- ```python
82
- try:
83
- payment_request = {
84
- "orderId": "ORD-SANDBOX-001",
85
- "amount": 5000, # Amount in minor units (e.g., cents) or as required
86
- "callbackUrl": "https://your-site.com/webhook/mmpay", # Optional
87
- "customMessage": "Your Custom Messages", # Optional
88
- "items": [ # Optional
89
- {
90
- "name": "Premium Subscription",
91
- "amount": 5000,
92
- "quantity": 1
93
- }
94
- ]
95
- }
96
-
97
- response = sdk.sandbox_pay(payment_request)
98
- print("Payment Response:", response)
99
-
100
- except Exception as e:
101
- print("Error creating payment:", e)
102
- ```
103
-
104
- ### 2. Create a Payment (Production)
105
-
106
- For production environments, use the `pay` method.
107
-
108
- ```python
109
- try:
110
- payment_request = {
111
- "orderId": "ORD-LIVE-98765",
112
- "amount": 5000, # Amount in minor units (e.g., cents) or as required
113
- "callbackUrl": "https://your-site.com/webhook/mmpay", # Optional
114
- "customMessage": "Your Custom Messages", # Optional
115
- "items": [ # Optional
116
- {
117
- "name": "Premium Subscription",
118
- "amount": 5000,
119
- "quantity": 1
120
- }
121
- ]
122
- }
123
-
124
- # Helper automatically handles the handshake and signing
125
- response = sdk.pay(payment_request)
126
- print("Production Payment URL:", response.get('url'))
127
-
128
- except Exception as e:
129
- print("Error:", e)
130
- ```
131
-
132
- ### 3. Verify Callback (Webhook)
133
-
134
- When MMPay sends a callback to your `callbackUrl`, you must verify the request signature to ensure it is genuine. If not provide, it will fallback to default cb URL provided in your console
135
-
136
- **Handling callbacks**
137
-
138
- Incoming HTTP POST Parameters
139
-
140
- Header
141
-
142
- | Field Name | Type | Required | Description |
143
- | :--- | :--- | :--- | :--- |
144
- | **Content-Type** | `string` | Yes | 'application/json' |
145
- | **X-Mmpay-Signature** | `string` | Yes | '34834890vfgh9hnf94irfg_48932i4rt90349849' |
146
- | **X-Mmpay-Nonce** | `string` | Yes | '94843943949349' |
147
-
148
- Body
149
-
150
- | Field Name | Type | Required | Description |
151
- | :--- | :--- | :--- | :--- |
152
- | **orderId** | `string` | Yes | Unique identifier for the specific order. |
153
- | **amount** | `number` | Yes | The transaction amount. |
154
- | **currency** | `string` | Yes | The 3-letter currency code (e.g., MMK, USD). |
155
- | **vendor** | `string` | Yes | Identifier for the vendor initiating the request. |
156
- | **method** | `'QR', 'PIN', 'PWA', 'CARD'` | Yes | Identifier for the method. |
157
- | **status** | `'PENDING','SUCCESS','FAILED','REFUNDED'` | Yes | Current status of the transaction. |
158
- | **transactionRefId** | `string` | Yes | The reference ID generated by the payment provider. |
159
- | **callbackUrl** | `string` | No | Optional URL to receive webhooks or updates. |
160
- | **customMessage** | `string` | No | User provided custom message |
161
-
162
-
163
- **Example using Flask:**
164
-
165
- ```python
166
- from flask import request
167
-
168
- @app.route('/webhook/mmpay', methods=['POST'])
169
- def mmpay_webhook():
170
- # 1. Get the raw payload body as a string (Crucial for signature check)
171
- payload_str = request.data.decode('utf-8')
172
- payload = request.data
173
-
174
- # 2. Get headers
175
- nonce = request.headers.get('X-Mmpay-Nonce')
176
- signature = request.headers.get('X-Mmpay-Signature')
177
-
178
- try:
179
- # 3. Verify
180
- is_valid = sdk.verify_cb(payload_str, nonce, signature)
181
-
182
- if is_valid:
183
- # Process the order (e.g., mark as paid in DB)
184
-
185
- return "Verified", 200
186
- else:
187
- return "Invalid Signature", 400
188
-
189
- except ValueError as e:
190
- return str(e), 400
191
- ```
192
-
193
-
194
- ### 4. Error Codes
195
-
196
- ##### Api Key Layer Authentication [SERVER SDK]
197
- | Code | Description |
198
- | :--- | :--- |
199
- | **`KA0001`** | Bearer Token Not Included In Your Request |
200
- | **`KA0002`** | API Key Not 'LIVE' |
201
- | **`KA0003`** | Signature mismatch |
202
- | **`KA0004`** | Internal Server Error ( Talk to our support immediately fot this ) |
203
- | **`KA0005`** | IP Not whitelisted |
204
- | **`429`** | Ratelimit hit only 1000 request / minute allowed |
205
-
206
-
207
- ##### JWT Layer Authentication [SERVER SDK]
208
- | Code | Description |
209
- | :--- | :--- |
210
- | **`BA001`** | `Btoken` is nonce one time token is not included |
211
- | **`BA002`** | `Btoken` one time nonce mismatch |
212
- | **`BA000`** | Internal Server Error ( Talk to our support immediately fot this ) |
213
- | **`429`** | Ratelimit hit only 1000 request / minute allowed |
214
-
215
-
216
- ## License
217
-
218
- MIT
@@ -1,11 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mmpay-python-sdk
3
- Version: 0.1.1
4
- Summary: Python SDK for MMPay (Ported from JS)
5
- Author: MyanMyanPay
6
- Requires-Python: >=3.6
7
- Requires-Dist: requests
8
- Dynamic: author
9
- Dynamic: requires-dist
10
- Dynamic: requires-python
11
- Dynamic: summary
@@ -1,13 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- setup(
4
- name="mmpay-python-sdk",
5
- version="0.1.1",
6
- description="Python SDK for MMPay (Ported from JS)",
7
- author="MyanMyanPay",
8
- packages=find_packages(),
9
- install_requires=[
10
- "requests",
11
- ],
12
- python_requires=">=3.6",
13
- )