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