bootpay-backend 2.5.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.5.0 → bootpay_backend-2.7.0}/PKG-INFO +1 -1
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order.py +1 -1
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_subscription.py +10 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_subscription_adjustment.py +7 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/product.py +20 -2
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/user.py +8 -2
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/types/__init__.py +37 -3
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/rest_client.py +1 -1
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/PKG-INFO +1 -1
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/setup.py +1 -1
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_wire_format.py +165 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/LICENSE +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/README.md +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/__init__.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/bankcode.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/__init__.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/commerce_resource.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/__init__.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/cart.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/category.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/coupon.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/invoice.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/mall_setting.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_cancel.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_subscription_bill.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_subscription_request.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/point.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/store.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/user_group.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/webhook.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/SOURCES.txt +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/dependency_links.txt +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/not-zip-safe +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/requires.txt +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/top_level.txt +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/setup.cfg +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/__init__.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/__init__.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_auth_contract.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_mall_setting.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_order.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_order_subscription_charge.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_product.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_product_mall.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_store.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_token.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_user.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_user_mall.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_webhook.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/conftest.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/__init__.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_auth.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_billing.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_cash_receipt.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_legacy_compatibility.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_payment.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_sequential_billing_key.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_token.py +0 -0
- {bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/pg/test_wallet.py +0 -0
|
@@ -52,7 +52,7 @@ class OrderModule:
|
|
|
52
52
|
if params.get('payment_status') and len(params['payment_status']) > 0:
|
|
53
53
|
query_params['payment_status'] = ','.join(map(str, params['payment_status']))
|
|
54
54
|
if params.get('order_subscription_ids') and len(params['order_subscription_ids']) > 0:
|
|
55
|
-
query_params['order_subscription_ids'] = ','.join(params['order_subscription_ids'])
|
|
55
|
+
query_params['order_subscription_ids'] = ','.join(map(str, params['order_subscription_ids']))
|
|
56
56
|
|
|
57
57
|
query = urlencode(query_params) if query_params else ''
|
|
58
58
|
return self._bootpay.get(f'orders{"?" + query if query else ""}')
|
|
@@ -193,6 +193,9 @@ class OrderSubscriptionModule:
|
|
|
193
193
|
query_params['status'] = params['status']
|
|
194
194
|
if params.get('user_id'):
|
|
195
195
|
query_params['user_id'] = params['user_id']
|
|
196
|
+
# 주문번호 역조회 — 서버(#index)가 params[:order_number] 로 구독을 찾아준다.
|
|
197
|
+
if params.get('order_number'):
|
|
198
|
+
query_params['order_number'] = params['order_number']
|
|
196
199
|
|
|
197
200
|
query = urlencode(query_params) if query_params else ''
|
|
198
201
|
return self._bootpay.get(f'order_subscriptions{"?" + query if query else ""}')
|
|
@@ -210,6 +213,13 @@ class OrderSubscriptionModule:
|
|
|
210
213
|
구독 계약 내용 변경
|
|
211
214
|
PUT /v1/order_subscriptions/{order_subscription_id}
|
|
212
215
|
바뀐 값만 보내면 된다 (나머지는 서버가 그대로 유지한다). supervisor scope.
|
|
216
|
+
|
|
217
|
+
price 는 회차별 결제 금액의 기준금액이다. 바꾸면 결제예정(READY) 회차의 청구액이
|
|
218
|
+
즉시 다시 계산되고, 이후 회차도 이 금액으로 만들어진다. 이미 결제된 회차는 그대로다.
|
|
219
|
+
0 이하는 받지 않는다. 특정 회차만 가감하려면 order_subscription_adjustment.create 를 쓴다.
|
|
220
|
+
(관리자 화면의 금액 변경과 같은 구현을 탄다)
|
|
221
|
+
|
|
222
|
+
memo 는 변경이력(SUBSCRIPTION_ACTION_UPDATE)에 남길 사유다.
|
|
213
223
|
:param params: 수정 파라미터
|
|
214
224
|
:return: CommerceOrderSubscription
|
|
215
225
|
"""
|
|
@@ -27,6 +27,13 @@ class OrderSubscriptionAdjustmentModule:
|
|
|
27
27
|
가감산 조정항목 추가
|
|
28
28
|
POST /v1/order_subscriptions/{order_subscription_id}/adjustments
|
|
29
29
|
type 미전달시 서버가 price > 0 이면 SETUP_PRICE, 아니면 PERIOD_DISCOUNT 로 자동 판정한다.
|
|
30
|
+
|
|
31
|
+
회차 지정 방법 3가지 (아래로 갈수록 넓다):
|
|
32
|
+
- duration: 5 → 5회차 한 건만
|
|
33
|
+
- duration_from: 3, duration_to: 7 → 3~7회차 각각 한 건씩 (총 5건)
|
|
34
|
+
- duration_from: 3, is_unlimited: True → 3회차부터 계약 끝까지 (레코드는 1건, duration_to 는 무시)
|
|
35
|
+
상한은 계약 총회차이며, 총회차가 무제한인 계약은 60회차까지다.
|
|
36
|
+
이미 결제가 끝난 회차는 거절된다. 범위 중 한 회차라도 최종 금액이 음수면 전부 거절된다(부분 반영 없음).
|
|
30
37
|
:param order_subscription_id: 정기구독 ID
|
|
31
38
|
:param adjustment: 조정 정보 (price/duration/tax_free_price 미지정시 각각 0 / 1 / 0)
|
|
32
39
|
:param idempotency_key: 미지정시 자동 생성
|
|
@@ -24,6 +24,12 @@ class ProductModule:
|
|
|
24
24
|
def list(self, params: Optional[ProductListParams] = None):
|
|
25
25
|
"""
|
|
26
26
|
상품 목록 조회
|
|
27
|
+
GET /v1/products
|
|
28
|
+
|
|
29
|
+
⚠️ 서버(v1/products_controller#index)가 읽는 것은
|
|
30
|
+
page / limit / keyword / category_id / ex_uid / sort 뿐이다.
|
|
31
|
+
type / period_type / s_at / e_at / category_code 는 보내도 에러 없이 무시되고
|
|
32
|
+
전체 목록이 돌아온다 (하위호환을 위해 전송 자체는 유지한다).
|
|
27
33
|
:param params: 조회 파라미터
|
|
28
34
|
:return: {'items': List[CommerceProduct], 'total': int}
|
|
29
35
|
"""
|
|
@@ -35,6 +41,13 @@ class ProductModule:
|
|
|
35
41
|
query_params['limit'] = params['limit']
|
|
36
42
|
if params.get('keyword'):
|
|
37
43
|
query_params['keyword'] = params['keyword']
|
|
44
|
+
if params.get('category_id'):
|
|
45
|
+
query_params['category_id'] = params['category_id']
|
|
46
|
+
if params.get('ex_uid'):
|
|
47
|
+
query_params['ex_uid'] = params['ex_uid']
|
|
48
|
+
if params.get('sort'):
|
|
49
|
+
query_params['sort'] = params['sort']
|
|
50
|
+
# 아래 4개는 서버가 읽지 않는다 — 기존 호출을 깨지 않으려고 전송만 유지한다
|
|
38
51
|
if params.get('type') is not None:
|
|
39
52
|
query_params['type'] = params['type']
|
|
40
53
|
if params.get('period_type'):
|
|
@@ -69,6 +82,8 @@ class ProductModule:
|
|
|
69
82
|
}
|
|
70
83
|
if params.get('category_id'):
|
|
71
84
|
query_params['category_id'] = params['category_id']
|
|
85
|
+
if params.get('ex_uid'):
|
|
86
|
+
query_params['ex_uid'] = params['ex_uid']
|
|
72
87
|
if params.get('sort'):
|
|
73
88
|
query_params['sort'] = params['sort']
|
|
74
89
|
if params.get('keyword'):
|
|
@@ -131,15 +146,18 @@ class ProductModule:
|
|
|
131
146
|
headers=self._mall_headers(user_jwt=user_jwt, idempotency_key=idempotency_key)
|
|
132
147
|
)
|
|
133
148
|
|
|
134
|
-
def lookup_product(self, product_id: str,
|
|
149
|
+
def lookup_product(self, product_id: str, user_jwt: Optional[str] = None,
|
|
150
|
+
idempotency_key: Optional[str] = None):
|
|
135
151
|
"""
|
|
136
152
|
상품 정보 조회 — `product_detail` 과 같은 endpoint 다.
|
|
137
153
|
ruby 동기화 파이프라인이 쓰는 이름이라 호환용으로 둔다.
|
|
154
|
+
매뉴얼이 GET /v1/products/{product_id} 에 user_jwt 를 안내하므로 회원 컨텍스트 조회도 지원한다.
|
|
138
155
|
:param product_id: 상품 ID
|
|
156
|
+
:param user_jwt: 회원 JWT (선택)
|
|
139
157
|
:param idempotency_key: 미지정시 자동 생성
|
|
140
158
|
:return: CommerceProduct
|
|
141
159
|
"""
|
|
142
|
-
return self.product_detail(product_id, idempotency_key=idempotency_key)
|
|
160
|
+
return self.product_detail(product_id, user_jwt=user_jwt, idempotency_key=idempotency_key)
|
|
143
161
|
|
|
144
162
|
def update(self, product: CommerceProduct, idempotency_key: Optional[str] = None):
|
|
145
163
|
"""
|
|
@@ -170,6 +170,9 @@ class UserModule:
|
|
|
170
170
|
def list(self, params: Optional[UserListParams] = None):
|
|
171
171
|
"""
|
|
172
172
|
사용자 목록 조회
|
|
173
|
+
⚠️ 회원등급 필터의 서버(v1/users_controller#index) 키는 membership_type 이다.
|
|
174
|
+
member_type 으로 보내면 에러 없이 조용히 무시되고 전체 목록이 돌아온다.
|
|
175
|
+
기존 호출 호환을 위해 member_type 도 받아 membership_type 으로 매핑한다.
|
|
173
176
|
:param params: 조회 파라미터
|
|
174
177
|
:return: {'items': List[CommerceUser], 'total': int}
|
|
175
178
|
"""
|
|
@@ -181,8 +184,11 @@ class UserModule:
|
|
|
181
184
|
query_params['limit'] = params['limit']
|
|
182
185
|
if params.get('keyword'):
|
|
183
186
|
query_params['keyword'] = params['keyword']
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
membership_type = params.get('membership_type')
|
|
188
|
+
if membership_type is None:
|
|
189
|
+
membership_type = params.get('member_type')
|
|
190
|
+
if membership_type is not None:
|
|
191
|
+
query_params['membership_type'] = membership_type
|
|
186
192
|
if params.get('type'):
|
|
187
193
|
query_params['type'] = params['type']
|
|
188
194
|
|
|
@@ -91,6 +91,9 @@ class CommerceUser(TypedDict, total=False):
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
class UserListParams(ListParams, total=False):
|
|
94
|
+
# 회원등급 필터. 서버가 읽는 정식 키는 membership_type 이다.
|
|
95
|
+
membership_type: int
|
|
96
|
+
# ⚠️ 하위호환 별칭 — 지정하면 membership_type 으로 매핑되어 전송된다.
|
|
94
97
|
member_type: int
|
|
95
98
|
type: str
|
|
96
99
|
|
|
@@ -358,6 +361,23 @@ class CommerceProduct(TypedDict, total=False):
|
|
|
358
361
|
|
|
359
362
|
|
|
360
363
|
class ProductListParams(ListParams, total=False):
|
|
364
|
+
# 상품 목록 조회 파라미터 (GET /v1/products)
|
|
365
|
+
#
|
|
366
|
+
# ⚠️ 서버(v1/products_controller#index)가 실제로 읽는 것은
|
|
367
|
+
# page / limit / keyword / category_id / ex_uid / sort 뿐이다.
|
|
368
|
+
# 아래 type / period_type / s_at / e_at / category_code 는 전송되더라도 에러 없이
|
|
369
|
+
# 무시되고 전체 목록이 돌아온다 — 필터가 걸린 것으로 착각하지 말 것.
|
|
370
|
+
# (keyword 는 26-08-26 서버 변경부터 적용된다. 그 이전 배포본에서는 무시된다.)
|
|
371
|
+
|
|
372
|
+
# 카테고리 ID 로 필터한다 (하위 카테고리 포함)
|
|
373
|
+
category_id: str
|
|
374
|
+
# 외부 UID 로 상품을 찾는다
|
|
375
|
+
ex_uid: str
|
|
376
|
+
# 정렬 키 — position | created_at | -created_at | price | -price | -sold
|
|
377
|
+
sort: str
|
|
378
|
+
|
|
379
|
+
# ── 아래는 서버가 읽지 않는다 (하위호환 때문에 인자만 유지) ──
|
|
380
|
+
# 서버의 상품 타입 필터는 문자열(subscription / discount / normal)이라 값 체계가 다르다
|
|
361
381
|
type: int
|
|
362
382
|
period_type: str
|
|
363
383
|
s_at: str
|
|
@@ -367,9 +387,8 @@ class ProductListParams(ListParams, total=False):
|
|
|
367
387
|
|
|
368
388
|
class MallProductListParams(ProductListParams, total=False):
|
|
369
389
|
# 상품 목록 조회 (V1 Mall API) 파라미터
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
sort: str
|
|
390
|
+
# category_id / ex_uid / sort 는 ProductListParams 에서 상속받는다.
|
|
391
|
+
# 회원 JWT — Bootpay-User-JWT 헤더로 전송된다 (query 에는 포함되지 않는다)
|
|
373
392
|
user_jwt: str
|
|
374
393
|
# 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, query 에는 포함되지 않는다)
|
|
375
394
|
idempotency_key: str
|
|
@@ -716,6 +735,8 @@ class OrderSubscriptionListParams(ListParams, total=False):
|
|
|
716
735
|
user_group_id: str
|
|
717
736
|
user_id: str
|
|
718
737
|
status: int
|
|
738
|
+
# 주문번호로 구독을 역조회한다
|
|
739
|
+
order_number: str
|
|
719
740
|
|
|
720
741
|
|
|
721
742
|
class OrderSubscriptionUpdateParams(TypedDict, total=False):
|
|
@@ -738,6 +759,12 @@ class OrderSubscriptionUpdateParams(TypedDict, total=False):
|
|
|
738
759
|
status: int
|
|
739
760
|
payment_next_at: str
|
|
740
761
|
service_end_at: str
|
|
762
|
+
# 회차별 결제 금액의 기준금액. 바꾸면 결제예정(READY) 회차의 청구액이 즉시 재계산되고
|
|
763
|
+
# 이후 회차도 이 금액으로 만들어진다. 이미 결제된 회차는 그대로다. 0 이하는 받지 않는다.
|
|
764
|
+
# 특정 회차만 가감하려면 order_subscription_adjustment.create 를 쓴다.
|
|
765
|
+
price: int
|
|
766
|
+
# 변경이력(SUBSCRIPTION_ACTION_UPDATE)에 남길 사유
|
|
767
|
+
memo: str
|
|
741
768
|
# 미지정시 자동 생성 (Idempotency-Key 헤더로 전송, body 에는 포함되지 않는다)
|
|
742
769
|
idempotency_key: str
|
|
743
770
|
|
|
@@ -883,6 +910,13 @@ class CommerceOrderSubscriptionAdjustment(TypedDict, total=False):
|
|
|
883
910
|
name: str
|
|
884
911
|
type: int
|
|
885
912
|
created_at: str
|
|
913
|
+
# 회차 지정 방법 3가지 (아래로 갈수록 넓다) — create 전용 파라미터
|
|
914
|
+
# - duration: 5 → 5회차 한 건만
|
|
915
|
+
# - duration_from: 3, duration_to: 7 → 3~7회차 각각 한 건씩 (총 5건)
|
|
916
|
+
# - duration_from: 3, is_unlimited: True → 3회차부터 계약 끝까지 (레코드 1건, duration_to 는 무시)
|
|
917
|
+
duration_from: int
|
|
918
|
+
duration_to: int
|
|
919
|
+
is_unlimited: bool
|
|
886
920
|
|
|
887
921
|
|
|
888
922
|
class OrderSubscriptionAdjustmentUpdateParams(TypedDict, total=False):
|
|
@@ -11,7 +11,7 @@ class BootpayBackend:
|
|
|
11
11
|
'production': 'https://api.bootpay.co.kr/v2'
|
|
12
12
|
}
|
|
13
13
|
API_VERSION = '5.1.0'
|
|
14
|
-
SDK_VERSION = '2.
|
|
14
|
+
SDK_VERSION = '2.7.0'
|
|
15
15
|
|
|
16
16
|
def __init__(self, application_id=None, private_key=None, mode='production', client_key=None, secret_key=None):
|
|
17
17
|
# application_id/private_key는 legacy 사용자를 위해 그대로 지원한다.
|
|
@@ -9,7 +9,7 @@ with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
|
|
|
9
9
|
|
|
10
10
|
setuptools.setup(
|
|
11
11
|
name = 'bootpay_backend',
|
|
12
|
-
version = '2.
|
|
12
|
+
version = '2.7.0',
|
|
13
13
|
description = 'bootpay server side plugin for python',
|
|
14
14
|
long_description=long_description,
|
|
15
15
|
long_description_content_type='text/markdown',
|
|
@@ -246,6 +246,19 @@ def test_products_mall_defaults_page_1_limit_20(commerce, captured):
|
|
|
246
246
|
assert captured['headers']['Bootpay-User-JWT'] == 'member-jwt'
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
def test_product_list_sends_server_read_filters(commerce, captured):
|
|
250
|
+
"""list() 는 서버가 읽는 category_id / ex_uid / sort 를 실어야 한다."""
|
|
251
|
+
commerce.product.list({
|
|
252
|
+
'page': 1, 'limit': 10, 'keyword': '커피',
|
|
253
|
+
'category_id': 'cat1', 'ex_uid': 'EX-1', 'sort': '-price'
|
|
254
|
+
})
|
|
255
|
+
assert captured['method'] == 'get'
|
|
256
|
+
url = captured['url']
|
|
257
|
+
for fragment in ('page=1', 'limit=10', 'category_id=cat1', 'ex_uid=EX-1', 'sort=-price'):
|
|
258
|
+
assert fragment in url, f'{fragment} not in {url}'
|
|
259
|
+
assert 'keyword=' in url
|
|
260
|
+
|
|
261
|
+
|
|
249
262
|
def test_product_detail_mall_supports_jwt(commerce, captured):
|
|
250
263
|
commerce.product.product_detail('prod1', user_jwt='member-jwt')
|
|
251
264
|
assert captured['url'].endswith('/v1/products/prod1')
|
|
@@ -390,6 +403,54 @@ def test_adjustment_create_defaults(commerce, captured):
|
|
|
390
403
|
assert captured['json'] == {'price': 5000, 'duration': 1, 'tax_free_price': 0, 'name': '설치비'}
|
|
391
404
|
|
|
392
405
|
|
|
406
|
+
def test_adjustment_create_supports_duration_range(commerce, captured):
|
|
407
|
+
"""duration_from ~ duration_to 로 회차 범위를 지정한다 (3~7회차 각각 한 건씩)."""
|
|
408
|
+
commerce.order_subscription_adjustment.create('os1', {
|
|
409
|
+
'name': '기간할인',
|
|
410
|
+
'price': -1000,
|
|
411
|
+
'duration_from': 3,
|
|
412
|
+
'duration_to': 7,
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
assert captured['method'] == 'post'
|
|
416
|
+
assert captured['url'].endswith('/v1/order_subscriptions/os1/adjustments')
|
|
417
|
+
assert captured['json'] == {
|
|
418
|
+
'name': '기간할인', 'price': -1000, 'duration': 1, 'tax_free_price': 0,
|
|
419
|
+
'duration_from': 3, 'duration_to': 7,
|
|
420
|
+
}
|
|
421
|
+
assert captured['headers']['BOOTPAY-ROLE'] == 'supervisor'
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def test_adjustment_create_supports_unlimited_range(commerce, captured):
|
|
425
|
+
"""duration_from + is_unlimited 이면 해당 회차부터 계약 끝까지 (duration_to 는 무시된다)."""
|
|
426
|
+
commerce.order_subscription_adjustment.create('os1', {
|
|
427
|
+
'name': '무기한할인',
|
|
428
|
+
'price': -500,
|
|
429
|
+
'duration_from': 3,
|
|
430
|
+
'is_unlimited': True,
|
|
431
|
+
'duration_to': None,
|
|
432
|
+
}, idempotency_key='adj-key')
|
|
433
|
+
|
|
434
|
+
assert captured['json'] == {
|
|
435
|
+
'name': '무기한할인', 'price': -500, 'duration': 1, 'tax_free_price': 0,
|
|
436
|
+
'duration_from': 3, 'is_unlimited': True,
|
|
437
|
+
}
|
|
438
|
+
assert captured['headers']['Idempotency-Key'] == 'adj-key'
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_adjustment_create_keeps_false_is_unlimited(commerce, captured):
|
|
442
|
+
"""None 만 제거한다 — is_unlimited=False 는 그대로 전송되어야 한다."""
|
|
443
|
+
commerce.order_subscription_adjustment.create('os1', {
|
|
444
|
+
'name': '단일회차',
|
|
445
|
+
'price': 1000,
|
|
446
|
+
'duration_from': 2,
|
|
447
|
+
'duration_to': 4,
|
|
448
|
+
'is_unlimited': False,
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
assert captured['json']['is_unlimited'] is False
|
|
452
|
+
|
|
453
|
+
|
|
393
454
|
# ---------------------------------------------------------------------------
|
|
394
455
|
# 파라미터 확장 — userGroup.limit / order.list / orderSubscription.list / orderSubscriptionRequest.list
|
|
395
456
|
# ---------------------------------------------------------------------------
|
|
@@ -501,6 +562,22 @@ def test_order_subscription_update_uses_supervisor_role(commerce, captured):
|
|
|
501
562
|
assert captured['headers']['BOOTPAY-ROLE'] == 'supervisor'
|
|
502
563
|
|
|
503
564
|
|
|
565
|
+
def test_order_subscription_update_sends_price(commerce, captured):
|
|
566
|
+
"""price 는 회차별 결제 기준금액 — 계약변경 body 로 그대로 전송된다."""
|
|
567
|
+
commerce.order_subscription.update({
|
|
568
|
+
'order_subscription_id': 'os1',
|
|
569
|
+
'price': 15000,
|
|
570
|
+
'order_name': None,
|
|
571
|
+
'idempotency_key': 'price-key',
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
assert captured['method'] == 'put'
|
|
575
|
+
assert captured['url'].endswith('/v1/order_subscriptions/os1')
|
|
576
|
+
assert captured['json'] == {'price': 15000}
|
|
577
|
+
assert captured['headers']['BOOTPAY-ROLE'] == 'supervisor'
|
|
578
|
+
assert captured['headers']['Idempotency-Key'] == 'price-key'
|
|
579
|
+
|
|
580
|
+
|
|
504
581
|
def test_order_subscription_request_list_role_depends_on_project_id(commerce, captured):
|
|
505
582
|
commerce.order_subscription_request.list()
|
|
506
583
|
assert 'page=1' in captured['url'] and 'limit=20' in captured['url']
|
|
@@ -561,3 +638,91 @@ def test_explicit_idempotency_key_is_forwarded_and_kept_out_of_body(commerce, ca
|
|
|
561
638
|
|
|
562
639
|
assert captured['headers']['Idempotency-Key'] == 'fixed-key'
|
|
563
640
|
assert captured['json'] == {'name': '카테고리'}
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
# ---------------------------------------------------------------------------
|
|
644
|
+
# ruby SDK 누락 파라미터 반영 — orders / order_subscriptions / products / users
|
|
645
|
+
# ---------------------------------------------------------------------------
|
|
646
|
+
def test_order_list_supports_subscription_filters(commerce, captured):
|
|
647
|
+
"""구독 계약별·결제유형별 필터 — order_subscription_ids 는 콤마로 join 해서 보낸다."""
|
|
648
|
+
commerce.order.list({
|
|
649
|
+
'order_subscription_ids': ['os1', 'os2'],
|
|
650
|
+
'subscription_billing_type': 1,
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
assert captured['method'] == 'get'
|
|
654
|
+
assert 'order_subscription_ids=os1%2Cos2' in captured['url']
|
|
655
|
+
assert 'subscription_billing_type=1' in captured['url']
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def test_order_list_omits_empty_status_filters(commerce, captured):
|
|
659
|
+
"""값이 비었으면 status=&payment_status= 를 실어 보내지 않는다 (서버는 무시하지만 노이즈)."""
|
|
660
|
+
commerce.order.list({'page': 1, 'status': [], 'payment_status': [], 'order_subscription_ids': []})
|
|
661
|
+
|
|
662
|
+
assert 'status=' not in captured['url']
|
|
663
|
+
assert 'payment_status=' not in captured['url']
|
|
664
|
+
assert 'order_subscription_ids=' not in captured['url']
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def test_order_subscription_list_supports_order_number(commerce, captured):
|
|
668
|
+
"""주문번호로 구독을 역조회한다."""
|
|
669
|
+
commerce.order_subscription.list({'order_number': 'ON-1'})
|
|
670
|
+
|
|
671
|
+
assert captured['method'] == 'get'
|
|
672
|
+
assert '/v1/order_subscriptions?' in captured['url']
|
|
673
|
+
assert 'order_number=ON-1' in captured['url']
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def test_order_subscription_update_sends_memo(commerce, captured):
|
|
677
|
+
"""memo 는 변경이력에 남길 사유로 body 에 그대로 전송된다."""
|
|
678
|
+
commerce.order_subscription.update({
|
|
679
|
+
'order_subscription_id': 'os1',
|
|
680
|
+
'price': 15000,
|
|
681
|
+
'memo': '고객 요청으로 금액 변경',
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
assert captured['method'] == 'put'
|
|
685
|
+
assert captured['url'].endswith('/v1/order_subscriptions/os1')
|
|
686
|
+
assert captured['json'] == {'price': 15000, 'memo': '고객 요청으로 금액 변경'}
|
|
687
|
+
assert captured['headers']['BOOTPAY-ROLE'] == 'supervisor'
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def test_products_mall_supports_ex_uid(commerce, captured):
|
|
691
|
+
"""외부 UID 로 상품 조회 — 서버(v1/products_controller#index)가 params[:ex_uid] 를 읽는다."""
|
|
692
|
+
commerce.product.products({'ex_uid': 'EX-1'})
|
|
693
|
+
|
|
694
|
+
assert captured['method'] == 'get'
|
|
695
|
+
assert 'ex_uid=EX-1' in captured['url']
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def test_lookup_product_supports_jwt(commerce, captured):
|
|
699
|
+
"""lookup_product 도 product_detail 과 같이 회원 컨텍스트 조회를 지원한다."""
|
|
700
|
+
commerce.product.lookup_product('prod1')
|
|
701
|
+
assert captured['url'].endswith('/v1/products/prod1')
|
|
702
|
+
assert 'Bootpay-User-JWT' not in captured['headers']
|
|
703
|
+
|
|
704
|
+
commerce.product.lookup_product('prod1', user_jwt='member-jwt', idempotency_key='lp-key')
|
|
705
|
+
assert captured['url'].endswith('/v1/products/prod1')
|
|
706
|
+
assert captured['headers']['Bootpay-User-JWT'] == 'member-jwt'
|
|
707
|
+
assert captured['headers']['Idempotency-Key'] == 'lp-key'
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def test_user_list_sends_membership_type_not_member_type(commerce, captured):
|
|
711
|
+
"""서버가 읽는 회원등급 키는 membership_type 이다 — member_type 으로 보내면 조용히 무시된다."""
|
|
712
|
+
commerce.user.list({'membership_type': 1})
|
|
713
|
+
|
|
714
|
+
assert captured['method'] == 'get'
|
|
715
|
+
assert 'membership_type=1' in captured['url']
|
|
716
|
+
assert 'member_type=1' not in captured['url']
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def test_user_list_maps_legacy_member_type_alias(commerce, captured):
|
|
720
|
+
"""기존 호출 호환 — member_type 은 membership_type 으로 매핑해서 보낸다."""
|
|
721
|
+
commerce.user.list({'member_type': 2, 'keyword': '테스트'})
|
|
722
|
+
|
|
723
|
+
assert 'membership_type=2' in captured['url']
|
|
724
|
+
assert 'member_type=2' not in captured['url']
|
|
725
|
+
|
|
726
|
+
# 둘 다 주면 정식 키가 우선한다.
|
|
727
|
+
commerce.user.list({'membership_type': 1, 'member_type': 2})
|
|
728
|
+
assert 'membership_type=1' in captured['url']
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/commerce_resource.py
RENAMED
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/category.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/mall_setting.py
RENAMED
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/order_cancel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend/commerce/modules/user_group.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/bootpay_backend.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bootpay_backend-2.5.0 → bootpay_backend-2.7.0}/tests/commerce/test_order_subscription_charge.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|