qpay-client 0.1.0__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.
- qpay_client-0.1.0/PKG-INFO +21 -0
- qpay_client-0.1.0/README.md +10 -0
- qpay_client-0.1.0/pyproject.toml +17 -0
- qpay_client-0.1.0/src/qpay_client/__init__.py +4 -0
- qpay_client-0.1.0/src/qpay_client/py.typed +0 -0
- qpay_client-0.1.0/src/qpay_client/v2/error.py +23 -0
- qpay_client-0.1.0/src/qpay_client/v2/qpay_client.py +273 -0
- qpay_client-0.1.0/src/qpay_client/v2/schemas.py +306 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: qpay-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async qpay payment API client
|
|
5
|
+
Author: Amraa1
|
|
6
|
+
Author-email: Amraa1 <amarsanaaganbaatar0409@gmail.com>
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: pydantic>=2.11.7
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Package description
|
|
13
|
+
Features I like to add:
|
|
14
|
+
* Async and sync client
|
|
15
|
+
* Provide ways for users to quickly create a client and start testing
|
|
16
|
+
* Provide ways for users to optimize and prepare for prod env
|
|
17
|
+
* qpay logs
|
|
18
|
+
*
|
|
19
|
+
|
|
20
|
+
# QPAY developer doc
|
|
21
|
+
https://developer.qpay.mn/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Package description
|
|
2
|
+
Features I like to add:
|
|
3
|
+
* Async and sync client
|
|
4
|
+
* Provide ways for users to quickly create a client and start testing
|
|
5
|
+
* Provide ways for users to optimize and prepare for prod env
|
|
6
|
+
* qpay logs
|
|
7
|
+
*
|
|
8
|
+
|
|
9
|
+
# QPAY developer doc
|
|
10
|
+
https://developer.qpay.mn/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qpay-client"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Async qpay payment API client"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Amraa1", email = "amarsanaaganbaatar0409@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.28.1",
|
|
12
|
+
"pydantic>=2.11.7",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["uv_build>=0.8.14,<0.9.0"]
|
|
17
|
+
build-backend = "uv_build"
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class QPayError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Raised when Qpay server returns error
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
def __init__(self, *, status_code: int, error_key: str) -> None:
|
|
7
|
+
self.exception_message = f"status_code: {status_code}, error_key: {error_key}"
|
|
8
|
+
super().__init__(self.exception_message)
|
|
9
|
+
self.status_code = status_code
|
|
10
|
+
self.error_key = error_key
|
|
11
|
+
|
|
12
|
+
def __repr__(self) -> str:
|
|
13
|
+
return self.exception_message
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClientConfigError(Exception):
|
|
17
|
+
"""
|
|
18
|
+
Raised when the client is configured wrong.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, *attr) -> None:
|
|
22
|
+
self.exception_message = f"incorrect attributes: {attr}"
|
|
23
|
+
super().__init__(self.exception_message)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from httpx import AsyncClient, BasicAuth, Response
|
|
3
|
+
import time
|
|
4
|
+
from .schemas import (
|
|
5
|
+
InvoiceCreateRequest,
|
|
6
|
+
InvoiceCreateSimpleRequest,
|
|
7
|
+
Payment,
|
|
8
|
+
PaymentCheckRequest,
|
|
9
|
+
PaymentCheckResponse,
|
|
10
|
+
CreateInvoiceResponse,
|
|
11
|
+
TokenResponse,
|
|
12
|
+
PaymentListRequest,
|
|
13
|
+
EbarimtCreateRequest,
|
|
14
|
+
Ebarimt,
|
|
15
|
+
)
|
|
16
|
+
from .error import QPayError, ClientConfigError
|
|
17
|
+
from typing import Optional
|
|
18
|
+
import enum
|
|
19
|
+
import logging
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Environment(enum.Enum):
|
|
23
|
+
sandbox = 1
|
|
24
|
+
production = 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("qpay")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
QPAY_USERNAME = os.getenv("QPAY_USERNAME", "TEST_MERCHANT")
|
|
31
|
+
QPAY_PASSWORD = os.getenv("QPAY_PASSWORD", "123456")
|
|
32
|
+
QPAY_ENV = os.getenv("QPAY_ENV", "sandbox")
|
|
33
|
+
|
|
34
|
+
if QPAY_ENV == "production":
|
|
35
|
+
is_sandbox = False
|
|
36
|
+
elif QPAY_ENV == "sandbox":
|
|
37
|
+
is_sandbox = True
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError("QPAY_ENV must either sandbox or production")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Env based settings
|
|
43
|
+
if is_sandbox:
|
|
44
|
+
BASE_URL = "https://merchant-sandbox.qpay.mn/v2"
|
|
45
|
+
else:
|
|
46
|
+
BASE_URL = "https://merchant.qpay.mn/v2"
|
|
47
|
+
|
|
48
|
+
is_prod = not is_sandbox
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class QPayClient:
|
|
52
|
+
"""
|
|
53
|
+
Async QPay v2 client
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
*,
|
|
59
|
+
username: Optional[str] = None,
|
|
60
|
+
password: Optional[str] = None,
|
|
61
|
+
base_url: Optional[str] = None,
|
|
62
|
+
is_sandbox: Optional[bool] = None,
|
|
63
|
+
token_leeway=60,
|
|
64
|
+
timeout=30,
|
|
65
|
+
logger=logger,
|
|
66
|
+
):
|
|
67
|
+
if is_sandbox is None:
|
|
68
|
+
is_sandbox = not is_prod
|
|
69
|
+
|
|
70
|
+
if is_sandbox:
|
|
71
|
+
self._env = Environment.sandbox
|
|
72
|
+
elif not is_sandbox:
|
|
73
|
+
self._env = Environment.production
|
|
74
|
+
|
|
75
|
+
self._base_url = base_url or BASE_URL
|
|
76
|
+
self._client = AsyncClient(timeout=timeout)
|
|
77
|
+
self._auth_credentials = BasicAuth(
|
|
78
|
+
username=username or QPAY_USERNAME,
|
|
79
|
+
password=password or QPAY_PASSWORD,
|
|
80
|
+
)
|
|
81
|
+
self._access_token = ""
|
|
82
|
+
self._access_token_expiry = 0
|
|
83
|
+
self._refresh_token = ""
|
|
84
|
+
self._refresh_token_expiry = 0
|
|
85
|
+
self._scope = ""
|
|
86
|
+
self._not_before_policy = ""
|
|
87
|
+
self._session_state = ""
|
|
88
|
+
self._timeout = timeout or 30
|
|
89
|
+
self._token_leeway = token_leeway or 60
|
|
90
|
+
self._logger = logger
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
async def headers(self):
|
|
94
|
+
token = await self.get_token()
|
|
95
|
+
return {
|
|
96
|
+
"Content-Type": "APP_JSON",
|
|
97
|
+
"Authorization": f"Bearer {token}",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def _check_error(self, response: Response):
|
|
101
|
+
if response.is_error:
|
|
102
|
+
print(response.json())
|
|
103
|
+
error_data = response.json()
|
|
104
|
+
raise QPayError(
|
|
105
|
+
status_code=response.status_code, error_key=error_data["message"]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Auth
|
|
109
|
+
async def authenticate(self):
|
|
110
|
+
response = await self._client.post(
|
|
111
|
+
BASE_URL + "/auth/token",
|
|
112
|
+
auth=self._auth_credentials,
|
|
113
|
+
timeout=self._timeout,
|
|
114
|
+
)
|
|
115
|
+
# Raises status error if there is error
|
|
116
|
+
self._check_error(response)
|
|
117
|
+
|
|
118
|
+
data = TokenResponse.model_validate(response.json())
|
|
119
|
+
|
|
120
|
+
self._access_token = data.access_token
|
|
121
|
+
self._refresh_token = data.refresh_token
|
|
122
|
+
self._access_token_expiry = data.expires_in - self._token_leeway
|
|
123
|
+
self._refresh_token_expiry = data.refresh_expires_in - self._token_leeway
|
|
124
|
+
self._scope = data.scope
|
|
125
|
+
self._not_before_policy = data.not_before_policy
|
|
126
|
+
self._session_state = data.session_state
|
|
127
|
+
|
|
128
|
+
async def refresh_access_token(self):
|
|
129
|
+
if not self._refresh_token or time.time() >= self._refresh_token_expiry:
|
|
130
|
+
await self.authenticate()
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
response = await self._client.post(
|
|
134
|
+
BASE_URL + "/auth/refresh",
|
|
135
|
+
headers={"Authorization": f"Bearer {self._refresh_token}"},
|
|
136
|
+
timeout=self._timeout,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
self._check_error(response)
|
|
140
|
+
|
|
141
|
+
if response.is_success:
|
|
142
|
+
data = TokenResponse.model_validate(response.json())
|
|
143
|
+
|
|
144
|
+
self._access_token = data.access_token
|
|
145
|
+
self._refresh_token = data.refresh_token
|
|
146
|
+
self._access_token_expiry = data.expires_in - self._token_leeway
|
|
147
|
+
self._refresh_token_expiry = data.refresh_expires_in - self._token_leeway
|
|
148
|
+
else:
|
|
149
|
+
await self.authenticate()
|
|
150
|
+
|
|
151
|
+
async def get_token(self):
|
|
152
|
+
if not self._access_token:
|
|
153
|
+
await self.authenticate()
|
|
154
|
+
elif time.time() >= self._access_token_expiry:
|
|
155
|
+
await self.refresh_access_token()
|
|
156
|
+
return self._access_token
|
|
157
|
+
|
|
158
|
+
# Invoice
|
|
159
|
+
async def invoice_create(
|
|
160
|
+
self, create_invoice_request: InvoiceCreateRequest | InvoiceCreateSimpleRequest
|
|
161
|
+
):
|
|
162
|
+
response = await self._client.post(
|
|
163
|
+
BASE_URL + "/invoice",
|
|
164
|
+
headers=await self.headers,
|
|
165
|
+
data=create_invoice_request.model_dump(),
|
|
166
|
+
timeout=self._timeout,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self._check_error(response)
|
|
170
|
+
|
|
171
|
+
data = CreateInvoiceResponse.model_validate_json(response.json())
|
|
172
|
+
return data
|
|
173
|
+
|
|
174
|
+
async def invoice_cancel(
|
|
175
|
+
self,
|
|
176
|
+
invoice_id: str,
|
|
177
|
+
):
|
|
178
|
+
response = await self._client.delete(
|
|
179
|
+
BASE_URL + "/invoice/" + invoice_id,
|
|
180
|
+
headers=await self.headers,
|
|
181
|
+
timeout=self._timeout,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
self._check_error(response)
|
|
185
|
+
|
|
186
|
+
return response.json()
|
|
187
|
+
|
|
188
|
+
# Payment
|
|
189
|
+
async def payment_get(self, payment_id: str):
|
|
190
|
+
response = await self._client.get(
|
|
191
|
+
BASE_URL + "/payment/" + payment_id,
|
|
192
|
+
headers=await self.headers,
|
|
193
|
+
timeout=self._timeout,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
self._check_error(response)
|
|
197
|
+
|
|
198
|
+
validated_response = Payment.model_validate(response.json())
|
|
199
|
+
return validated_response
|
|
200
|
+
|
|
201
|
+
async def payment_check(self, payment_check_request: PaymentCheckRequest):
|
|
202
|
+
response = await self._client.post(
|
|
203
|
+
BASE_URL + "/payment/check",
|
|
204
|
+
data=payment_check_request.model_dump(),
|
|
205
|
+
headers=await self.headers,
|
|
206
|
+
timeout=self._timeout,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
self._check_error(response)
|
|
210
|
+
|
|
211
|
+
validated_response = PaymentCheckResponse.model_validate_json(response.json())
|
|
212
|
+
return validated_response
|
|
213
|
+
|
|
214
|
+
async def payment_cancel(self, payment_id: str):
|
|
215
|
+
response = await self._client.delete(
|
|
216
|
+
BASE_URL + "/payment/cancel/" + payment_id,
|
|
217
|
+
headers=await self.headers,
|
|
218
|
+
timeout=self._timeout,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self._check_error(response)
|
|
222
|
+
|
|
223
|
+
return response.json()
|
|
224
|
+
|
|
225
|
+
async def payment_refund(self, payment_id: str):
|
|
226
|
+
response = await self._client.delete(
|
|
227
|
+
BASE_URL + "/payment/refund/" + payment_id,
|
|
228
|
+
headers=await self.headers,
|
|
229
|
+
timeout=self._timeout,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
self._check_error(response)
|
|
233
|
+
|
|
234
|
+
return response.json()
|
|
235
|
+
|
|
236
|
+
async def payment_list(self, payment_list_request: PaymentListRequest):
|
|
237
|
+
response = await self._client.post(
|
|
238
|
+
BASE_URL + "/payment/list",
|
|
239
|
+
data=payment_list_request.model_dump(),
|
|
240
|
+
headers=await self.headers,
|
|
241
|
+
timeout=self._timeout,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
self._check_error(response)
|
|
245
|
+
|
|
246
|
+
validated_response = PaymentCheckResponse.model_validate_json(response.json())
|
|
247
|
+
return validated_response
|
|
248
|
+
|
|
249
|
+
# ebarimt
|
|
250
|
+
async def ebarimt_create(self, ebarimt_create_request: EbarimtCreateRequest):
|
|
251
|
+
response = await self._client.post(
|
|
252
|
+
BASE_URL + "/ebarimt/create",
|
|
253
|
+
data=ebarimt_create_request.model_dump(),
|
|
254
|
+
headers=await self.headers,
|
|
255
|
+
timeout=self._timeout,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
self._check_error(response)
|
|
259
|
+
|
|
260
|
+
validated_response = Ebarimt.model_validate_json(response.json())
|
|
261
|
+
return validated_response
|
|
262
|
+
|
|
263
|
+
async def ebarimt_get(self, barimt_id: str):
|
|
264
|
+
response = await self._client.get(
|
|
265
|
+
BASE_URL + "/ebarimt/" + barimt_id,
|
|
266
|
+
headers=await self.headers,
|
|
267
|
+
timeout=self._timeout,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
self._check_error(response)
|
|
271
|
+
|
|
272
|
+
validated_response = Ebarimt.model_validate_json(response.json())
|
|
273
|
+
return validated_response
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
from typing import Optional, Dict, List
|
|
2
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Currency(StrEnum):
|
|
9
|
+
mnt = "MNT"
|
|
10
|
+
usd = "USD"
|
|
11
|
+
cny = "CNY"
|
|
12
|
+
jpy = "JPY"
|
|
13
|
+
rub = "RUB"
|
|
14
|
+
eur = "EUR"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PaymentStatus(StrEnum):
|
|
18
|
+
new = "NEW"
|
|
19
|
+
failed = "FAILED"
|
|
20
|
+
paid = "PAID"
|
|
21
|
+
partial = "PARTIAL"
|
|
22
|
+
refunded = "REFUNDED"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BankCode(StrEnum):
|
|
26
|
+
bank_of_mongolia = "010000"
|
|
27
|
+
capital_bank = "020000"
|
|
28
|
+
trade_and_development_bank_of_mongolia = "040000"
|
|
29
|
+
khan_bank = "050000"
|
|
30
|
+
golomt_bank = "150000"
|
|
31
|
+
trans_bank = "190000"
|
|
32
|
+
arig_bank = "210000"
|
|
33
|
+
credit_bank = "220000"
|
|
34
|
+
nib_bank = "290000"
|
|
35
|
+
capitron_bank = "300000"
|
|
36
|
+
khas_bank = "320000"
|
|
37
|
+
chingiskhan_bank = "330000"
|
|
38
|
+
state_bank = "340000"
|
|
39
|
+
national_development_bank = "360000"
|
|
40
|
+
bogd_bank = "380000"
|
|
41
|
+
state_fund = "900000"
|
|
42
|
+
mobi_finance = "500000"
|
|
43
|
+
m_bank = "390000"
|
|
44
|
+
invescore = "993000"
|
|
45
|
+
test_bank = "100000"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ObjectTypeNum(StrEnum):
|
|
49
|
+
invoice = "INVOICE"
|
|
50
|
+
qr = "QR"
|
|
51
|
+
item = "ITEM"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TokenResponse(BaseModel):
|
|
55
|
+
token_type: str
|
|
56
|
+
access_token: str
|
|
57
|
+
expires_in: float
|
|
58
|
+
refresh_token: str
|
|
59
|
+
refresh_expires_in: float
|
|
60
|
+
scope: str
|
|
61
|
+
not_before_policy: str = Field(..., alias="not-before-policy")
|
|
62
|
+
session_state: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class QPayDeeplink(BaseModel):
|
|
66
|
+
name: str
|
|
67
|
+
description: str
|
|
68
|
+
logo: str
|
|
69
|
+
link: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Address(BaseModel):
|
|
73
|
+
city: Optional[str] = Field(default=None, max_length=100)
|
|
74
|
+
district: Optional[str] = Field(default=None, max_length=100)
|
|
75
|
+
street: Optional[str] = Field(default=None, max_length=100)
|
|
76
|
+
building: Optional[str] = Field(default=None, max_length=100)
|
|
77
|
+
address: Optional[str] = Field(default=None, max_length=100)
|
|
78
|
+
zipcode: Optional[str] = Field(default=None, max_length=20)
|
|
79
|
+
longitude: Optional[str] = Field(default=None, max_length=20)
|
|
80
|
+
latitude: Optional[str] = Field(default=None, max_length=20)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SenderTerminalData(BaseModel):
|
|
84
|
+
name: Optional[str] = Field(default=None, max_length=100)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class InvoiceReceiverData(BaseModel):
|
|
88
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
89
|
+
|
|
90
|
+
registration_number: Optional[str] = Field(
|
|
91
|
+
default=None, alias="register", max_length=20
|
|
92
|
+
)
|
|
93
|
+
name: Optional[str] = Field(default=None, max_length=100)
|
|
94
|
+
email: Optional[str] = Field(default=None, max_length=255)
|
|
95
|
+
phone: Optional[str] = Field(default=None, max_length=20)
|
|
96
|
+
address: Optional[Address] = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class SenderBranchData(BaseModel):
|
|
100
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
101
|
+
|
|
102
|
+
registration_number: Optional[str] = Field(
|
|
103
|
+
default=None, alias="register", max_length=20
|
|
104
|
+
)
|
|
105
|
+
name: Optional[str] = Field(default=None, max_length=100)
|
|
106
|
+
email: Optional[str] = Field(default=None, max_length=255)
|
|
107
|
+
phone: Optional[str] = Field(default=None, max_length=20)
|
|
108
|
+
address: Optional[Address] = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Discount(BaseModel):
|
|
112
|
+
discount_code: Optional[str] = Field(default=None, max_length=45)
|
|
113
|
+
description: str = Field(max_length=100)
|
|
114
|
+
amount: Decimal = Field(max_digits=20)
|
|
115
|
+
note: Optional[str] = Field(default=None, max_length=255)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Surcharge(BaseModel):
|
|
119
|
+
surcharge_code: Optional[str] = Field(default=None, max_length=45)
|
|
120
|
+
description: str = Field(max_length=100)
|
|
121
|
+
amount: Decimal = Field(max_digits=20)
|
|
122
|
+
note: Optional[str] = Field(default=None, max_length=255)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Tax(BaseModel):
|
|
126
|
+
tax_code: Optional[str] = Field(default=None, max_length=20)
|
|
127
|
+
description: Optional[str] = Field(default=None, max_length=100)
|
|
128
|
+
amount: Decimal
|
|
129
|
+
note: Optional[str] = Field(default=None, max_length=255)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Line(BaseModel):
|
|
133
|
+
sender_product_code: Optional[str]
|
|
134
|
+
tax_product_code: Optional[str]
|
|
135
|
+
line_description: str = Field(max_length=255)
|
|
136
|
+
line_quantity: Decimal = Field(max_digits=20)
|
|
137
|
+
line_unit_price: Decimal = Field(max_digits=20)
|
|
138
|
+
note: Optional[str] = Field(default=None, max_length=100)
|
|
139
|
+
discounts: Optional[List[Discount]] = None
|
|
140
|
+
surcharges: Optional[List[Surcharge]] = None
|
|
141
|
+
taxes: Optional[List[Tax]] = None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SenderStaffData(BaseModel):
|
|
145
|
+
name: Optional[str] = Field(default=None, max_length=100)
|
|
146
|
+
email: Optional[str] = Field(default=None, max_length=255)
|
|
147
|
+
phone: Optional[str] = Field(default=None, max_length=20)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class InvoiceCreateSimpleRequest(BaseModel):
|
|
151
|
+
invoice_code: str = Field(examples=["TEST_INVOICE"], max_length=45)
|
|
152
|
+
sender_invoice_no: str = Field(examples=["123"], max_length=45)
|
|
153
|
+
invoice_receiver_code: str = Field(max_length=45)
|
|
154
|
+
invoice_description: str = Field(max_length=255)
|
|
155
|
+
sender_branch_code: Optional[str] = Field(default=None, max_length=45)
|
|
156
|
+
amount: Decimal = Field(gt=0)
|
|
157
|
+
callback_url: str = Field(max_length=255)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class InvoiceCreateRequest(BaseModel):
|
|
161
|
+
invoice_code: str = Field(max_length=45)
|
|
162
|
+
sender_invoice_no: str = Field(max_length=45)
|
|
163
|
+
sender_branch_code: Optional[str] = Field(default=None, max_length=45)
|
|
164
|
+
sender_branch_data: Optional[SenderBranchData] = None
|
|
165
|
+
sender_staff_code: Optional[str] = Field(default=None, max_length=100)
|
|
166
|
+
sender_staff_data: Optional[SenderStaffData] = None
|
|
167
|
+
sender_terminal_code: Optional[str] = Field(default=None, max_length=45)
|
|
168
|
+
sender_terminal_data: Optional[SenderTerminalData] = None
|
|
169
|
+
invoice_receiver_code: str = Field(max_length=45)
|
|
170
|
+
invoice_receiver_data: Optional[InvoiceReceiverData] = None
|
|
171
|
+
invoice_description: str = Field(max_length=255)
|
|
172
|
+
invoice_due_date: Optional[date] = None
|
|
173
|
+
enable_expiry: Optional[bool] = None
|
|
174
|
+
expiry_date: Optional[date] = None
|
|
175
|
+
calculate_vat: Optional[bool] = Field(default=None)
|
|
176
|
+
tax_customer_code: Optional[str] = None
|
|
177
|
+
line_tax_code: Optional[str] = Field(default=None)
|
|
178
|
+
allow_partial: Optional[bool] = Field(default=None)
|
|
179
|
+
minimum_amount: Optional[Decimal] = None
|
|
180
|
+
allow_exceed: Optional[bool] = Field(default=None)
|
|
181
|
+
maximum_amount: Optional[Decimal] = None
|
|
182
|
+
amount: Optional[Decimal] = Field(default=None)
|
|
183
|
+
callback_url: str = Field(max_length=255)
|
|
184
|
+
note: Optional[str] = Field(default=None, max_length=1000)
|
|
185
|
+
lines: Optional[List[Line]] = None
|
|
186
|
+
transactions: Optional[List] = None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class CreateInvoiceResponse(BaseModel):
|
|
190
|
+
invoice_id: str
|
|
191
|
+
qr_text: str
|
|
192
|
+
qr_image: str
|
|
193
|
+
qPay_shortUrl: str
|
|
194
|
+
urls: List[QPayDeeplink]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class CardTransaction(BaseModel):
|
|
198
|
+
card_type: str
|
|
199
|
+
is_cross_border: bool
|
|
200
|
+
amount: Decimal
|
|
201
|
+
currency: Currency
|
|
202
|
+
date: datetime
|
|
203
|
+
status: str
|
|
204
|
+
settlement_status: str
|
|
205
|
+
settlement_status_date: datetime
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class P2PTransaction(BaseModel):
|
|
209
|
+
transaction_bank_code: BankCode
|
|
210
|
+
account_bank_code: BankCode
|
|
211
|
+
account_bank_name: str
|
|
212
|
+
account_number: str
|
|
213
|
+
status: str
|
|
214
|
+
amount: Decimal
|
|
215
|
+
currency: Currency
|
|
216
|
+
settlement_status: str
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class Payment(BaseModel):
|
|
220
|
+
payment_id: Decimal
|
|
221
|
+
payment_status: PaymentStatus
|
|
222
|
+
payment_amount: Decimal
|
|
223
|
+
trx_fee: Decimal
|
|
224
|
+
payment_currency: Currency
|
|
225
|
+
payment_wallet: str
|
|
226
|
+
payment_type: str
|
|
227
|
+
next_payment_date: Optional[date] = None
|
|
228
|
+
next_payment_datetime: Optional[datetime] = None
|
|
229
|
+
card_transactions: List[CardTransaction]
|
|
230
|
+
p2p_transactions: List[P2PTransaction]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class Offset(BaseModel):
|
|
234
|
+
page_number: Decimal = Field(default=Decimal(1), ge=1, le=100)
|
|
235
|
+
page_limit: Decimal = Field(default=Decimal(10), ge=1, le=100)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class PaymentCheckResponse(BaseModel):
|
|
239
|
+
count: Optional[Decimal] = None
|
|
240
|
+
paid_amount: Optional[Decimal] = None
|
|
241
|
+
rows: Optional[List[Payment]] = None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class PaymentCheckRequest(BaseModel):
|
|
245
|
+
object_type: ObjectTypeNum
|
|
246
|
+
object_id: str = Field(max_length=50)
|
|
247
|
+
offset: Optional[Offset] = Field(default_factory=Offset)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class CancelPaymentRequest(Payment):
|
|
251
|
+
callback_url: str
|
|
252
|
+
note: str
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class EbarimtCreateRequest(BaseModel):
|
|
256
|
+
payment_id: str
|
|
257
|
+
ebarimt_receiver_type: str
|
|
258
|
+
ebarimt_receiver: Optional[str] = None
|
|
259
|
+
callback_url: Optional[str] = None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class Ebarimt(BaseModel):
|
|
263
|
+
id: str
|
|
264
|
+
ebarimt_by: str
|
|
265
|
+
g_wallet_id: str
|
|
266
|
+
g_wallet_customer_id: str
|
|
267
|
+
ebarim_receiver_type: str
|
|
268
|
+
ebarimt_receiver: str
|
|
269
|
+
ebarimt_district_code: str
|
|
270
|
+
ebarimt_bill_type: str
|
|
271
|
+
g_merchant_id: str
|
|
272
|
+
merchant_branch_code: str
|
|
273
|
+
merchant_terminal_code: str
|
|
274
|
+
merchant_staff_code: str
|
|
275
|
+
merchant_register: Decimal
|
|
276
|
+
g_payment_id: Decimal
|
|
277
|
+
paid_by: str
|
|
278
|
+
object_type: str
|
|
279
|
+
object_id: str
|
|
280
|
+
amount: Decimal
|
|
281
|
+
vat_amount: Decimal
|
|
282
|
+
city_tax_amount: Decimal
|
|
283
|
+
ebarimt_qr_data: str
|
|
284
|
+
ebarimt_lottery: str
|
|
285
|
+
note: str
|
|
286
|
+
ebarimt_status: str
|
|
287
|
+
ebarimt_status_date: datetime
|
|
288
|
+
tax_type: str
|
|
289
|
+
created_by: str
|
|
290
|
+
created_date: datetime
|
|
291
|
+
updated_by: str
|
|
292
|
+
updated_date: datetime
|
|
293
|
+
status: bool
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class PaymentListRequest(BaseModel):
|
|
297
|
+
object_type: str
|
|
298
|
+
object_id: str
|
|
299
|
+
start_date: datetime
|
|
300
|
+
end_date: datetime
|
|
301
|
+
offset: Offset
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class PaymentCancelRequest(BaseModel):
|
|
305
|
+
callback_url: Optional[str] = None
|
|
306
|
+
note: Optional[str] = None
|