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.
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/PKG-INFO +79 -41
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/README.md +78 -40
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay/client.py +78 -128
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay_python_sdk.egg-info/PKG-INFO +79 -41
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay_python_sdk.egg-info/SOURCES.txt +1 -0
- mmpay_python_sdk-0.1.3/pyproject.toml +3 -0
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/setup.py +1 -1
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay/__init__.py +0 -0
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay_python_sdk.egg-info/dependency_links.txt +0 -0
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay_python_sdk.egg-info/requires.txt +0 -0
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay_python_sdk.egg-info/top_level.txt +0 -0
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/setup.cfg +0 -0
- {mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/test/test_sdk.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmpay-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
94
|
-
|
|
93
|
+
### 2. Create a Payment
|
|
95
94
|
|
|
96
95
|
```python
|
|
97
96
|
try:
|
|
98
97
|
payment_request = {
|
|
99
|
-
"orderId": "ORD-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
144
|
-
print(response)
|
|
145
|
-
```
|
|
135
|
+
**Incoming Headers**
|
|
146
136
|
|
|
147
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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 (
|
|
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('/
|
|
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
|
|
69
|
-
|
|
68
|
+
### 2. Create a Payment
|
|
70
69
|
|
|
71
70
|
```python
|
|
72
71
|
try:
|
|
73
72
|
payment_request = {
|
|
74
|
-
"orderId": "ORD-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
print(response)
|
|
120
|
-
```
|
|
110
|
+
**Incoming Headers**
|
|
121
111
|
|
|
122
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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 (
|
|
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('/
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
94
|
-
|
|
93
|
+
### 2. Create a Payment
|
|
95
94
|
|
|
96
95
|
```python
|
|
97
96
|
try:
|
|
98
97
|
payment_request = {
|
|
99
|
-
"orderId": "ORD-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
144
|
-
print(response)
|
|
145
|
-
```
|
|
135
|
+
**Incoming Headers**
|
|
146
136
|
|
|
147
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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 (
|
|
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('/
|
|
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')
|
|
@@ -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.
|
|
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",
|
|
File without changes
|
{mmpay_python_sdk-0.1.2 → mmpay_python_sdk-0.1.3}/mmpay_python_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|