mmpay-python-sdk 0.1.0__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.
- mmpay_python_sdk-0.1.2/PKG-INFO +230 -0
- mmpay_python_sdk-0.1.2/README.md +205 -0
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay/client.py +152 -49
- mmpay_python_sdk-0.1.2/mmpay_python_sdk.egg-info/PKG-INFO +230 -0
- mmpay_python_sdk-0.1.2/setup.py +29 -0
- mmpay_python_sdk-0.1.2/test/test_sdk.py +76 -0
- mmpay_python_sdk-0.1.0/PKG-INFO +0 -11
- mmpay_python_sdk-0.1.0/README.md +0 -169
- mmpay_python_sdk-0.1.0/mmpay_python_sdk.egg-info/PKG-INFO +0 -11
- mmpay_python_sdk-0.1.0/setup.py +0 -13
- mmpay_python_sdk-0.1.0/test/test_sdk.py +0 -21
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay/__init__.py +0 -0
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay_python_sdk.egg-info/SOURCES.txt +0 -0
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay_python_sdk.egg-info/dependency_links.txt +0 -0
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay_python_sdk.egg-info/requires.txt +0 -0
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay_python_sdk.egg-info/top_level.txt +0 -0
- {mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/setup.cfg +0 -0
|
@@ -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
|
|
@@ -3,43 +3,82 @@ import json
|
|
|
3
3
|
import hmac
|
|
4
4
|
import hashlib
|
|
5
5
|
import requests
|
|
6
|
-
from typing import List, Optional, TypedDict, Dict, Any
|
|
7
|
-
|
|
8
|
-
# --- Type Definitions to mirror your Interfaces ---
|
|
6
|
+
from typing import List, Optional, TypedDict, Dict, Any, Union
|
|
9
7
|
|
|
10
8
|
class Item(TypedDict):
|
|
11
9
|
name: str
|
|
12
10
|
amount: float
|
|
13
11
|
quantity: int
|
|
14
12
|
|
|
15
|
-
class
|
|
13
|
+
class _PaymentRequestRequired(TypedDict):
|
|
16
14
|
orderId: str
|
|
17
15
|
amount: float
|
|
16
|
+
|
|
17
|
+
class PaymentRequest(_PaymentRequestRequired, total=False):
|
|
18
18
|
items: List[Item]
|
|
19
|
-
currency:
|
|
20
|
-
callbackUrl:
|
|
19
|
+
currency: str
|
|
20
|
+
callbackUrl: str
|
|
21
|
+
customMessage: str
|
|
21
22
|
|
|
22
|
-
class
|
|
23
|
+
class _XPaymentRequestRequired(TypedDict):
|
|
23
24
|
appId: str
|
|
24
25
|
nonce: str
|
|
25
26
|
|
|
27
|
+
class XPaymentRequest(PaymentRequest, _XPaymentRequestRequired):
|
|
28
|
+
pass
|
|
29
|
+
|
|
26
30
|
class HandShakeRequest(TypedDict):
|
|
27
31
|
orderId: str
|
|
28
32
|
nonce: str
|
|
29
33
|
|
|
34
|
+
class HandShakeResponse(TypedDict):
|
|
35
|
+
token: str
|
|
36
|
+
|
|
37
|
+
class CallbackIncomingData(TypedDict):
|
|
38
|
+
orderId: str
|
|
39
|
+
amount: float
|
|
40
|
+
method: str
|
|
41
|
+
currency: str
|
|
42
|
+
vendor: str
|
|
43
|
+
status: str
|
|
44
|
+
condition: str
|
|
45
|
+
transactionRefId: str
|
|
46
|
+
callbackUrl: Optional[str]
|
|
47
|
+
customMessage: Optional[str]
|
|
48
|
+
|
|
30
49
|
class SDKOptions(TypedDict):
|
|
31
50
|
appId: str
|
|
32
51
|
publishableKey: str
|
|
33
52
|
secretKey: str
|
|
34
53
|
apiBaseUrl: str
|
|
35
54
|
|
|
36
|
-
|
|
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
|
|
37
79
|
|
|
38
80
|
class MMPaySDK:
|
|
39
81
|
def __init__(self, options: SDKOptions):
|
|
40
|
-
"""
|
|
41
|
-
Initializes the SDK with the merchant's keys and the API endpoint.
|
|
42
|
-
"""
|
|
43
82
|
self._app_id = options['appId']
|
|
44
83
|
self._publishable_key = options['publishableKey']
|
|
45
84
|
self._secret_key = options['secretKey']
|
|
@@ -47,9 +86,6 @@ class MMPaySDK:
|
|
|
47
86
|
self._btoken: Optional[str] = None
|
|
48
87
|
|
|
49
88
|
def _generate_signature(self, body_string: str, nonce: str) -> str:
|
|
50
|
-
"""
|
|
51
|
-
Generates an HMAC SHA256 signature for request integrity.
|
|
52
|
-
"""
|
|
53
89
|
string_to_sign = f"{nonce}.{body_string}"
|
|
54
90
|
return hmac.new(
|
|
55
91
|
self._secret_key.encode('utf-8'),
|
|
@@ -58,23 +94,15 @@ class MMPaySDK:
|
|
|
58
94
|
).hexdigest()
|
|
59
95
|
|
|
60
96
|
def _get_nonce(self) -> str:
|
|
61
|
-
"""Helper to get current timestamp as string (milliseconds)"""
|
|
62
97
|
return str(int(time.time() * 1000))
|
|
63
98
|
|
|
64
99
|
def _json_stringify(self, data: Any) -> str:
|
|
65
|
-
"""
|
|
66
|
-
Mimics JS JSON.stringify exactly (no spaces).
|
|
67
|
-
Crucial for signature verification.
|
|
68
|
-
"""
|
|
69
100
|
return json.dumps(data, separators=(',', ':'))
|
|
70
101
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def sandbox_handshake(self, payload: HandShakeRequest) -> Dict[str, Any]:
|
|
102
|
+
def sandbox_handshake(self, payload: HandShakeRequest) -> Union[HandShakeResponse, Dict[str, Any]]:
|
|
74
103
|
endpoint = f"{self._api_base_url}/payments/sandbox-handshake"
|
|
75
104
|
nonce = self._get_nonce()
|
|
76
105
|
|
|
77
|
-
# Ensure payload is serialized exactly as the signature expects
|
|
78
106
|
body_string = self._json_stringify(payload)
|
|
79
107
|
signature = self._generate_signature(body_string, nonce)
|
|
80
108
|
|
|
@@ -93,37 +121,41 @@ class MMPaySDK:
|
|
|
93
121
|
self._btoken = data['token']
|
|
94
122
|
return data
|
|
95
123
|
except requests.exceptions.RequestException as e:
|
|
96
|
-
|
|
97
|
-
return {"error": str(e), "details": response.text if response else None}
|
|
124
|
+
return {"error": str(e), "details": getattr(e.response, 'text', '')}
|
|
98
125
|
|
|
99
126
|
def sandbox_pay(self, params: PaymentRequest) -> Dict[str, Any]:
|
|
100
127
|
endpoint = f"{self._api_base_url}/payments/sandbox-create"
|
|
101
128
|
nonce = self._get_nonce()
|
|
102
129
|
|
|
103
|
-
|
|
104
|
-
xpayload: XPaymentRequest = {
|
|
130
|
+
xpayload: Dict[str, Any] = {
|
|
105
131
|
"appId": self._app_id,
|
|
106
132
|
"nonce": nonce,
|
|
107
133
|
"amount": params['amount'],
|
|
108
134
|
"orderId": params['orderId'],
|
|
109
|
-
"items": params['items'],
|
|
110
135
|
}
|
|
136
|
+
|
|
137
|
+
if 'items' in params:
|
|
138
|
+
xpayload['items'] = params['items']
|
|
111
139
|
if 'callbackUrl' in params:
|
|
112
140
|
xpayload['callbackUrl'] = params['callbackUrl']
|
|
113
|
-
if '
|
|
114
|
-
xpayload['
|
|
141
|
+
if 'customMessage' in params:
|
|
142
|
+
xpayload['customMessage'] = params['customMessage']
|
|
115
143
|
|
|
116
144
|
body_string = self._json_stringify(xpayload)
|
|
117
145
|
signature = self._generate_signature(body_string, nonce)
|
|
118
146
|
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
handshake_payload: HandShakeRequest = {
|
|
148
|
+
'orderId': str(xpayload['orderId']),
|
|
149
|
+
'nonce': str(xpayload['nonce'])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
handshake_res = self.sandbox_handshake(handshake_payload)
|
|
121
153
|
if 'error' in handshake_res:
|
|
122
154
|
return handshake_res
|
|
123
155
|
|
|
124
156
|
headers = {
|
|
125
157
|
'Authorization': f"Bearer {self._publishable_key}",
|
|
126
|
-
'X-Mmpay-Btoken': self._btoken,
|
|
158
|
+
'X-Mmpay-Btoken': self._btoken or '',
|
|
127
159
|
'X-Mmpay-Nonce': nonce,
|
|
128
160
|
'X-Mmpay-Signature': signature,
|
|
129
161
|
'Content-Type': 'application/json',
|
|
@@ -134,11 +166,45 @@ class MMPaySDK:
|
|
|
134
166
|
response.raise_for_status()
|
|
135
167
|
return response.json()
|
|
136
168
|
except requests.exceptions.RequestException as e:
|
|
137
|
-
return {"error": str(e), "details":
|
|
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)
|
|
138
182
|
|
|
139
|
-
|
|
183
|
+
handshake_payload: HandShakeRequest = {
|
|
184
|
+
'orderId': str(xpayload['orderId']),
|
|
185
|
+
'nonce': str(xpayload['nonce'])
|
|
186
|
+
}
|
|
140
187
|
|
|
141
|
-
|
|
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', '')}
|
|
206
|
+
|
|
207
|
+
def handshake(self, payload: HandShakeRequest) -> Union[HandShakeResponse, Dict[str, Any]]:
|
|
142
208
|
endpoint = f"{self._api_base_url}/payments/handshake"
|
|
143
209
|
nonce = self._get_nonce()
|
|
144
210
|
|
|
@@ -160,35 +226,41 @@ class MMPaySDK:
|
|
|
160
226
|
self._btoken = data['token']
|
|
161
227
|
return data
|
|
162
228
|
except requests.exceptions.RequestException as e:
|
|
163
|
-
return {"error": str(e), "details":
|
|
229
|
+
return {"error": str(e), "details": getattr(e.response, 'text', '')}
|
|
164
230
|
|
|
165
231
|
def pay(self, params: PaymentRequest) -> Dict[str, Any]:
|
|
166
232
|
endpoint = f"{self._api_base_url}/payments/create"
|
|
167
233
|
nonce = self._get_nonce()
|
|
168
234
|
|
|
169
|
-
xpayload:
|
|
235
|
+
xpayload: Dict[str, Any] = {
|
|
170
236
|
"appId": self._app_id,
|
|
171
237
|
"nonce": nonce,
|
|
172
238
|
"amount": params['amount'],
|
|
173
239
|
"orderId": params['orderId'],
|
|
174
|
-
"items": params['items'],
|
|
175
240
|
}
|
|
241
|
+
|
|
242
|
+
if 'items' in params:
|
|
243
|
+
xpayload['items'] = params['items']
|
|
176
244
|
if 'callbackUrl' in params:
|
|
177
245
|
xpayload['callbackUrl'] = params['callbackUrl']
|
|
178
|
-
if '
|
|
179
|
-
xpayload['
|
|
246
|
+
if 'customMessage' in params:
|
|
247
|
+
xpayload['customMessage'] = params['customMessage']
|
|
180
248
|
|
|
181
249
|
body_string = self._json_stringify(xpayload)
|
|
182
250
|
signature = self._generate_signature(body_string, nonce)
|
|
183
251
|
|
|
184
|
-
|
|
185
|
-
|
|
252
|
+
handshake_payload: HandShakeRequest = {
|
|
253
|
+
'orderId': str(xpayload['orderId']),
|
|
254
|
+
'nonce': str(xpayload['nonce'])
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
handshake_res = self.handshake(handshake_payload)
|
|
186
258
|
if 'error' in handshake_res:
|
|
187
259
|
return handshake_res
|
|
188
260
|
|
|
189
261
|
headers = {
|
|
190
262
|
'Authorization': f"Bearer {self._publishable_key}",
|
|
191
|
-
'X-Mmpay-Btoken': self._btoken,
|
|
263
|
+
'X-Mmpay-Btoken': self._btoken or '',
|
|
192
264
|
'X-Mmpay-Nonce': nonce,
|
|
193
265
|
'X-Mmpay-Signature': signature,
|
|
194
266
|
'Content-Type': 'application/json',
|
|
@@ -199,14 +271,45 @@ class MMPaySDK:
|
|
|
199
271
|
response.raise_for_status()
|
|
200
272
|
return response.json()
|
|
201
273
|
except requests.exceptions.RequestException as e:
|
|
202
|
-
return {"error": str(e), "details":
|
|
274
|
+
return {"error": str(e), "details": getattr(e.response, 'text', '')}
|
|
275
|
+
|
|
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
|
+
}
|
|
203
304
|
|
|
204
|
-
|
|
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', '')}
|
|
205
311
|
|
|
206
312
|
def verify_cb(self, payload: str, nonce: str, expected_signature: str) -> bool:
|
|
207
|
-
"""
|
|
208
|
-
Verifies the signature of a callback request.
|
|
209
|
-
"""
|
|
210
313
|
if not payload or not nonce or not expected_signature:
|
|
211
314
|
raise ValueError("Callback verification failed: Missing payload, nonce, or signature.")
|
|
212
315
|
|
|
@@ -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
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
import random
|
|
4
|
+
import string
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
|
|
7
|
+
# from mmpay import MMPaySDK
|
|
8
|
+
from mmpay import MMPaySDK
|
|
9
|
+
|
|
10
|
+
# Load environment variables from .env file
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
def generate_secure_random_string(length: int) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Generates a random alphanumeric string.
|
|
16
|
+
"""
|
|
17
|
+
# Using random.choices to generate a string of requested length
|
|
18
|
+
# This mimics the base36 behavior of the JS example
|
|
19
|
+
chars = string.ascii_lowercase + string.digits
|
|
20
|
+
return ''.join(random.choices(chars, k=length))
|
|
21
|
+
|
|
22
|
+
def start():
|
|
23
|
+
"""
|
|
24
|
+
Executes the payment call and measures network latency.
|
|
25
|
+
"""
|
|
26
|
+
# Initialize SDK
|
|
27
|
+
# specific env keys matching your JS example
|
|
28
|
+
mmpay = MMPaySDK({
|
|
29
|
+
'appId': os.getenv('APP_ID', ''),
|
|
30
|
+
'publishableKey': os.getenv('PUB_KEY', ''),
|
|
31
|
+
'secretKey': os.getenv('SEC_KEY', ''),
|
|
32
|
+
'apiBaseUrl': os.getenv('BASEURL', '')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
# Generate Order ID
|
|
36
|
+
order_id = generate_secure_random_string(6)
|
|
37
|
+
|
|
38
|
+
# Start Timer
|
|
39
|
+
# time.perf_counter() is the Python equivalent to performance.now()
|
|
40
|
+
start_time = time.perf_counter()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
payload = {
|
|
44
|
+
"orderId": order_id,
|
|
45
|
+
"amount": 1500,
|
|
46
|
+
"customMessage": "MyanMyanPay Is The Best",
|
|
47
|
+
"items": [{"name": "Items", "amount": 3000, "quantity": 1}]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Execute Payment (Synchronous in Python version)
|
|
51
|
+
response = mmpay.sandbox_pay(payload)
|
|
52
|
+
|
|
53
|
+
# End Timer
|
|
54
|
+
end_time = time.perf_counter()
|
|
55
|
+
|
|
56
|
+
# Calculate latency in milliseconds
|
|
57
|
+
latency_ms = (end_time - start_time) * 1000
|
|
58
|
+
|
|
59
|
+
print(f"\n--- Transaction Request Successful ---")
|
|
60
|
+
print(f"Order ID: {order_id}")
|
|
61
|
+
print(f"**Network Latency: {latency_ms:.3f} ms**")
|
|
62
|
+
print(f"Response: {response}")
|
|
63
|
+
print(f"------------------------------\n")
|
|
64
|
+
|
|
65
|
+
except Exception as error:
|
|
66
|
+
end_time = time.perf_counter()
|
|
67
|
+
latency_ms = (end_time - start_time) * 1000
|
|
68
|
+
|
|
69
|
+
print(f"\n--- Transaction Request Failed ---")
|
|
70
|
+
print(f"Order ID: {order_id}")
|
|
71
|
+
print(f"**Network Latency: {latency_ms:.3f} ms**")
|
|
72
|
+
print(f"Error Message: {str(error)}")
|
|
73
|
+
print(f"--------------------------\n")
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
start()
|
mmpay_python_sdk-0.1.0/PKG-INFO
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mmpay-python-sdk
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Python SDK for MMPay (Ported from JS)
|
|
5
|
-
Author: Your Name
|
|
6
|
-
Requires-Python: >=3.6
|
|
7
|
-
Requires-Dist: requests
|
|
8
|
-
Dynamic: author
|
|
9
|
-
Dynamic: requires-dist
|
|
10
|
-
Dynamic: requires-python
|
|
11
|
-
Dynamic: summary
|
mmpay_python_sdk-0.1.0/README.md
DELETED
|
@@ -1,169 +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
|
-
| `currency` | `str` | No | Currency code (e.g., "MMK", "USD"). |
|
|
68
|
-
| `callbackUrl` | `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
|
-
"currency": "MMK",
|
|
87
|
-
"callbackUrl": "[https://your-site.com/webhook/mmpay](https://your-site.com/webhook/mmpay)",
|
|
88
|
-
"items": [
|
|
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": 10000,
|
|
113
|
-
"items": [
|
|
114
|
-
{"name": "E-Commerce Item", "amount": 10000, "quantity": 1}
|
|
115
|
-
]
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
# Helper automatically handles the handshake and signing
|
|
119
|
-
response = sdk.pay(payment_request)
|
|
120
|
-
print("Production Payment URL:", response.get('url'))
|
|
121
|
-
|
|
122
|
-
except Exception as e:
|
|
123
|
-
print("Error:", e)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 3. Verify Callback (Webhook)
|
|
127
|
-
|
|
128
|
-
When MMPay sends a callback to your `callbackUrl`, you must verify the request signature to ensure it is genuine.
|
|
129
|
-
|
|
130
|
-
### Callback Verification (`verify_cb`)
|
|
131
|
-
|
|
132
|
-
| Parameter | Type | Description |
|
|
133
|
-
| :--- | :--- | :--- |
|
|
134
|
-
| `payload` | `str` | The **raw** JSON string body of the incoming request. |
|
|
135
|
-
| `nonce` | `str` | The value of the `X-Mmpay-Nonce` header. |
|
|
136
|
-
| `expected_signature` | `str` | The value of the `X-Mmpay-Signature` header. |
|
|
137
|
-
|
|
138
|
-
**Example using Flask:**
|
|
139
|
-
|
|
140
|
-
```python
|
|
141
|
-
from flask import request
|
|
142
|
-
|
|
143
|
-
@app.route('/webhook/mmpay', methods=['POST'])
|
|
144
|
-
def mmpay_webhook():
|
|
145
|
-
# 1. Get the raw payload body as a string (Crucial for signature check)
|
|
146
|
-
payload_str = request.data.decode('utf-8')
|
|
147
|
-
|
|
148
|
-
# 2. Get headers
|
|
149
|
-
nonce = request.headers.get('X-Mmpay-Nonce')
|
|
150
|
-
signature = request.headers.get('X-Mmpay-Signature')
|
|
151
|
-
|
|
152
|
-
try:
|
|
153
|
-
# 3. Verify
|
|
154
|
-
is_valid = sdk.verify_cb(payload_str, nonce, signature)
|
|
155
|
-
|
|
156
|
-
if is_valid:
|
|
157
|
-
# Process the order (e.g., mark as paid in DB)
|
|
158
|
-
return "Verified", 200
|
|
159
|
-
else:
|
|
160
|
-
return "Invalid Signature", 400
|
|
161
|
-
|
|
162
|
-
except ValueError as e:
|
|
163
|
-
return str(e), 400
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
## License
|
|
168
|
-
|
|
169
|
-
MIT
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mmpay-python-sdk
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Python SDK for MMPay (Ported from JS)
|
|
5
|
-
Author: Your Name
|
|
6
|
-
Requires-Python: >=3.6
|
|
7
|
-
Requires-Dist: requests
|
|
8
|
-
Dynamic: author
|
|
9
|
-
Dynamic: requires-dist
|
|
10
|
-
Dynamic: requires-python
|
|
11
|
-
Dynamic: summary
|
mmpay_python_sdk-0.1.0/setup.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
|
|
3
|
-
setup(
|
|
4
|
-
name="mmpay-python-sdk",
|
|
5
|
-
version="0.1.0",
|
|
6
|
-
description="Python SDK for MMPay (Ported from JS)",
|
|
7
|
-
author="Your Name",
|
|
8
|
-
packages=find_packages(),
|
|
9
|
-
install_requires=[
|
|
10
|
-
"requests",
|
|
11
|
-
],
|
|
12
|
-
python_requires=">=3.6",
|
|
13
|
-
)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from mmpay import MMPaySDK
|
|
2
|
-
|
|
3
|
-
# Initialize
|
|
4
|
-
sdk = MMPaySDK({
|
|
5
|
-
"appId": "your_app_id",
|
|
6
|
-
"publishableKey": "your_pub_key",
|
|
7
|
-
"secretKey": "your_secret_key",
|
|
8
|
-
"apiBaseUrl": "https://api.mmpay.com" # Example URL
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
# Create a Payment (Sandbox)
|
|
12
|
-
response = sdk.sandbox_pay({
|
|
13
|
-
"orderId": "ORD-123456789",
|
|
14
|
-
"amount": 1000,
|
|
15
|
-
"callbackUrl": "https://yoursite.com/callback",
|
|
16
|
-
"items": [
|
|
17
|
-
{"name": "Test Item", "amount": 1000, "quantity": 1}
|
|
18
|
-
]
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
print(response)
|
|
File without changes
|
|
File without changes
|
{mmpay_python_sdk-0.1.0 → mmpay_python_sdk-0.1.2}/mmpay_python_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|