bootpay-backend 2.3.0__tar.gz → 2.7.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.
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/PKG-INFO +4 -3
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/README.md +3 -2
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/__init__.py +24 -2
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/commerce_resource.py +53 -26
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/__init__.py +5 -1
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/category.py +92 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/invoice.py +106 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/mall_setting.py +63 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order.py +6 -1
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/order_cancel.py +139 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/order_subscription.py +390 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/order_subscription_adjustment.py +102 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_subscription_bill.py +32 -16
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/order_subscription_request.py +131 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/product.py +235 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/store.py +45 -0
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/user.py +238 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/user_group.py +55 -9
- bootpay_backend-2.7.0/bootpay_backend/commerce/modules/webhook.py +34 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/types/__init__.py +482 -2
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/rest_client.py +63 -2
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/PKG-INFO +4 -3
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/SOURCES.txt +10 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/setup.py +1 -1
- bootpay_backend-2.7.0/tests/commerce/test_auth_contract.py +86 -0
- bootpay_backend-2.7.0/tests/commerce/test_mall_setting.py +20 -0
- bootpay_backend-2.7.0/tests/commerce/test_order_subscription_charge.py +50 -0
- bootpay_backend-2.7.0/tests/commerce/test_product_mall.py +27 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/commerce/test_token.py +7 -0
- bootpay_backend-2.7.0/tests/commerce/test_user_mall.py +44 -0
- bootpay_backend-2.7.0/tests/commerce/test_webhook.py +20 -0
- bootpay_backend-2.7.0/tests/commerce/test_wire_format.py +728 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/conftest.py +13 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_legacy_compatibility.py +59 -0
- bootpay_backend-2.7.0/tests/pg/test_sequential_billing_key.py +29 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_token.py +7 -0
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/category.py +0 -60
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/invoice.py +0 -61
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/order_cancel.py +0 -77
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/order_subscription.py +0 -217
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/order_subscription_adjustment.py +0 -52
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/order_subscription_request.py +0 -92
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/product.py +0 -93
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/store.py +0 -25
- bootpay_backend-2.3.0/bootpay_backend/commerce/modules/user.py +0 -114
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/LICENSE +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/__init__.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/bankcode.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/cart.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/coupon.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/point.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/dependency_links.txt +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/not-zip-safe +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/requires.txt +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/top_level.txt +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/setup.cfg +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/__init__.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/commerce/__init__.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/commerce/test_order.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/commerce/test_product.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/commerce/test_store.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/commerce/test_user.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/__init__.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_auth.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_billing.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_cash_receipt.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_payment.py +0 -0
- {bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/tests/pg/test_wallet.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bootpay_backend
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: bootpay server side plugin for python
|
|
5
5
|
Home-page: https://github.com/bootpay/backend-python/tree/2-x-development
|
|
6
6
|
Author: bootpay <bootpay.co.kr@gmail.com>
|
|
@@ -444,11 +444,12 @@ if 'error_code' not in token:
|
|
|
444
444
|
|
|
445
445
|
## Documentation
|
|
446
446
|
|
|
447
|
-
[부트페이
|
|
447
|
+
- [부트페이 결제 개발문서](https://developers.bootpay.ai)
|
|
448
|
+
- [부트페이 커머스 개발문서](https://commerce.bootpay.ai)
|
|
448
449
|
|
|
449
450
|
## 기술문의
|
|
450
451
|
|
|
451
|
-
[부트페이 홈페이지](https://www.bootpay.
|
|
452
|
+
[부트페이 홈페이지](https://www.bootpay.ai)에서 문의해주세요!
|
|
452
453
|
|
|
453
454
|
## License
|
|
454
455
|
|
|
@@ -404,11 +404,12 @@ if 'error_code' not in token:
|
|
|
404
404
|
|
|
405
405
|
## Documentation
|
|
406
406
|
|
|
407
|
-
[부트페이
|
|
407
|
+
- [부트페이 결제 개발문서](https://developers.bootpay.ai)
|
|
408
|
+
- [부트페이 커머스 개발문서](https://commerce.bootpay.ai)
|
|
408
409
|
|
|
409
410
|
## 기술문의
|
|
410
411
|
|
|
411
|
-
[부트페이 홈페이지](https://www.bootpay.
|
|
412
|
+
[부트페이 홈페이지](https://www.bootpay.ai)에서 문의해주세요!
|
|
412
413
|
|
|
413
414
|
## License
|
|
414
415
|
|
|
@@ -15,7 +15,9 @@ from .modules import (
|
|
|
15
15
|
CouponModule,
|
|
16
16
|
PointModule,
|
|
17
17
|
CartModule,
|
|
18
|
-
OrderSubscriptionRequestModule
|
|
18
|
+
OrderSubscriptionRequestModule,
|
|
19
|
+
MallSettingModule,
|
|
20
|
+
WebhookModule
|
|
19
21
|
)
|
|
20
22
|
from .types import *
|
|
21
23
|
|
|
@@ -55,7 +57,7 @@ class BootpayCommerce(BootpayCommerceResource):
|
|
|
55
57
|
:param mode: 환경 ('development', 'stage', 'production')
|
|
56
58
|
"""
|
|
57
59
|
super().__init__()
|
|
58
|
-
if client_key
|
|
60
|
+
if client_key is not None or secret_key is not None:
|
|
59
61
|
self.set_configuration(client_key, secret_key, mode)
|
|
60
62
|
self._init_modules()
|
|
61
63
|
|
|
@@ -76,6 +78,8 @@ class BootpayCommerce(BootpayCommerceResource):
|
|
|
76
78
|
self.point = PointModule(self)
|
|
77
79
|
self.cart = CartModule(self)
|
|
78
80
|
self.order_subscription_request = OrderSubscriptionRequestModule(self)
|
|
81
|
+
self.mall_setting = MallSettingModule(self)
|
|
82
|
+
self.webhook = WebhookModule(self)
|
|
79
83
|
|
|
80
84
|
def get_access_token(self):
|
|
81
85
|
"""
|
|
@@ -206,6 +210,8 @@ __all__ = [
|
|
|
206
210
|
'PointModule',
|
|
207
211
|
'CartModule',
|
|
208
212
|
'OrderSubscriptionRequestModule',
|
|
213
|
+
'MallSettingModule',
|
|
214
|
+
'WebhookModule',
|
|
209
215
|
# Types
|
|
210
216
|
'ListParams',
|
|
211
217
|
'CommerceAddress',
|
|
@@ -287,4 +293,20 @@ __all__ = [
|
|
|
287
293
|
'OrderSubscriptionRequest',
|
|
288
294
|
'OrderSubscriptionRequestListParams',
|
|
289
295
|
'OrderSubscriptionRequestUpdateParams',
|
|
296
|
+
'InvoiceListResponse',
|
|
297
|
+
'OrderCancelWithdrawParams',
|
|
298
|
+
'OrderSubscriptionPurchaseParams',
|
|
299
|
+
'OrderSubscriptionTransferParams',
|
|
300
|
+
'SupervisorOrderSubscriptionChargeParams',
|
|
301
|
+
'SupervisorOrderSubscriptionChargeRevokeParams',
|
|
302
|
+
'OrderSubscriptionChargeResponse',
|
|
303
|
+
'OrderSubscriptionChargeRevokeResponse',
|
|
304
|
+
'MallProductListParams',
|
|
305
|
+
'MallUserLoginParams',
|
|
306
|
+
'MallUserJoinParams',
|
|
307
|
+
'MallUserJoinCheckType',
|
|
308
|
+
'MallUserSessionResponse',
|
|
309
|
+
'MallSettingUpdateParams',
|
|
310
|
+
'CommerceMallSetting',
|
|
311
|
+
'SendTestWebhookParams',
|
|
290
312
|
]
|
{bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/commerce_resource.py
RENAMED
|
@@ -53,19 +53,28 @@ class BootpayCommerceResource:
|
|
|
53
53
|
|
|
54
54
|
def _get_basic_auth_header(self) -> str:
|
|
55
55
|
"""Basic Auth 헤더 생성"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
has_client_key = bool(self.client_key)
|
|
57
|
+
has_secret_key = bool(self.secret_key)
|
|
58
|
+
if not has_client_key and not has_secret_key:
|
|
59
|
+
raise ValueError('Commerce API에는 client_key와 secret_key가 필요합니다.')
|
|
60
|
+
if has_client_key != has_secret_key:
|
|
61
|
+
missing = 'secret_key' if has_client_key else 'client_key'
|
|
62
|
+
raise ValueError(f'{missing} 값이 비어있습니다. client_key와 secret_key는 함께 지정해야 합니다.')
|
|
63
|
+
credentials = f'{self.client_key}:{self.secret_key}'
|
|
64
|
+
encoded = base64.b64encode(credentials.encode()).decode()
|
|
65
|
+
return f'Basic {encoded}'
|
|
61
66
|
|
|
62
67
|
def _entrypoints(self, url: str) -> str:
|
|
63
68
|
"""엔트리포인트 URL 생성"""
|
|
64
69
|
return '/'.join([self.API_ENTRYPOINTS[self.mode], url])
|
|
65
70
|
|
|
66
|
-
def _get_headers(self, include_auth: bool = True) -> Dict[str, str]:
|
|
67
|
-
"""
|
|
68
|
-
|
|
71
|
+
def _get_headers(self, include_auth: bool = True, headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
72
|
+
"""
|
|
73
|
+
공통 헤더 생성
|
|
74
|
+
요청별로 지정된 헤더(headers)가 공통값을 덮어쓴다 —
|
|
75
|
+
supervisor 전용 endpoint 의 BOOTPAY-ROLE 을 공통 계층이 덮어쓰지 않기 위함.
|
|
76
|
+
"""
|
|
77
|
+
merged = {
|
|
69
78
|
'Content-Type': 'application/json',
|
|
70
79
|
'Accept': 'application/json',
|
|
71
80
|
'Accept-Charset': 'utf-8',
|
|
@@ -77,24 +86,26 @@ class BootpayCommerceResource:
|
|
|
77
86
|
if include_auth:
|
|
78
87
|
basic_auth = self._get_basic_auth_header()
|
|
79
88
|
if basic_auth:
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
merged['Authorization'] = basic_auth
|
|
90
|
+
if headers:
|
|
91
|
+
merged.update(headers)
|
|
92
|
+
return merged
|
|
82
93
|
|
|
83
|
-
def get(self, url: str, params: Optional[Dict] = None):
|
|
94
|
+
def get(self, url: str, params: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None):
|
|
84
95
|
"""GET 요청"""
|
|
85
96
|
response = requests.get(
|
|
86
97
|
self._entrypoints(url),
|
|
87
|
-
headers=self._get_headers(),
|
|
98
|
+
headers=self._get_headers(headers=headers),
|
|
88
99
|
params=params,
|
|
89
100
|
timeout=self.timeout
|
|
90
101
|
)
|
|
91
102
|
return response.json()
|
|
92
103
|
|
|
93
|
-
def post(self, url: str, data: Optional[Dict] = None):
|
|
104
|
+
def post(self, url: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None):
|
|
94
105
|
"""POST 요청"""
|
|
95
106
|
response = requests.post(
|
|
96
107
|
self._entrypoints(url),
|
|
97
|
-
headers=self._get_headers(),
|
|
108
|
+
headers=self._get_headers(headers=headers),
|
|
98
109
|
json=data,
|
|
99
110
|
timeout=self.timeout
|
|
100
111
|
)
|
|
@@ -112,14 +123,18 @@ class BootpayCommerceResource:
|
|
|
112
123
|
)
|
|
113
124
|
return response.json()
|
|
114
125
|
|
|
115
|
-
def post_multipart(self, url: str, data: Dict, image_paths: Optional[List[str]] = None
|
|
126
|
+
def post_multipart(self, url: str, data: Dict, image_paths: Optional[List[str]] = None,
|
|
127
|
+
headers: Optional[Dict[str, str]] = None):
|
|
116
128
|
"""
|
|
117
129
|
Multipart/form-data POST 요청 (이미지 업로드 포함)
|
|
130
|
+
⚠️ Content-Type 은 requests 가 생성한 값(boundary 포함)을 그대로 사용한다.
|
|
131
|
+
직접 지정하면 boundary 가 사라져 본문이 서버에서 null 로 파싱된다.
|
|
118
132
|
:param url: 요청 URL
|
|
119
133
|
:param data: 폼 데이터
|
|
120
134
|
:param image_paths: 이미지 파일 경로 배열
|
|
135
|
+
:param headers: 요청별 추가 헤더 (공통값을 덮어쓴다)
|
|
121
136
|
"""
|
|
122
|
-
|
|
137
|
+
request_headers = {
|
|
123
138
|
'Accept': 'application/json',
|
|
124
139
|
'Accept-Charset': 'utf-8',
|
|
125
140
|
'BOOTPAY-SDK-VERSION': self.SDK_VERSION,
|
|
@@ -129,28 +144,35 @@ class BootpayCommerceResource:
|
|
|
129
144
|
}
|
|
130
145
|
basic_auth = self._get_basic_auth_header()
|
|
131
146
|
if basic_auth:
|
|
132
|
-
|
|
147
|
+
request_headers['Authorization'] = basic_auth
|
|
148
|
+
if headers:
|
|
149
|
+
request_headers.update(headers)
|
|
133
150
|
|
|
134
151
|
# 폼 데이터 준비
|
|
152
|
+
# ⚠️ bool 은 소문자 'true'/'false' 로 보낸다 — str(True) 의 'True'/'False' 는
|
|
153
|
+
# Rails 캐스팅 대상이 아니라서 "False" 가 true 로 해석되는 실위험이 있다.
|
|
135
154
|
form_data = {}
|
|
136
155
|
for key, value in data.items():
|
|
137
156
|
if value is not None:
|
|
138
|
-
if isinstance(value,
|
|
157
|
+
if isinstance(value, bool):
|
|
158
|
+
form_data[key] = 'true' if value else 'false'
|
|
159
|
+
elif isinstance(value, (dict, list)):
|
|
139
160
|
form_data[key] = json.dumps(value)
|
|
140
161
|
else:
|
|
141
162
|
form_data[key] = str(value)
|
|
142
163
|
|
|
143
164
|
# 파일 준비
|
|
165
|
+
# ⚠️ Rails 는 반복된 `images` 를 배열로 받지 않는다. images[0], images[1] ... 로 인덱싱해야 한다.
|
|
144
166
|
files = []
|
|
145
167
|
if image_paths:
|
|
146
|
-
for image_path in image_paths:
|
|
168
|
+
for index, image_path in enumerate(image_paths):
|
|
147
169
|
filename = os.path.basename(image_path)
|
|
148
|
-
files.append(('images', (filename, open(image_path, 'rb'))))
|
|
170
|
+
files.append((f'images[{index}]', (filename, open(image_path, 'rb'))))
|
|
149
171
|
|
|
150
172
|
try:
|
|
151
173
|
response = requests.post(
|
|
152
174
|
self._entrypoints(url),
|
|
153
|
-
headers=
|
|
175
|
+
headers=request_headers,
|
|
154
176
|
data=form_data,
|
|
155
177
|
files=files if files else None,
|
|
156
178
|
timeout=self.timeout
|
|
@@ -161,22 +183,27 @@ class BootpayCommerceResource:
|
|
|
161
183
|
for _, file_tuple in files:
|
|
162
184
|
file_tuple[1].close()
|
|
163
185
|
|
|
164
|
-
def put(self, url: str, data: Optional[Dict] = None):
|
|
186
|
+
def put(self, url: str, data: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None):
|
|
165
187
|
"""PUT 요청"""
|
|
166
188
|
response = requests.put(
|
|
167
189
|
self._entrypoints(url),
|
|
168
|
-
headers=self._get_headers(),
|
|
190
|
+
headers=self._get_headers(headers=headers),
|
|
169
191
|
json=data,
|
|
170
192
|
timeout=self.timeout
|
|
171
193
|
)
|
|
172
194
|
return response.json()
|
|
173
195
|
|
|
174
|
-
def delete(self, url: str, params: Optional[Dict] = None
|
|
175
|
-
|
|
196
|
+
def delete(self, url: str, params: Optional[Dict] = None, headers: Optional[Dict[str, str]] = None,
|
|
197
|
+
data: Optional[Dict] = None):
|
|
198
|
+
"""
|
|
199
|
+
DELETE 요청
|
|
200
|
+
:param data: 요청 body (JSON) — 대상 ID 를 query 가 아닌 body 로 보내는 endpoint 용
|
|
201
|
+
"""
|
|
176
202
|
response = requests.delete(
|
|
177
203
|
self._entrypoints(url),
|
|
178
|
-
headers=self._get_headers(),
|
|
204
|
+
headers=self._get_headers(headers=headers),
|
|
179
205
|
params=params,
|
|
206
|
+
json=data,
|
|
180
207
|
timeout=self.timeout
|
|
181
208
|
)
|
|
182
209
|
return response.json()
|
{bootpay_backend-2.3.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/__init__.py
RENAMED
|
@@ -14,6 +14,8 @@ from .coupon import CouponModule
|
|
|
14
14
|
from .point import PointModule
|
|
15
15
|
from .cart import CartModule
|
|
16
16
|
from .order_subscription_request import OrderSubscriptionRequestModule
|
|
17
|
+
from .mall_setting import MallSettingModule
|
|
18
|
+
from .webhook import WebhookModule
|
|
17
19
|
|
|
18
20
|
__all__ = [
|
|
19
21
|
'UserModule',
|
|
@@ -30,5 +32,7 @@ __all__ = [
|
|
|
30
32
|
'CouponModule',
|
|
31
33
|
'PointModule',
|
|
32
34
|
'CartModule',
|
|
33
|
-
'OrderSubscriptionRequestModule'
|
|
35
|
+
'OrderSubscriptionRequestModule',
|
|
36
|
+
'MallSettingModule',
|
|
37
|
+
'WebhookModule'
|
|
34
38
|
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from ..commerce_resource import BootpayCommerceResource
|
|
6
|
+
|
|
7
|
+
from ..types import (
|
|
8
|
+
CommerceCategory,
|
|
9
|
+
CategoryCreateParams,
|
|
10
|
+
CategoryUpdateParams
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CategoryModule:
|
|
15
|
+
"""카테고리 모듈"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, bootpay: 'BootpayCommerceResource'):
|
|
18
|
+
self._bootpay = bootpay
|
|
19
|
+
|
|
20
|
+
def list(self):
|
|
21
|
+
"""
|
|
22
|
+
카테고리 트리 조회
|
|
23
|
+
:return: List[CommerceCategory]
|
|
24
|
+
"""
|
|
25
|
+
return self._bootpay.get('categories')
|
|
26
|
+
|
|
27
|
+
def detail(self, category_id: str):
|
|
28
|
+
"""
|
|
29
|
+
카테고리 단건 조회
|
|
30
|
+
:param category_id: 카테고리 ID
|
|
31
|
+
:return: CommerceCategory
|
|
32
|
+
"""
|
|
33
|
+
return self._bootpay.get(f'categories/{category_id}')
|
|
34
|
+
|
|
35
|
+
def create(self, params: CategoryCreateParams):
|
|
36
|
+
"""
|
|
37
|
+
카테고리 생성
|
|
38
|
+
POST /v1/categories
|
|
39
|
+
⚠️ 서버가 supervisor scope 를 요구한다 (scope_invalid!).
|
|
40
|
+
:param params: 카테고리 생성 파라미터
|
|
41
|
+
:return: CommerceCategory
|
|
42
|
+
"""
|
|
43
|
+
params = dict(params or {})
|
|
44
|
+
idempotency_key = params.pop('idempotency_key', None)
|
|
45
|
+
return self._bootpay.post(
|
|
46
|
+
'categories',
|
|
47
|
+
params,
|
|
48
|
+
headers=self._supervisor_headers(idempotency_key)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def update(self, params: CategoryUpdateParams):
|
|
52
|
+
"""
|
|
53
|
+
카테고리 수정
|
|
54
|
+
PUT /v1/categories/{category_id}
|
|
55
|
+
⚠️ 서버가 supervisor scope 를 요구한다 (scope_invalid!).
|
|
56
|
+
:param params: 카테고리 수정 파라미터 (category_id 필수)
|
|
57
|
+
:return: CommerceCategory
|
|
58
|
+
"""
|
|
59
|
+
if not params.get('category_id'):
|
|
60
|
+
raise ValueError('category_id is required')
|
|
61
|
+
params = dict(params)
|
|
62
|
+
category_id = params.pop('category_id')
|
|
63
|
+
idempotency_key = params.pop('idempotency_key', None)
|
|
64
|
+
return self._bootpay.put(
|
|
65
|
+
f'categories/{category_id}',
|
|
66
|
+
params,
|
|
67
|
+
headers=self._supervisor_headers(idempotency_key)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def destroy(self, category_id: str, idempotency_key: Optional[str] = None):
|
|
71
|
+
"""
|
|
72
|
+
카테고리 삭제
|
|
73
|
+
DELETE /v1/categories/{category_id}
|
|
74
|
+
⚠️ 서버가 supervisor scope 를 요구한다 (scope_invalid!).
|
|
75
|
+
:param category_id: 카테고리 ID
|
|
76
|
+
:param idempotency_key: 미지정시 자동 생성
|
|
77
|
+
:return: None
|
|
78
|
+
"""
|
|
79
|
+
return self._bootpay.delete(
|
|
80
|
+
f'categories/{category_id}',
|
|
81
|
+
headers=self._supervisor_headers(idempotency_key)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _supervisor_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
|
|
85
|
+
"""
|
|
86
|
+
카테고리 쓰기(등록/수정/삭제) 요청 헤더 — 서버가 supervisor scope 를 요구한다.
|
|
87
|
+
Idempotency-Key 는 미지정시 매 호출마다 생성된다.
|
|
88
|
+
"""
|
|
89
|
+
return {
|
|
90
|
+
'Idempotency-Key': idempotency_key or str(uuid.uuid4()),
|
|
91
|
+
'BOOTPAY-ROLE': 'supervisor'
|
|
92
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import TYPE_CHECKING, Optional, List, Dict
|
|
3
|
+
from urllib.parse import urlencode
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..commerce_resource import BootpayCommerceResource
|
|
7
|
+
|
|
8
|
+
from ..types import (
|
|
9
|
+
CommerceInvoice,
|
|
10
|
+
InvoiceListParams,
|
|
11
|
+
InvoiceListResponse,
|
|
12
|
+
ListParams
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InvoiceModule:
|
|
17
|
+
"""청구서 모듈"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, bootpay: 'BootpayCommerceResource'):
|
|
20
|
+
self._bootpay = bootpay
|
|
21
|
+
|
|
22
|
+
def list(self, params: Optional[InvoiceListParams] = None):
|
|
23
|
+
"""
|
|
24
|
+
청구서 목록 조회
|
|
25
|
+
GET /v1/invoices
|
|
26
|
+
응답은 {'list': [...], 'count': N} 구조다 ({'items', 'total'} 아님).
|
|
27
|
+
limit 미지정시 서버 기본값과 동일한 24 를 보낸다.
|
|
28
|
+
:param params: 조회 파라미터
|
|
29
|
+
:return: InvoiceListResponse — {'list': List[CommerceInvoice], 'count': int}
|
|
30
|
+
"""
|
|
31
|
+
params = dict(params or {})
|
|
32
|
+
idempotency_key = params.pop('idempotency_key', None)
|
|
33
|
+
|
|
34
|
+
query_params = {
|
|
35
|
+
'page': 1 if params.get('page') is None else params['page'],
|
|
36
|
+
'limit': 24 if params.get('limit') is None else params['limit'],
|
|
37
|
+
}
|
|
38
|
+
if params.get('keyword'):
|
|
39
|
+
query_params['keyword'] = params['keyword']
|
|
40
|
+
if params.get('cs_type'):
|
|
41
|
+
query_params['cs_type'] = params['cs_type']
|
|
42
|
+
if params.get('user_id'):
|
|
43
|
+
query_params['user_id'] = params['user_id']
|
|
44
|
+
if params.get('product_type') is not None:
|
|
45
|
+
query_params['product_type'] = params['product_type']
|
|
46
|
+
if params.get('css_at'):
|
|
47
|
+
query_params['css_at'] = params['css_at']
|
|
48
|
+
if params.get('cse_at'):
|
|
49
|
+
query_params['cse_at'] = params['cse_at']
|
|
50
|
+
|
|
51
|
+
return self._bootpay.get(
|
|
52
|
+
f'invoices?{urlencode(query_params)}',
|
|
53
|
+
headers=self._invoice_headers(idempotency_key)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def create(self, invoice: CommerceInvoice):
|
|
57
|
+
"""
|
|
58
|
+
청구서 생성
|
|
59
|
+
:param invoice: 청구서 정보
|
|
60
|
+
:return: CommerceInvoice
|
|
61
|
+
"""
|
|
62
|
+
return self._bootpay.post('invoices', invoice)
|
|
63
|
+
|
|
64
|
+
def notify(self, invoice_id: str, send_types: Optional[List[int]] = None,
|
|
65
|
+
idempotency_key: Optional[str] = None):
|
|
66
|
+
"""
|
|
67
|
+
청구서 알림 재발송
|
|
68
|
+
POST /v1/invoices/{invoice_id}/notify
|
|
69
|
+
send_types 미전달시 서버가 빈 배열로 처리한다.
|
|
70
|
+
⚠️ 실제 고객에게 알림이 발송되므로 테스트 호출 주의.
|
|
71
|
+
:param invoice_id: 청구서 ID
|
|
72
|
+
:param send_types: 발송 타입 배열 (예: [1, 2] - SMS, Email 등)
|
|
73
|
+
:param idempotency_key: 미지정시 자동 생성
|
|
74
|
+
:return: None
|
|
75
|
+
"""
|
|
76
|
+
payload = {}
|
|
77
|
+
if send_types is not None:
|
|
78
|
+
payload['send_types'] = send_types
|
|
79
|
+
return self._bootpay.post(
|
|
80
|
+
f'invoices/{invoice_id}/notify',
|
|
81
|
+
payload,
|
|
82
|
+
headers=self._invoice_headers(idempotency_key)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def detail(self, invoice_id: str, idempotency_key: Optional[str] = None):
|
|
86
|
+
"""
|
|
87
|
+
청구서 상세 조회
|
|
88
|
+
GET /v1/invoices/{invoice_id}
|
|
89
|
+
:param invoice_id: 청구서 ID
|
|
90
|
+
:param idempotency_key: 미지정시 자동 생성
|
|
91
|
+
:return: CommerceInvoice
|
|
92
|
+
"""
|
|
93
|
+
return self._bootpay.get(
|
|
94
|
+
f'invoices/{invoice_id}',
|
|
95
|
+
headers=self._invoice_headers(idempotency_key)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _invoice_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
|
|
99
|
+
"""
|
|
100
|
+
청구서 API 요청 헤더
|
|
101
|
+
Idempotency-Key 는 미지정시 매 호출마다 생성된다.
|
|
102
|
+
"""
|
|
103
|
+
return {
|
|
104
|
+
'Idempotency-Key': idempotency_key or str(uuid.uuid4()),
|
|
105
|
+
'BOOTPAY-ROLE': 'user'
|
|
106
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import TYPE_CHECKING, Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from ..commerce_resource import BootpayCommerceResource
|
|
6
|
+
|
|
7
|
+
from ..types import (
|
|
8
|
+
CommerceMallSetting,
|
|
9
|
+
MallSettingUpdateParams
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MallSettingModule:
|
|
14
|
+
"""몰 설정 모듈 (supervisor scope 전용)"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, bootpay: 'BootpayCommerceResource'):
|
|
17
|
+
self._bootpay = bootpay
|
|
18
|
+
|
|
19
|
+
def get_mall_setting(self, idempotency_key: Optional[str] = None):
|
|
20
|
+
"""
|
|
21
|
+
몰 설정 조회
|
|
22
|
+
GET /v1/mall-setting (supervisor scope 토큰 전용)
|
|
23
|
+
:param idempotency_key: 미지정시 자동 생성
|
|
24
|
+
:return: CommerceMallSetting
|
|
25
|
+
"""
|
|
26
|
+
return self._bootpay.get('mall-setting', headers=self._supervisor_headers(idempotency_key))
|
|
27
|
+
|
|
28
|
+
def detail(self, idempotency_key: Optional[str] = None):
|
|
29
|
+
"""get_mall_setting 별칭"""
|
|
30
|
+
return self.get_mall_setting(idempotency_key)
|
|
31
|
+
|
|
32
|
+
def update_mall_setting(self, params: MallSettingUpdateParams, idempotency_key: Optional[str] = None):
|
|
33
|
+
"""
|
|
34
|
+
몰 설정 수정
|
|
35
|
+
PUT /v1/mall-setting (supervisor scope 토큰 전용)
|
|
36
|
+
요청 바디는 flatten 형식이며 전달된 값(non-None)만 서버로 전송된다.
|
|
37
|
+
:param params: 수정할 설정값
|
|
38
|
+
:param idempotency_key: 미지정시 자동 생성
|
|
39
|
+
:return: CommerceMallSetting
|
|
40
|
+
"""
|
|
41
|
+
return self._bootpay.put(
|
|
42
|
+
'mall-setting',
|
|
43
|
+
self._compact(params),
|
|
44
|
+
headers=self._supervisor_headers(idempotency_key)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def update(self, params: MallSettingUpdateParams, idempotency_key: Optional[str] = None):
|
|
48
|
+
"""update_mall_setting 별칭"""
|
|
49
|
+
return self.update_mall_setting(params, idempotency_key)
|
|
50
|
+
|
|
51
|
+
def _compact(self, params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
52
|
+
"""None 값을 제거한다."""
|
|
53
|
+
return {k: v for k, v in (params or {}).items() if v is not None}
|
|
54
|
+
|
|
55
|
+
def _supervisor_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
|
|
56
|
+
"""
|
|
57
|
+
supervisor 전용 요청 헤더
|
|
58
|
+
Idempotency-Key 는 미지정시 매 호출마다 생성된다.
|
|
59
|
+
"""
|
|
60
|
+
return {
|
|
61
|
+
'Idempotency-Key': idempotency_key or str(uuid.uuid4()),
|
|
62
|
+
'BOOTPAY-ROLE': 'supervisor'
|
|
63
|
+
}
|
|
@@ -36,6 +36,11 @@ class OrderModule:
|
|
|
36
36
|
query_params['user_group_id'] = params['user_group_id']
|
|
37
37
|
if params.get('cs_type'):
|
|
38
38
|
query_params['cs_type'] = params['cs_type']
|
|
39
|
+
if params.get('search_date_from'):
|
|
40
|
+
query_params['search_date_from'] = params['search_date_from']
|
|
41
|
+
if params.get('search_date_to'):
|
|
42
|
+
query_params['search_date_to'] = params['search_date_to']
|
|
43
|
+
# css_at / cse_at 는 서버 별칭으로 계속 지원 (정식 키는 search_date_from / search_date_to)
|
|
39
44
|
if params.get('css_at'):
|
|
40
45
|
query_params['css_at'] = params['css_at']
|
|
41
46
|
if params.get('cse_at'):
|
|
@@ -47,7 +52,7 @@ class OrderModule:
|
|
|
47
52
|
if params.get('payment_status') and len(params['payment_status']) > 0:
|
|
48
53
|
query_params['payment_status'] = ','.join(map(str, params['payment_status']))
|
|
49
54
|
if params.get('order_subscription_ids') and len(params['order_subscription_ids']) > 0:
|
|
50
|
-
query_params['order_subscription_ids'] = ','.join(params['order_subscription_ids'])
|
|
55
|
+
query_params['order_subscription_ids'] = ','.join(map(str, params['order_subscription_ids']))
|
|
51
56
|
|
|
52
57
|
query = urlencode(query_params) if query_params else ''
|
|
53
58
|
return self._bootpay.get(f'orders{"?" + query if query else ""}')
|