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.
@@ -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 PaymentRequest(TypedDict):
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: Optional[str]
20
- callbackUrl: Optional[str]
19
+ currency: str
20
+ callbackUrl: str
21
+ customMessage: str
21
22
 
22
- class XPaymentRequest(PaymentRequest, total=False):
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
- # --- Main SDK Class ---
55
+ class _PayGetRequestRequired(TypedDict):
56
+ orderId: str
57
+
58
+ class PayGetRequest(_PayGetRequestRequired, total=False):
59
+ nonce: str
60
+
61
+ class PayGetResponse(TypedDict, total=False):
62
+ appId: str
63
+ orderId: str
64
+ amount: float
65
+ vendor: str
66
+ method: str
67
+ customMessage: str
68
+ callbackUrl: str
69
+ callbackUrlStatus: str
70
+ callbackAt: str
71
+ disbursementId: str
72
+ disStatus: str
73
+ status: str
74
+ condition: str
75
+ createdAt: str
76
+ transactionRefId: str
77
+ qr: str
78
+ url: str
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
- # --- Sandbox Methods ---
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
- # In a real SDK, you might want to raise custom exceptions here
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
- # Construct payload
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 'currency' in params:
114
- xpayload['currency'] = params['currency']
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
- # Perform handshake first (as per TS logic)
120
- handshake_res = self.sandbox_handshake({'orderId': xpayload['orderId'], 'nonce': xpayload['nonce']})
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": response.text if response else None}
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
- # --- Production Methods ---
183
+ handshake_payload: HandShakeRequest = {
184
+ 'orderId': str(xpayload['orderId']),
185
+ 'nonce': str(xpayload['nonce'])
186
+ }
140
187
 
141
- def handshake(self, payload: HandShakeRequest) -> Dict[str, Any]:
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": response.text if response else None}
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: XPaymentRequest = {
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 'currency' in params:
179
- xpayload['currency'] = params['currency']
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
- # Perform handshake
185
- handshake_res = self.handshake({'orderId': xpayload['orderId'], 'nonce': xpayload['nonce']})
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": response.text if response else None}
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
- # --- Verification ---
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()
@@ -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
@@ -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
@@ -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)