mmpay-python-sdk 0.1.2__tar.gz → 0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmpay-python-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Python SDK for MyanMyanPay
5
5
  Home-page: https://github.com/nawing/MMPay-Python-SDK
6
6
  Author: Naw Ing
@@ -90,13 +90,12 @@ Used inside the `items` list of a Payment Request.
90
90
  | `amount` | `float` | Yes | Price per unit. |
91
91
  | `quantity` | `int` | Yes | Quantity of the item. |
92
92
 
93
- ### 2. Create a Payment (Sandbox)
94
-
93
+ ### 2. Create a Payment
95
94
 
96
95
  ```python
97
96
  try:
98
97
  payment_request = {
99
- "orderId": "ORD-SANDBOX-001",
98
+ "orderId": "ORD-LIVE-98765",
100
99
  "amount": 5000,
101
100
  "callbackUrl": "https://your-site.com/webhook/mmpay",
102
101
  "customMessage": "Your Custom Message",
@@ -109,8 +108,8 @@ try:
109
108
  ]
110
109
  }
111
110
 
112
- response = sdk.sandbox_pay(payment_request)
113
- print(response)
111
+ response = sdk.pay(payment_request)
112
+ print(response.get('url'))
114
113
 
115
114
  except Exception as e:
116
115
  print(e)
@@ -119,57 +118,96 @@ except Exception as e:
119
118
  ### 3. Retrieve a Payment
120
119
 
121
120
  ```python
122
- #(Sandbox)
123
121
  try:
124
122
  get_request = {
125
123
  "orderId": "ORD-SANDBOX-001"
126
124
  }
127
125
 
128
- response = sdk.sandbox_get(get_request)
126
+ response = sdk.get(get_request)
129
127
  print(response)
130
-
131
- except Exception as e:
132
- print(e)
133
128
  ```
134
129
 
130
+ ### 4. Handling Webhooks
135
131
 
136
- ```python
137
- #(Production)
138
- try:
139
- get_request = {
140
- "orderId": "ORD-SANDBOX-001"
141
- }
132
+ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
133
+ When you implement this method, our package automatically process the Hmac verification
142
134
 
143
- response = sdk.get(get_request)
144
- print(response)
145
- ```
135
+ **Incoming Headers**
146
136
 
147
- ### 4. Create a Payment (Production)
137
+ | Field Name | Type | Required | Description |
138
+ | :--- | :--- | :--- | :--- |
139
+ | `Content-Type` | `str` | Yes | `application/json` |
140
+ | `X-Mmpay-Signature` | `str` | Yes | Generated HMAC signature |
141
+ | `X-Mmpay-Nonce` | `str` | Yes | Unique nonce string |
148
142
 
149
143
  ```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
144
 
165
- response = sdk.pay(payment_request)
166
- print(response.get('url'))
145
+ from mmpay.client import MMPaySDK
146
+
147
+ sdk = MMPaySDK({
148
+ 'appId': 'your_app_id',
149
+ 'publishableKey': 'pk_test_12345',
150
+ 'secretKey': 'your_secret_key',
151
+ 'apiBaseUrl': 'https://api.myanmyanpay.com'
152
+ })
153
+
154
+ def handle_create(tx):
155
+ print("Created:", tx.get('orderId'))
156
+
157
+ def handle_success(tx):
158
+ print("Success:", tx.get('orderId'))
159
+
160
+ def handle_fail(tx):
161
+ print("Failed:", tx.get('orderId'))
162
+
163
+ def handle_refund(tx):
164
+ print("Refunded:", tx.get('orderId'))
165
+
166
+ def handle_cancel(tx):
167
+ print("Cancelled:", tx.get('orderId'))
168
+
169
+ def handle_expire(tx):
170
+ print("Expired:", tx.get('orderId'))
171
+
172
+ def handle_heartbeat(tx):
173
+ print("Heartbeat:", tx.get('orderId'))
174
+
175
+ def handle_unknown(tx):
176
+ print("Unknown:", tx.get('status'))
177
+
178
+ def handle_error(error):
179
+ print("Error:", error)
180
+
181
+ sdk.on_tx_create(handle_create)
182
+ sdk.on_tx_success(handle_success)
183
+ sdk.on_tx_fail(handle_fail)
184
+ sdk.on_tx_refund(handle_refund)
185
+ sdk.on_tx_cancel(handle_cancel)
186
+ sdk.on_tx_expire(handle_expire)
187
+ sdk.on_heartbeat(handle_heartbeat)
188
+ sdk.on('tx:unknown', handle_unknown)
189
+ sdk.on('error', handle_error)
190
+
191
+
192
+ @app.route('/webhooks/mmpay', methods=['POST'])
193
+ def mmpay_webhook():
194
+ payload_str = request.data.decode('utf-8')
195
+ nonce = request.headers.get('X-Mmpay-Nonce')
196
+ signature = request.headers.get('X-Mmpay-Signature')
197
+
198
+ if not nonce or not signature:
199
+ return jsonify({"error": "Missing headers"}), 400
200
+
201
+ sdk.listen(payload_str, nonce, signature)
202
+
203
+ return jsonify({"received": True}), 200
204
+
205
+ if __name__ == '__main__':
206
+ app.run(port=5000)
167
207
 
168
- except Exception as e:
169
- print(e)
170
208
  ```
171
209
 
172
- ### 5. Verify Callback (Webhook)
210
+ ### 5. Verify Callback (Manually)
173
211
 
174
212
  When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
175
213
 
@@ -187,7 +225,7 @@ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verif
187
225
  ```python
188
226
  from flask import request
189
227
 
190
- @app.route('/webhook/mmpay', methods=['POST'])
228
+ @app.route('/webhooks/mmpay', methods=['POST'])
191
229
  def mmpay_webhook():
192
230
  payload_str = request.data.decode('utf-8')
193
231
  nonce = request.headers.get('X-Mmpay-Nonce')
@@ -65,13 +65,12 @@ Used inside the `items` list of a Payment Request.
65
65
  | `amount` | `float` | Yes | Price per unit. |
66
66
  | `quantity` | `int` | Yes | Quantity of the item. |
67
67
 
68
- ### 2. Create a Payment (Sandbox)
69
-
68
+ ### 2. Create a Payment
70
69
 
71
70
  ```python
72
71
  try:
73
72
  payment_request = {
74
- "orderId": "ORD-SANDBOX-001",
73
+ "orderId": "ORD-LIVE-98765",
75
74
  "amount": 5000,
76
75
  "callbackUrl": "https://your-site.com/webhook/mmpay",
77
76
  "customMessage": "Your Custom Message",
@@ -84,8 +83,8 @@ try:
84
83
  ]
85
84
  }
86
85
 
87
- response = sdk.sandbox_pay(payment_request)
88
- print(response)
86
+ response = sdk.pay(payment_request)
87
+ print(response.get('url'))
89
88
 
90
89
  except Exception as e:
91
90
  print(e)
@@ -94,57 +93,96 @@ except Exception as e:
94
93
  ### 3. Retrieve a Payment
95
94
 
96
95
  ```python
97
- #(Sandbox)
98
96
  try:
99
97
  get_request = {
100
98
  "orderId": "ORD-SANDBOX-001"
101
99
  }
102
100
 
103
- response = sdk.sandbox_get(get_request)
101
+ response = sdk.get(get_request)
104
102
  print(response)
105
-
106
- except Exception as e:
107
- print(e)
108
103
  ```
109
104
 
105
+ ### 4. Handling Webhooks
110
106
 
111
- ```python
112
- #(Production)
113
- try:
114
- get_request = {
115
- "orderId": "ORD-SANDBOX-001"
116
- }
107
+ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
108
+ When you implement this method, our package automatically process the Hmac verification
117
109
 
118
- response = sdk.get(get_request)
119
- print(response)
120
- ```
110
+ **Incoming Headers**
121
111
 
122
- ### 4. Create a Payment (Production)
112
+ | Field Name | Type | Required | Description |
113
+ | :--- | :--- | :--- | :--- |
114
+ | `Content-Type` | `str` | Yes | `application/json` |
115
+ | `X-Mmpay-Signature` | `str` | Yes | Generated HMAC signature |
116
+ | `X-Mmpay-Nonce` | `str` | Yes | Unique nonce string |
123
117
 
124
118
  ```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
119
 
140
- response = sdk.pay(payment_request)
141
- print(response.get('url'))
120
+ from mmpay.client import MMPaySDK
121
+
122
+ sdk = MMPaySDK({
123
+ 'appId': 'your_app_id',
124
+ 'publishableKey': 'pk_test_12345',
125
+ 'secretKey': 'your_secret_key',
126
+ 'apiBaseUrl': 'https://api.myanmyanpay.com'
127
+ })
128
+
129
+ def handle_create(tx):
130
+ print("Created:", tx.get('orderId'))
131
+
132
+ def handle_success(tx):
133
+ print("Success:", tx.get('orderId'))
134
+
135
+ def handle_fail(tx):
136
+ print("Failed:", tx.get('orderId'))
137
+
138
+ def handle_refund(tx):
139
+ print("Refunded:", tx.get('orderId'))
140
+
141
+ def handle_cancel(tx):
142
+ print("Cancelled:", tx.get('orderId'))
143
+
144
+ def handle_expire(tx):
145
+ print("Expired:", tx.get('orderId'))
146
+
147
+ def handle_heartbeat(tx):
148
+ print("Heartbeat:", tx.get('orderId'))
149
+
150
+ def handle_unknown(tx):
151
+ print("Unknown:", tx.get('status'))
152
+
153
+ def handle_error(error):
154
+ print("Error:", error)
155
+
156
+ sdk.on_tx_create(handle_create)
157
+ sdk.on_tx_success(handle_success)
158
+ sdk.on_tx_fail(handle_fail)
159
+ sdk.on_tx_refund(handle_refund)
160
+ sdk.on_tx_cancel(handle_cancel)
161
+ sdk.on_tx_expire(handle_expire)
162
+ sdk.on_heartbeat(handle_heartbeat)
163
+ sdk.on('tx:unknown', handle_unknown)
164
+ sdk.on('error', handle_error)
165
+
166
+
167
+ @app.route('/webhooks/mmpay', methods=['POST'])
168
+ def mmpay_webhook():
169
+ payload_str = request.data.decode('utf-8')
170
+ nonce = request.headers.get('X-Mmpay-Nonce')
171
+ signature = request.headers.get('X-Mmpay-Signature')
172
+
173
+ if not nonce or not signature:
174
+ return jsonify({"error": "Missing headers"}), 400
175
+
176
+ sdk.listen(payload_str, nonce, signature)
177
+
178
+ return jsonify({"received": True}), 200
179
+
180
+ if __name__ == '__main__':
181
+ app.run(port=5000)
142
182
 
143
- except Exception as e:
144
- print(e)
145
183
  ```
146
184
 
147
- ### 5. Verify Callback (Webhook)
185
+ ### 5. Verify Callback (Manually)
148
186
 
149
187
  When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
150
188
 
@@ -162,7 +200,7 @@ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verif
162
200
  ```python
163
201
  from flask import request
164
202
 
165
- @app.route('/webhook/mmpay', methods=['POST'])
203
+ @app.route('/webhooks/mmpay', methods=['POST'])
166
204
  def mmpay_webhook():
167
205
  payload_str = request.data.decode('utf-8')
168
206
  nonce = request.headers.get('X-Mmpay-Nonce')
@@ -3,7 +3,7 @@ import json
3
3
  import hmac
4
4
  import hashlib
5
5
  import requests
6
- from typing import List, Optional, TypedDict, Dict, Any, Union
6
+ from typing import List, Optional, TypedDict, Dict, Any, Union, Callable
7
7
 
8
8
  class Item(TypedDict):
9
9
  name: str
@@ -83,7 +83,19 @@ class MMPaySDK:
83
83
  self._publishable_key = options['publishableKey']
84
84
  self._secret_key = options['secretKey']
85
85
  self._api_base_url = options['apiBaseUrl'].rstrip('/')
86
+ self._is_sandbox = self._publishable_key.startswith('pk_test')
86
87
  self._btoken: Optional[str] = None
88
+ self._listeners: Dict[str, List[Callable]] = {
89
+ 'tx:create': [],
90
+ 'tx:success': [],
91
+ 'tx:failed': [],
92
+ 'tx:refunded': [],
93
+ 'tx:cancel': [],
94
+ 'tx:expire': [],
95
+ 'tx:heartbeat': [],
96
+ 'tx:unknown': [],
97
+ 'error': []
98
+ }
87
99
 
88
100
  def _generate_signature(self, body_string: str, nonce: str) -> str:
89
101
  string_to_sign = f"{nonce}.{body_string}"
@@ -99,125 +111,28 @@ class MMPaySDK:
99
111
  def _json_stringify(self, data: Any) -> str:
100
112
  return json.dumps(data, separators=(',', ':'))
101
113
 
102
- def sandbox_handshake(self, payload: HandShakeRequest) -> Union[HandShakeResponse, Dict[str, Any]]:
103
- endpoint = f"{self._api_base_url}/payments/sandbox-handshake"
104
- nonce = self._get_nonce()
105
-
106
- body_string = self._json_stringify(payload)
107
- signature = self._generate_signature(body_string, nonce)
108
-
109
- headers = {
110
- 'Authorization': f"Bearer {self._publishable_key}",
111
- 'X-Mmpay-Nonce': nonce,
112
- 'X-Mmpay-Signature': signature,
113
- 'Content-Type': 'application/json',
114
- }
115
-
116
- try:
117
- response = requests.post(endpoint, data=body_string, headers=headers)
118
- response.raise_for_status()
119
- data = response.json()
120
- if 'token' in data:
121
- self._btoken = data['token']
122
- return data
123
- except requests.exceptions.RequestException as e:
124
- return {"error": str(e), "details": getattr(e.response, 'text', '')}
125
-
126
- def sandbox_pay(self, params: PaymentRequest) -> Dict[str, Any]:
127
- endpoint = f"{self._api_base_url}/payments/sandbox-create"
128
- nonce = self._get_nonce()
129
-
130
- xpayload: Dict[str, Any] = {
131
- "appId": self._app_id,
132
- "nonce": nonce,
133
- "amount": params['amount'],
134
- "orderId": params['orderId'],
135
- }
136
-
137
- if 'items' in params:
138
- xpayload['items'] = params['items']
139
- if 'callbackUrl' in params:
140
- xpayload['callbackUrl'] = params['callbackUrl']
141
- if 'customMessage' in params:
142
- xpayload['customMessage'] = params['customMessage']
143
-
144
- body_string = self._json_stringify(xpayload)
145
- signature = self._generate_signature(body_string, nonce)
146
-
147
- handshake_payload: HandShakeRequest = {
148
- 'orderId': str(xpayload['orderId']),
149
- 'nonce': str(xpayload['nonce'])
150
- }
151
-
152
- handshake_res = self.sandbox_handshake(handshake_payload)
153
- if 'error' in handshake_res:
154
- return handshake_res
155
-
156
- headers = {
157
- 'Authorization': f"Bearer {self._publishable_key}",
158
- 'X-Mmpay-Btoken': self._btoken or '',
159
- 'X-Mmpay-Nonce': nonce,
160
- 'X-Mmpay-Signature': signature,
161
- 'Content-Type': 'application/json',
162
- }
163
-
164
- try:
165
- response = requests.post(endpoint, data=body_string, headers=headers)
166
- response.raise_for_status()
167
- return response.json()
168
- except requests.exceptions.RequestException as e:
169
- return {"error": str(e), "details": getattr(e.response, 'text', '')}
170
-
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
- }
114
+ def on(self, event: str, callback: Callable) -> 'MMPaySDK':
115
+ if event in self._listeners:
116
+ self._listeners[event].append(callback)
117
+ return self
199
118
 
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', '')}
119
+ def emit(self, event: str, *args, **kwargs) -> None:
120
+ if event in self._listeners:
121
+ for cb in self._listeners[event]:
122
+ cb(*args, **kwargs)
206
123
 
207
124
  def handshake(self, payload: HandShakeRequest) -> Union[HandShakeResponse, Dict[str, Any]]:
208
- endpoint = f"{self._api_base_url}/payments/handshake"
125
+ path = "/payments/sandbox-handshake" if self._is_sandbox else "/payments/handshake"
126
+ endpoint = f"{self._api_base_url}{path}"
209
127
  nonce = self._get_nonce()
210
-
211
128
  body_string = self._json_stringify(payload)
212
129
  signature = self._generate_signature(body_string, nonce)
213
-
214
130
  headers = {
215
131
  'Authorization': f"Bearer {self._publishable_key}",
216
132
  'X-Mmpay-Nonce': nonce,
217
133
  'X-Mmpay-Signature': signature,
218
134
  'Content-Type': 'application/json',
219
135
  }
220
-
221
136
  try:
222
137
  response = requests.post(endpoint, data=body_string, headers=headers)
223
138
  response.raise_for_status()
@@ -229,35 +144,30 @@ class MMPaySDK:
229
144
  return {"error": str(e), "details": getattr(e.response, 'text', '')}
230
145
 
231
146
  def pay(self, params: PaymentRequest) -> Dict[str, Any]:
232
- endpoint = f"{self._api_base_url}/payments/create"
147
+ path = "/payments/sandbox-create" if self._is_sandbox else "/payments/create"
148
+ endpoint = f"{self._api_base_url}{path}"
233
149
  nonce = self._get_nonce()
234
-
235
150
  xpayload: Dict[str, Any] = {
236
151
  "appId": self._app_id,
237
152
  "nonce": nonce,
238
153
  "amount": params['amount'],
239
154
  "orderId": params['orderId'],
240
155
  }
241
-
242
156
  if 'items' in params:
243
157
  xpayload['items'] = params['items']
244
158
  if 'callbackUrl' in params:
245
159
  xpayload['callbackUrl'] = params['callbackUrl']
246
160
  if 'customMessage' in params:
247
161
  xpayload['customMessage'] = params['customMessage']
248
-
249
162
  body_string = self._json_stringify(xpayload)
250
163
  signature = self._generate_signature(body_string, nonce)
251
-
252
164
  handshake_payload: HandShakeRequest = {
253
165
  'orderId': str(xpayload['orderId']),
254
166
  'nonce': str(xpayload['nonce'])
255
167
  }
256
-
257
168
  handshake_res = self.handshake(handshake_payload)
258
169
  if 'error' in handshake_res:
259
170
  return handshake_res
260
-
261
171
  headers = {
262
172
  'Authorization': f"Bearer {self._publishable_key}",
263
173
  'X-Mmpay-Btoken': self._btoken or '',
@@ -265,7 +175,6 @@ class MMPaySDK:
265
175
  'X-Mmpay-Signature': signature,
266
176
  'Content-Type': 'application/json',
267
177
  }
268
-
269
178
  try:
270
179
  response = requests.post(endpoint, data=body_string, headers=headers)
271
180
  response.raise_for_status()
@@ -274,26 +183,22 @@ class MMPaySDK:
274
183
  return {"error": str(e), "details": getattr(e.response, 'text', '')}
275
184
 
276
185
  def get(self, params: PayGetRequest) -> Union[PayGetResponse, Dict[str, Any]]:
277
- endpoint = f"{self._api_base_url}/payments/get"
186
+ path = "/payments/sandbox-get" if self._is_sandbox else "/payments/get"
187
+ endpoint = f"{self._api_base_url}{path}"
278
188
  nonce = self._get_nonce()
279
-
280
189
  xpayload: Dict[str, Any] = {
281
190
  "orderId": params['orderId'],
282
191
  "nonce": nonce
283
192
  }
284
-
285
193
  body_string = self._json_stringify(xpayload)
286
194
  signature = self._generate_signature(body_string, nonce)
287
-
288
195
  handshake_payload: HandShakeRequest = {
289
196
  'orderId': str(xpayload['orderId']),
290
197
  'nonce': str(xpayload['nonce'])
291
198
  }
292
-
293
199
  handshake_res = self.handshake(handshake_payload)
294
200
  if 'error' in handshake_res:
295
201
  return handshake_res
296
-
297
202
  headers = {
298
203
  'Authorization': f"Bearer {self._publishable_key}",
299
204
  'X-Mmpay-Btoken': self._btoken or '',
@@ -301,7 +206,6 @@ class MMPaySDK:
301
206
  'X-Mmpay-Signature': signature,
302
207
  'Content-Type': 'application/json',
303
208
  }
304
-
305
209
  try:
306
210
  response = requests.post(endpoint, data=body_string, headers=headers)
307
211
  response.raise_for_status()
@@ -312,16 +216,62 @@ class MMPaySDK:
312
216
  def verify_cb(self, payload: str, nonce: str, expected_signature: str) -> bool:
313
217
  if not payload or not nonce or not expected_signature:
314
218
  raise ValueError("Callback verification failed: Missing payload, nonce, or signature.")
315
-
316
219
  string_to_sign = f"{nonce}.{payload}"
317
220
  generated_signature = hmac.new(
318
221
  self._secret_key.encode('utf-8'),
319
222
  string_to_sign.encode('utf-8'),
320
223
  hashlib.sha256
321
224
  ).hexdigest()
322
-
323
225
  if generated_signature != expected_signature:
324
- print(f"Signature mismatch: gen={generated_signature}, exp={expected_signature}")
325
226
  return False
326
-
327
- return True
227
+ return True
228
+
229
+ def listen(self, payload: str, nonce: str, expected_signature: str) -> 'MMPaySDK':
230
+ try:
231
+ is_valid = self.verify_cb(payload, nonce, expected_signature)
232
+ if not is_valid:
233
+ self.emit('error', ValueError('Signature verification failed'))
234
+ return self
235
+ tx = json.loads(payload)
236
+ status = tx.get('status')
237
+ if status == 'PENDING':
238
+ self.emit('tx:create', tx)
239
+ elif status == 'SUCCESS':
240
+ if tx.get('condition') == 'TOUCHED':
241
+ self.emit('tx:heartbeat', tx)
242
+ else:
243
+ self.emit('tx:success', tx)
244
+ elif status == 'FAILED':
245
+ self.emit('tx:failed', tx)
246
+ elif status == 'REFUNDED':
247
+ self.emit('tx:refunded', tx)
248
+ elif status == 'CANCELLED':
249
+ self.emit('tx:cancel', tx)
250
+ elif status == 'EXPIRED':
251
+ self.emit('tx:expire', tx)
252
+ else:
253
+ self.emit('tx:unknown', tx)
254
+ except Exception as err:
255
+ self.emit('error', err)
256
+ return self
257
+
258
+ def on_tx_create(self, cb: Callable) -> 'MMPaySDK':
259
+ return self.on('tx:create', cb)
260
+
261
+ def on_tx_success(self, cb: Callable) -> 'MMPaySDK':
262
+ return self.on('tx:success', cb)
263
+
264
+ def on_tx_fail(self, cb: Callable) -> 'MMPaySDK':
265
+ return self.on('tx:failed', cb)
266
+
267
+ def on_tx_refund(self, cb: Callable) -> 'MMPaySDK':
268
+ return self.on('tx:refunded', cb)
269
+
270
+ def on_tx_cancel(self, cb: Callable) -> 'MMPaySDK':
271
+ return self.on('tx:cancel', cb)
272
+
273
+ def on_tx_expire(self, cb: Callable) -> 'MMPaySDK':
274
+ return self.on('tx:expire', cb)
275
+
276
+ def on_heartbeat(self, cb: Callable) -> 'MMPaySDK':
277
+ return self.on('tx:heartbeat', cb)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmpay-python-sdk
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Python SDK for MyanMyanPay
5
5
  Home-page: https://github.com/nawing/MMPay-Python-SDK
6
6
  Author: Naw Ing
@@ -90,13 +90,12 @@ Used inside the `items` list of a Payment Request.
90
90
  | `amount` | `float` | Yes | Price per unit. |
91
91
  | `quantity` | `int` | Yes | Quantity of the item. |
92
92
 
93
- ### 2. Create a Payment (Sandbox)
94
-
93
+ ### 2. Create a Payment
95
94
 
96
95
  ```python
97
96
  try:
98
97
  payment_request = {
99
- "orderId": "ORD-SANDBOX-001",
98
+ "orderId": "ORD-LIVE-98765",
100
99
  "amount": 5000,
101
100
  "callbackUrl": "https://your-site.com/webhook/mmpay",
102
101
  "customMessage": "Your Custom Message",
@@ -109,8 +108,8 @@ try:
109
108
  ]
110
109
  }
111
110
 
112
- response = sdk.sandbox_pay(payment_request)
113
- print(response)
111
+ response = sdk.pay(payment_request)
112
+ print(response.get('url'))
114
113
 
115
114
  except Exception as e:
116
115
  print(e)
@@ -119,57 +118,96 @@ except Exception as e:
119
118
  ### 3. Retrieve a Payment
120
119
 
121
120
  ```python
122
- #(Sandbox)
123
121
  try:
124
122
  get_request = {
125
123
  "orderId": "ORD-SANDBOX-001"
126
124
  }
127
125
 
128
- response = sdk.sandbox_get(get_request)
126
+ response = sdk.get(get_request)
129
127
  print(response)
130
-
131
- except Exception as e:
132
- print(e)
133
128
  ```
134
129
 
130
+ ### 4. Handling Webhooks
135
131
 
136
- ```python
137
- #(Production)
138
- try:
139
- get_request = {
140
- "orderId": "ORD-SANDBOX-001"
141
- }
132
+ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
133
+ When you implement this method, our package automatically process the Hmac verification
142
134
 
143
- response = sdk.get(get_request)
144
- print(response)
145
- ```
135
+ **Incoming Headers**
146
136
 
147
- ### 4. Create a Payment (Production)
137
+ | Field Name | Type | Required | Description |
138
+ | :--- | :--- | :--- | :--- |
139
+ | `Content-Type` | `str` | Yes | `application/json` |
140
+ | `X-Mmpay-Signature` | `str` | Yes | Generated HMAC signature |
141
+ | `X-Mmpay-Nonce` | `str` | Yes | Unique nonce string |
148
142
 
149
143
  ```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
144
 
165
- response = sdk.pay(payment_request)
166
- print(response.get('url'))
145
+ from mmpay.client import MMPaySDK
146
+
147
+ sdk = MMPaySDK({
148
+ 'appId': 'your_app_id',
149
+ 'publishableKey': 'pk_test_12345',
150
+ 'secretKey': 'your_secret_key',
151
+ 'apiBaseUrl': 'https://api.myanmyanpay.com'
152
+ })
153
+
154
+ def handle_create(tx):
155
+ print("Created:", tx.get('orderId'))
156
+
157
+ def handle_success(tx):
158
+ print("Success:", tx.get('orderId'))
159
+
160
+ def handle_fail(tx):
161
+ print("Failed:", tx.get('orderId'))
162
+
163
+ def handle_refund(tx):
164
+ print("Refunded:", tx.get('orderId'))
165
+
166
+ def handle_cancel(tx):
167
+ print("Cancelled:", tx.get('orderId'))
168
+
169
+ def handle_expire(tx):
170
+ print("Expired:", tx.get('orderId'))
171
+
172
+ def handle_heartbeat(tx):
173
+ print("Heartbeat:", tx.get('orderId'))
174
+
175
+ def handle_unknown(tx):
176
+ print("Unknown:", tx.get('status'))
177
+
178
+ def handle_error(error):
179
+ print("Error:", error)
180
+
181
+ sdk.on_tx_create(handle_create)
182
+ sdk.on_tx_success(handle_success)
183
+ sdk.on_tx_fail(handle_fail)
184
+ sdk.on_tx_refund(handle_refund)
185
+ sdk.on_tx_cancel(handle_cancel)
186
+ sdk.on_tx_expire(handle_expire)
187
+ sdk.on_heartbeat(handle_heartbeat)
188
+ sdk.on('tx:unknown', handle_unknown)
189
+ sdk.on('error', handle_error)
190
+
191
+
192
+ @app.route('/webhooks/mmpay', methods=['POST'])
193
+ def mmpay_webhook():
194
+ payload_str = request.data.decode('utf-8')
195
+ nonce = request.headers.get('X-Mmpay-Nonce')
196
+ signature = request.headers.get('X-Mmpay-Signature')
197
+
198
+ if not nonce or not signature:
199
+ return jsonify({"error": "Missing headers"}), 400
200
+
201
+ sdk.listen(payload_str, nonce, signature)
202
+
203
+ return jsonify({"received": True}), 200
204
+
205
+ if __name__ == '__main__':
206
+ app.run(port=5000)
167
207
 
168
- except Exception as e:
169
- print(e)
170
208
  ```
171
209
 
172
- ### 5. Verify Callback (Webhook)
210
+ ### 5. Verify Callback (Manually)
173
211
 
174
212
  When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verify the request signature to ensure it is genuine.
175
213
 
@@ -187,7 +225,7 @@ When MyanMyanPay sends a callback to your `callbackUrl`[cite: 1], you must verif
187
225
  ```python
188
226
  from flask import request
189
227
 
190
- @app.route('/webhook/mmpay', methods=['POST'])
228
+ @app.route('/webhooks/mmpay', methods=['POST'])
191
229
  def mmpay_webhook():
192
230
  payload_str = request.data.decode('utf-8')
193
231
  nonce = request.headers.get('X-Mmpay-Nonce')
@@ -1,4 +1,5 @@
1
1
  README.md
2
+ pyproject.toml
2
3
  setup.py
3
4
  mmpay/__init__.py
4
5
  mmpay/client.py
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
@@ -7,7 +7,7 @@ long_description = (here / "README.md").read_text(encoding="utf-8")
7
7
 
8
8
  setup(
9
9
  name="mmpay-python-sdk",
10
- version="0.1.2",
10
+ version="0.1.3",
11
11
  description="Python SDK for MyanMyanPay",
12
12
  long_description=long_description,
13
13
  long_description_content_type="text/markdown",