bootpay-backend 2.9.0__tar.gz → 2.10.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.
Files changed (78) hide show
  1. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/PKG-INFO +86 -1
  2. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/README.md +85 -0
  3. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/__init__.py +28 -0
  4. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/__init__.py +4 -0
  5. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_webhook.py +1 -0
  6. bootpay_backend-2.10.0/bootpay_backend/commerce/modules/deposit.py +107 -0
  7. bootpay_backend-2.10.0/bootpay_backend/commerce/modules/payout.py +52 -0
  8. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/product.py +8 -7
  9. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/types/__init__.py +136 -1
  10. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/rest_client.py +1 -1
  11. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend.egg-info/PKG-INFO +86 -1
  12. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend.egg-info/SOURCES.txt +4 -0
  13. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/setup.py +1 -1
  14. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_alimtalk.py +22 -0
  15. bootpay_backend-2.10.0/tests/commerce/test_deposit.py +150 -0
  16. bootpay_backend-2.10.0/tests/commerce/test_payout.py +179 -0
  17. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_wire_format.py +16 -6
  18. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/LICENSE +0 -0
  19. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/__init__.py +0 -0
  20. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/bankcode.py +0 -0
  21. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/commerce_resource.py +0 -0
  22. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_message.py +0 -0
  23. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_official.py +0 -0
  24. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_optout.py +0 -0
  25. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_send.py +0 -0
  26. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_sender.py +0 -0
  27. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/alimtalk_template.py +0 -0
  28. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/cart.py +0 -0
  29. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/category.py +0 -0
  30. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/coupon.py +0 -0
  31. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/faq.py +0 -0
  32. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/inquiry.py +0 -0
  33. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/invoice.py +0 -0
  34. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/mall_setting.py +0 -0
  35. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/notice.py +0 -0
  36. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/order.py +0 -0
  37. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/order_cancel.py +0 -0
  38. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/order_subscription.py +0 -0
  39. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/order_subscription_adjustment.py +0 -0
  40. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/order_subscription_bill.py +0 -0
  41. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/order_subscription_request.py +0 -0
  42. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/point.py +0 -0
  43. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/product_qna.py +0 -0
  44. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/product_review.py +0 -0
  45. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/store.py +0 -0
  46. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/user.py +0 -0
  47. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/user_group.py +0 -0
  48. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend/commerce/modules/webhook.py +0 -0
  49. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend.egg-info/dependency_links.txt +0 -0
  50. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend.egg-info/not-zip-safe +0 -0
  51. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend.egg-info/requires.txt +0 -0
  52. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/bootpay_backend.egg-info/top_level.txt +0 -0
  53. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/setup.cfg +0 -0
  54. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/__init__.py +0 -0
  55. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/__init__.py +0 -0
  56. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_auth_contract.py +0 -0
  57. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_board.py +0 -0
  58. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_mall_setting.py +0 -0
  59. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_order.py +0 -0
  60. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_order_subscription_charge.py +0 -0
  61. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_product.py +0 -0
  62. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_product_mall.py +0 -0
  63. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_store.py +0 -0
  64. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_token.py +0 -0
  65. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_user.py +0 -0
  66. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_user_mall.py +0 -0
  67. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/commerce/test_webhook.py +0 -0
  68. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/conftest.py +0 -0
  69. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/__init__.py +0 -0
  70. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_auth.py +0 -0
  71. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_billing.py +0 -0
  72. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_cash_receipt.py +0 -0
  73. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_cash_receipt_wire_format.py +0 -0
  74. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_legacy_compatibility.py +0 -0
  75. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_payment.py +0 -0
  76. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_sequential_billing_key.py +0 -0
  77. {bootpay_backend-2.9.0 → bootpay_backend-2.10.0}/tests/pg/test_token.py +0 -0
  78. {bootpay_backend-2.9.0 → bootpay_backend-2.10.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.9.0
3
+ Version: 2.10.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>
@@ -69,6 +69,8 @@ Python 언어로 작성된 어플리케이션, 프레임워크 등에서 사용
69
69
  - [9-2. 현금영수증 발행 취소](#9-2-현금영수증-발행-취소)
70
70
  - [9-3. 별건 현금영수증 발행](#9-3-별건-현금영수증-발행)
71
71
  - [9-4. 별건 현금영수증 발행 취소](#9-4-별건-현금영수증-발행-취소)
72
+ - [10. 뱅크알람 입금대기](#10-뱅크알람-입금대기-무통장-입금-자동-확인)
73
+ - [11. 입점사 지급 요청](#11-입점사-지급-요청)
72
74
  - [Example 프로젝트](#example-프로젝트)
73
75
  - [Documentation](#documentation)
74
76
  - [기술문의](#기술문의)
@@ -440,6 +442,89 @@ if 'error_code' not in token:
440
442
  print(response)
441
443
  ```
442
444
 
445
+ ## 10. 뱅크알람 입금대기 (무통장 입금 자동 확인)
446
+
447
+ 가맹점 서버가 아직 입금되지 않은 주문을 등록하면, 판매자 폰의 입금 알림 중 **금액 · 입금자명**이 맞는 것을
448
+ 뱅크알람이 그 주문에 연결해 입금을 확인하고 입금 웹훅으로 알립니다.
449
+ 전부 supervisor 전용이며, 인스턴스 role 과 무관하게 `BOOTPAY-ROLE: supervisor` 로 나갑니다.
450
+
451
+ ```python
452
+ import os
453
+
454
+ from bootpay_backend.commerce import BootpayCommerce
455
+
456
+ commerce = BootpayCommerce(
457
+ client_key=os.getenv('BOOTPAY_COMMERCE_CLIENT_KEY_PROD'),
458
+ secret_key=os.getenv('BOOTPAY_COMMERCE_SECRET_KEY_PROD')
459
+ )
460
+ commerce.get_access_token()
461
+
462
+ # 판매자 입금 계좌 목록 — 첫 계좌가 대표 계좌(primary: true)
463
+ accounts = commerce.deposit.accounts()['accounts']
464
+
465
+ # 웹훅 서명 비밀값 발급 — ⚠️ 원문은 이 응답에서만 받습니다. 다시 부르면 바뀝니다
466
+ secret = commerce.deposit.webhook_secret()['secret']
467
+
468
+ # 입금대기 등록 — 같은 merchant_order_id 로 같은 금액·입금자명·계좌를 다시 보내면 기존 주문을 돌려줍니다
469
+ # (하나라도 다르면 18110 BANK_DEPOSIT_MERCHANT_ID_CONFLICT)
470
+ commerce.deposit.create({
471
+ 'merchant_order_id': 'ORDER_1234',
472
+ 'price': 15000,
473
+ 'depositor': '홍길동', # 은행 입금 알림과 맞춰 볼 이름
474
+ 'order_name': '티셔츠 외 1건', # 미지정시 '무통장 입금'
475
+ 'memo': '관리자 메모',
476
+ 'buyer_name': '홍길동', # 미지정시 입금자명
477
+ 'buyer_email': 'buyer@example.com', # 주면 입금 안내 · 확인 메일 (무료)
478
+ 'account_id': accounts[0]['id'], # 미지정시 대표 계좌
479
+ 'webhook_url': 'https://example.com/hooks/deposit', # 비밀값을 먼저 발급받아야 합니다(18131)
480
+ # 구매자 알림톡 (유료) — buyer_phone 필수, link 는 https
481
+ # 'notify': {'deposit_guide': True, 'deposit_paid': True, 'link': 'https://example.com/orders/ORDER_1234'},
482
+ })
483
+
484
+ # 조회 · 취소 — 가맹점 주문번호 또는 부트페이 주문번호
485
+ commerce.deposit.detail('ORDER_1234') # status: awaiting · paid · expired · cancelled
486
+ commerce.deposit.cancel('ORDER_1234') # 이미 닫힌 주문은 그대로 돌려줍니다
487
+ ```
488
+
489
+ `webhook_secret(idempotency_key=None)` · `cancel(id, idempotency_key=None)` 은 멱등 키 선택 인자를 받고,
490
+ `create(params)` 는 `params['idempotency_key']` 로 받습니다. 미지정시 호출할 때마다 새 UUID 를 사용합니다.
491
+
492
+ > 입금 웹훅은 `X-Bootpay-Signature: sha256=HMAC_SHA256(secret, "{X-Bootpay-Timestamp}.{raw_body}")` 로 서명됩니다.
493
+ > 이벤트 코드는 `BANK_DEPOSIT_WEBHOOK_EVENT_*` 상수(400~407)를 참고하세요.
494
+
495
+ ## 11. 입점사 지급 요청
496
+
497
+ 가맹점 지정 금액(`manual`) 정산 방식에서 입점사와 지급액을 정해 지급 원장에 등록합니다.
498
+ 실제 송금은 별도 배치가 처리하며, 세 API 모두 인스턴스 role과 무관하게 `BOOTPAY-ROLE: supervisor`로 나갑니다.
499
+
500
+ ```python
501
+ from bootpay_backend.commerce import BootpayCommerce
502
+
503
+ commerce = BootpayCommerce(client_key='CLIENT_KEY', secret_key='SECRET_KEY')
504
+
505
+ # 등록 — 같은 merchant_payout_id 재호출은 처음 생성한 항목을 돌려줍니다
506
+ commerce.payout.create({
507
+ 'sub_seller_id': 'SUB_SELLER_ID',
508
+ 'amount': 150000,
509
+ 'merchant_payout_id': 'PAYOUT_20261112_001',
510
+ 'order_number': 'ORDER_1234', # 선택
511
+ 'payout_date': '2026-11-20', # 선택, YYYY-MM-DD
512
+ })
513
+
514
+ # merchant_payout_id 또는 지급 항목 id로 조회
515
+ commerce.payout.detail('PAYOUT_20261112_001')
516
+
517
+ # 목록은 전체 개수 래퍼 없는 지급 항목 배열입니다
518
+ commerce.payout.list({
519
+ 'status': 'ready', 'sub_seller_id': 'SUB_SELLER_ID',
520
+ 'from': '2026-11-01', 'to': '2026-11-30', 'page': 1, 'limit': 20,
521
+ })
522
+ ```
523
+
524
+ `status`는 `ready` · `held` · `sending` · `requested` · `processing` · `succeeded` · `failed` ·
525
+ `cancelled` · `unknown` · `void` 중 하나입니다. 세 메서드 모두 명시적 `idempotency_key`를 받을 수 있고,
526
+ 미지정하면 호출마다 새 UUID를 사용합니다.
527
+
443
528
  ## Example 프로젝트
444
529
 
445
530
  [적용한 샘플 프로젝트](https://github.com/bootpay/backend-python-example)을 참조해주세요
@@ -29,6 +29,8 @@ Python 언어로 작성된 어플리케이션, 프레임워크 등에서 사용
29
29
  - [9-2. 현금영수증 발행 취소](#9-2-현금영수증-발행-취소)
30
30
  - [9-3. 별건 현금영수증 발행](#9-3-별건-현금영수증-발행)
31
31
  - [9-4. 별건 현금영수증 발행 취소](#9-4-별건-현금영수증-발행-취소)
32
+ - [10. 뱅크알람 입금대기](#10-뱅크알람-입금대기-무통장-입금-자동-확인)
33
+ - [11. 입점사 지급 요청](#11-입점사-지급-요청)
32
34
  - [Example 프로젝트](#example-프로젝트)
33
35
  - [Documentation](#documentation)
34
36
  - [기술문의](#기술문의)
@@ -400,6 +402,89 @@ if 'error_code' not in token:
400
402
  print(response)
401
403
  ```
402
404
 
405
+ ## 10. 뱅크알람 입금대기 (무통장 입금 자동 확인)
406
+
407
+ 가맹점 서버가 아직 입금되지 않은 주문을 등록하면, 판매자 폰의 입금 알림 중 **금액 · 입금자명**이 맞는 것을
408
+ 뱅크알람이 그 주문에 연결해 입금을 확인하고 입금 웹훅으로 알립니다.
409
+ 전부 supervisor 전용이며, 인스턴스 role 과 무관하게 `BOOTPAY-ROLE: supervisor` 로 나갑니다.
410
+
411
+ ```python
412
+ import os
413
+
414
+ from bootpay_backend.commerce import BootpayCommerce
415
+
416
+ commerce = BootpayCommerce(
417
+ client_key=os.getenv('BOOTPAY_COMMERCE_CLIENT_KEY_PROD'),
418
+ secret_key=os.getenv('BOOTPAY_COMMERCE_SECRET_KEY_PROD')
419
+ )
420
+ commerce.get_access_token()
421
+
422
+ # 판매자 입금 계좌 목록 — 첫 계좌가 대표 계좌(primary: true)
423
+ accounts = commerce.deposit.accounts()['accounts']
424
+
425
+ # 웹훅 서명 비밀값 발급 — ⚠️ 원문은 이 응답에서만 받습니다. 다시 부르면 바뀝니다
426
+ secret = commerce.deposit.webhook_secret()['secret']
427
+
428
+ # 입금대기 등록 — 같은 merchant_order_id 로 같은 금액·입금자명·계좌를 다시 보내면 기존 주문을 돌려줍니다
429
+ # (하나라도 다르면 18110 BANK_DEPOSIT_MERCHANT_ID_CONFLICT)
430
+ commerce.deposit.create({
431
+ 'merchant_order_id': 'ORDER_1234',
432
+ 'price': 15000,
433
+ 'depositor': '홍길동', # 은행 입금 알림과 맞춰 볼 이름
434
+ 'order_name': '티셔츠 외 1건', # 미지정시 '무통장 입금'
435
+ 'memo': '관리자 메모',
436
+ 'buyer_name': '홍길동', # 미지정시 입금자명
437
+ 'buyer_email': 'buyer@example.com', # 주면 입금 안내 · 확인 메일 (무료)
438
+ 'account_id': accounts[0]['id'], # 미지정시 대표 계좌
439
+ 'webhook_url': 'https://example.com/hooks/deposit', # 비밀값을 먼저 발급받아야 합니다(18131)
440
+ # 구매자 알림톡 (유료) — buyer_phone 필수, link 는 https
441
+ # 'notify': {'deposit_guide': True, 'deposit_paid': True, 'link': 'https://example.com/orders/ORDER_1234'},
442
+ })
443
+
444
+ # 조회 · 취소 — 가맹점 주문번호 또는 부트페이 주문번호
445
+ commerce.deposit.detail('ORDER_1234') # status: awaiting · paid · expired · cancelled
446
+ commerce.deposit.cancel('ORDER_1234') # 이미 닫힌 주문은 그대로 돌려줍니다
447
+ ```
448
+
449
+ `webhook_secret(idempotency_key=None)` · `cancel(id, idempotency_key=None)` 은 멱등 키 선택 인자를 받고,
450
+ `create(params)` 는 `params['idempotency_key']` 로 받습니다. 미지정시 호출할 때마다 새 UUID 를 사용합니다.
451
+
452
+ > 입금 웹훅은 `X-Bootpay-Signature: sha256=HMAC_SHA256(secret, "{X-Bootpay-Timestamp}.{raw_body}")` 로 서명됩니다.
453
+ > 이벤트 코드는 `BANK_DEPOSIT_WEBHOOK_EVENT_*` 상수(400~407)를 참고하세요.
454
+
455
+ ## 11. 입점사 지급 요청
456
+
457
+ 가맹점 지정 금액(`manual`) 정산 방식에서 입점사와 지급액을 정해 지급 원장에 등록합니다.
458
+ 실제 송금은 별도 배치가 처리하며, 세 API 모두 인스턴스 role과 무관하게 `BOOTPAY-ROLE: supervisor`로 나갑니다.
459
+
460
+ ```python
461
+ from bootpay_backend.commerce import BootpayCommerce
462
+
463
+ commerce = BootpayCommerce(client_key='CLIENT_KEY', secret_key='SECRET_KEY')
464
+
465
+ # 등록 — 같은 merchant_payout_id 재호출은 처음 생성한 항목을 돌려줍니다
466
+ commerce.payout.create({
467
+ 'sub_seller_id': 'SUB_SELLER_ID',
468
+ 'amount': 150000,
469
+ 'merchant_payout_id': 'PAYOUT_20261112_001',
470
+ 'order_number': 'ORDER_1234', # 선택
471
+ 'payout_date': '2026-11-20', # 선택, YYYY-MM-DD
472
+ })
473
+
474
+ # merchant_payout_id 또는 지급 항목 id로 조회
475
+ commerce.payout.detail('PAYOUT_20261112_001')
476
+
477
+ # 목록은 전체 개수 래퍼 없는 지급 항목 배열입니다
478
+ commerce.payout.list({
479
+ 'status': 'ready', 'sub_seller_id': 'SUB_SELLER_ID',
480
+ 'from': '2026-11-01', 'to': '2026-11-30', 'page': 1, 'limit': 20,
481
+ })
482
+ ```
483
+
484
+ `status`는 `ready` · `held` · `sending` · `requested` · `processing` · `succeeded` · `failed` ·
485
+ `cancelled` · `unknown` · `void` 중 하나입니다. 세 메서드 모두 명시적 `idempotency_key`를 받을 수 있고,
486
+ 미지정하면 호출마다 새 UUID를 사용합니다.
487
+
403
488
  ## Example 프로젝트
404
489
 
405
490
  [적용한 샘플 프로젝트](https://github.com/bootpay/backend-python-example)을 참조해주세요
@@ -18,6 +18,8 @@ from .modules import (
18
18
  OrderSubscriptionRequestModule,
19
19
  MallSettingModule,
20
20
  WebhookModule,
21
+ DepositModule,
22
+ PayoutModule,
21
23
  FaqModule,
22
24
  NoticeModule,
23
25
  InquiryModule,
@@ -92,6 +94,8 @@ class BootpayCommerce(BootpayCommerceResource):
92
94
  self.order_subscription_request = OrderSubscriptionRequestModule(self)
93
95
  self.mall_setting = MallSettingModule(self)
94
96
  self.webhook = WebhookModule(self)
97
+ self.deposit = DepositModule(self)
98
+ self.payout = PayoutModule(self)
95
99
  # 게시판 계열 (FAQ·공지사항·1:1문의·상품문의·상품평)
96
100
  self.faq = FaqModule(self)
97
101
  self.notice = NoticeModule(self)
@@ -238,6 +242,8 @@ __all__ = [
238
242
  'OrderSubscriptionRequestModule',
239
243
  'MallSettingModule',
240
244
  'WebhookModule',
245
+ 'DepositModule',
246
+ 'PayoutModule',
241
247
  'FaqModule',
242
248
  'NoticeModule',
243
249
  'InquiryModule',
@@ -347,6 +353,28 @@ __all__ = [
347
353
  'MallSettingUpdateParams',
348
354
  'CommerceMallSetting',
349
355
  'SendTestWebhookParams',
356
+ # Deposit Types
357
+ 'DepositAccount',
358
+ 'DepositAccountsResponse',
359
+ 'DepositWebhookSecretResponse',
360
+ 'DepositNotifyParams',
361
+ 'DepositCreateParams',
362
+ 'DepositNotice',
363
+ 'DepositNotify',
364
+ 'CommerceDeposit',
365
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_RECEIVED',
366
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_MATCHED',
367
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_AMBIGUOUS',
368
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_UNMATCHED',
369
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_CONFIRMED_MANUALLY',
370
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_EXPIRED',
371
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_CANCELLED',
372
+ 'BANK_DEPOSIT_WEBHOOK_EVENT_REVERTED',
373
+ # Payout Types
374
+ 'PayoutStatus',
375
+ 'PayoutCreateParams',
376
+ 'PayoutListParams',
377
+ 'CommercePayout',
350
378
  # Alimtalk Types
351
379
  'ALIMTALK_WEBHOOK_EVENT_SEND_REQUESTED',
352
380
  'ALIMTALK_WEBHOOK_EVENT_SEND_SUCCESS',
@@ -16,6 +16,8 @@ from .cart import CartModule
16
16
  from .order_subscription_request import OrderSubscriptionRequestModule
17
17
  from .mall_setting import MallSettingModule
18
18
  from .webhook import WebhookModule
19
+ from .deposit import DepositModule
20
+ from .payout import PayoutModule
19
21
  from .faq import FaqModule
20
22
  from .notice import NoticeModule
21
23
  from .inquiry import InquiryModule
@@ -47,6 +49,8 @@ __all__ = [
47
49
  'OrderSubscriptionRequestModule',
48
50
  'MallSettingModule',
49
51
  'WebhookModule',
52
+ 'DepositModule',
53
+ 'PayoutModule',
50
54
  'FaqModule',
51
55
  'NoticeModule',
52
56
  'InquiryModule',
@@ -45,6 +45,7 @@ class AlimtalkWebhookModule:
45
45
  300 발송 접수(기본 미구독) / 301 전달 성공 / 302 전달 실패 / 303 예약 취소 /
46
46
  304 문자(LMS) 대체발송 전환 / 310 검수 승인 / 311 검수 반려 / 320 수신거부 등록(기본 미구독)
47
47
  events 를 비우면 기본 구독셋(301·302·303·304·310·311)이 적용된다.
48
+ retry_count: 건별 재시도 횟수 (1~25, 기본 10). 범위 밖이면 11304. 생략하면 기존 값을 유지한다 (26-09-30).
48
49
  :param params: 웹훅 설정
49
50
  :return: 저장된 웹훅 설정
50
51
  """
@@ -0,0 +1,107 @@
1
+ import uuid
2
+ from typing import TYPE_CHECKING, Optional, Dict, Any
3
+ from urllib.parse import quote
4
+
5
+ if TYPE_CHECKING:
6
+ from ..commerce_resource import BootpayCommerceResource
7
+
8
+ from ..types import (
9
+ CommerceDeposit,
10
+ DepositAccountsResponse,
11
+ DepositCreateParams,
12
+ DepositWebhookSecretResponse
13
+ )
14
+
15
+
16
+ class DepositModule:
17
+ """
18
+ 뱅크알람 입금대기 모듈 (/v1/sdk/deposits)
19
+
20
+ 판매자 폰의 입금 알림 중 금액·입금자명이 맞는 것을 주문에 연결하고 입금 웹훅으로 알린다.
21
+ 인스턴스 role 과 무관하게 모든 요청은 supervisor 전용이다.
22
+ """
23
+
24
+ def __init__(self, bootpay: 'BootpayCommerceResource'):
25
+ self._bootpay = bootpay
26
+
27
+ def accounts(self) -> DepositAccountsResponse:
28
+ """
29
+ 판매자 입금 계좌 목록
30
+ GET /v1/sdk/deposits/accounts
31
+ 첫 계좌가 대표 계좌(primary: True)다.
32
+ :return: {'accounts': List[DepositAccount]}
33
+ """
34
+ return self._bootpay.get('sdk/deposits/accounts', headers=self._supervisor_headers())
35
+
36
+ def webhook_secret(self, idempotency_key: Optional[str] = None) -> DepositWebhookSecretResponse:
37
+ """
38
+ 웹훅 서명 비밀값 발급
39
+ POST /v1/sdk/deposits/webhook_secret
40
+ ⚠️ 원문은 이 응답에서만 준다. 다시 부르면 바뀐다 (기존 웹훅 작업은 이전 비밀값으로 서명된다).
41
+ :param idempotency_key: 미지정시 자동 생성
42
+ :return: {'secret': str}
43
+ """
44
+ return self._bootpay.post(
45
+ 'sdk/deposits/webhook_secret', {},
46
+ headers=self._supervisor_headers(idempotency_key)
47
+ )
48
+
49
+ def create(self, params: DepositCreateParams) -> CommerceDeposit:
50
+ """
51
+ 입금대기 등록
52
+ POST /v1/sdk/deposits
53
+ 같은 merchant_order_id 재호출은 기존 주문을 반환한다 (금액·입금자명·계좌가 다르면 18110).
54
+ webhook_url 을 주려면 webhook_secret() 으로 비밀값을 먼저 발급받아야 한다 (18131).
55
+ notify 는 유료 알림톡이며, 준비가 안 됐으면 주문을 만들지 않는다 (18133).
56
+ :param params: merchant_order_id · price · depositor 필수, idempotency_key 선택
57
+ :return: CommerceDeposit
58
+ """
59
+ payload = self._compact({k: v for k, v in params.items() if k != 'idempotency_key'})
60
+ if payload.get('notify') is not None:
61
+ payload['notify'] = self._compact(payload['notify'])
62
+ return self._bootpay.post(
63
+ 'sdk/deposits', payload,
64
+ headers=self._supervisor_headers(params.get('idempotency_key'))
65
+ )
66
+
67
+ def detail(self, deposit_id: str) -> CommerceDeposit:
68
+ """
69
+ 입금대기 조회
70
+ GET /v1/sdk/deposits/{id}
71
+ 기한이 지났으면 조회 시점에 만료(expired)로 닫힌다.
72
+ :param deposit_id: 가맹점 주문번호 또는 부트페이 주문번호
73
+ :return: CommerceDeposit
74
+ """
75
+ if not deposit_id:
76
+ raise ValueError('deposit_id is required')
77
+ return self._bootpay.get(
78
+ f'sdk/deposits/{quote(deposit_id, safe="")}',
79
+ headers=self._supervisor_headers()
80
+ )
81
+
82
+ def cancel(self, deposit_id: str, idempotency_key: Optional[str] = None) -> CommerceDeposit:
83
+ """
84
+ 입금대기 취소
85
+ DELETE /v1/sdk/deposits/{id}
86
+ 기한이 지났으면 만료로 닫고, 이미 닫힌 주문은 그대로 반환한다. 입금 확인된 주문은 18108.
87
+ :param deposit_id: 가맹점 주문번호 또는 부트페이 주문번호
88
+ :param idempotency_key: 미지정시 자동 생성
89
+ :return: CommerceDeposit
90
+ """
91
+ if not deposit_id:
92
+ raise ValueError('deposit_id is required')
93
+ return self._bootpay.delete(
94
+ f'sdk/deposits/{quote(deposit_id, safe="")}',
95
+ headers=self._supervisor_headers(idempotency_key)
96
+ )
97
+
98
+ def _compact(self, payload: Dict[str, Any]) -> Dict[str, Any]:
99
+ """None 값을 제거한다 (False 는 남긴다)."""
100
+ return {k: v for k, v in payload.items() if v is not None}
101
+
102
+ def _supervisor_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
103
+ """요청별 supervisor 헤더와 멱등키 (미지정시 매 호출마다 UUID 생성)."""
104
+ return {
105
+ 'Idempotency-Key': idempotency_key or str(uuid.uuid4()),
106
+ 'BOOTPAY-ROLE': 'supervisor'
107
+ }
@@ -0,0 +1,52 @@
1
+ import uuid
2
+ from typing import TYPE_CHECKING, Optional, Dict, Any, List
3
+ from urllib.parse import quote
4
+
5
+ if TYPE_CHECKING:
6
+ from ..commerce_resource import BootpayCommerceResource
7
+
8
+ from ..types import CommercePayout, PayoutCreateParams, PayoutListParams
9
+
10
+
11
+ class PayoutModule:
12
+ """입점사 지급 요청 모듈 (/v1/sdk/payouts)."""
13
+
14
+ def __init__(self, bootpay: 'BootpayCommerceResource'):
15
+ self._bootpay = bootpay
16
+
17
+ def create(self, params: PayoutCreateParams) -> CommercePayout:
18
+ """입점사와 지급액을 지정해 지급 원장에 등록한다."""
19
+ payload = self._compact({k: v for k, v in params.items() if k != 'idempotency_key'})
20
+ return self._bootpay.post(
21
+ 'sdk/payouts', payload,
22
+ headers=self._supervisor_headers(params.get('idempotency_key'))
23
+ )
24
+
25
+ def detail(self, payout_id: str, idempotency_key: Optional[str] = None) -> CommercePayout:
26
+ """merchant_payout_id 또는 지급 항목 id로 한 건을 조회한다."""
27
+ if not payout_id:
28
+ raise ValueError('payout_id is required')
29
+ return self._bootpay.get(
30
+ f'sdk/payouts/{quote(payout_id, safe="")}',
31
+ headers=self._supervisor_headers(idempotency_key)
32
+ )
33
+
34
+ def list(self, params: Optional[PayoutListParams] = None) -> List[CommercePayout]:
35
+ """현재 프로젝트의 지급 항목 배열을 최근 생성 순으로 조회한다."""
36
+ request_params = params or {}
37
+ query = self._compact({k: v for k, v in request_params.items() if k != 'idempotency_key'})
38
+ return self._bootpay.get(
39
+ 'sdk/payouts', params=query,
40
+ headers=self._supervisor_headers(request_params.get('idempotency_key'))
41
+ )
42
+
43
+ def _compact(self, payload: Dict[str, Any]) -> Dict[str, Any]:
44
+ """None 값을 제거한다 (0과 빈 문자열은 호출자가 보낸 값으로 유지한다)."""
45
+ return {k: v for k, v in payload.items() if v is not None}
46
+
47
+ def _supervisor_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
48
+ """요청별 supervisor 헤더와 멱등키 (미지정시 매 호출마다 UUID 생성)."""
49
+ return {
50
+ 'Idempotency-Key': idempotency_key or str(uuid.uuid4()),
51
+ 'BOOTPAY-ROLE': 'supervisor'
52
+ }
@@ -116,7 +116,7 @@ class ProductModule:
116
116
  :return: CommerceProduct
117
117
  """
118
118
  payload = self._compact(product)
119
- headers = self._manager_headers(idempotency_key)
119
+ headers = self._write_headers(idempotency_key)
120
120
 
121
121
  if not image_paths:
122
122
  return self._bootpay.post('products', payload, headers=headers)
@@ -173,7 +173,7 @@ class ProductModule:
173
173
  return self._bootpay.put(
174
174
  f'products/{product["product_id"]}',
175
175
  self._compact(product),
176
- headers=self._manager_headers(idempotency_key)
176
+ headers=self._write_headers(idempotency_key)
177
177
  )
178
178
 
179
179
  def status(self, params: ProductStatusParams):
@@ -192,7 +192,7 @@ class ProductModule:
192
192
  return self._bootpay.put(
193
193
  f'products/{product_id}/status',
194
194
  self._compact(params),
195
- headers=self._manager_headers(idempotency_key)
195
+ headers=self._write_headers(idempotency_key)
196
196
  )
197
197
 
198
198
  def delete(self, product_id: str, idempotency_key: Optional[str] = None):
@@ -205,21 +205,22 @@ class ProductModule:
205
205
  """
206
206
  return self._bootpay.delete(
207
207
  f'products/{product_id}',
208
- headers=self._manager_headers(idempotency_key)
208
+ headers=self._write_headers(idempotency_key)
209
209
  )
210
210
 
211
211
  def _compact(self, payload: Dict[str, Any]) -> Dict[str, Any]:
212
212
  """None 값을 제거한다."""
213
213
  return {k: v for k, v in dict(payload or {}).items() if v is not None}
214
214
 
215
- def _manager_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
215
+ def _write_headers(self, idempotency_key: Optional[str] = None) -> Dict[str, str]:
216
216
  """
217
217
  상품 쓰기(등록/수정/삭제/상태변경) 요청 헤더
218
- 서버가 manager scope 를 요구한다.
218
+ 서버 스코프는 user:product_create/update/delete/status 뿐이라 BOOTPAY-ROLE 은 user 로 보낸다.
219
+ manager 로 보내면 manager:product_* 를 찾다 401 API_ROLE_NOT_SUPPORT(-50) 로 막힌다.
219
220
  """
220
221
  return {
221
222
  'Idempotency-Key': idempotency_key or str(uuid.uuid4()),
222
- 'BOOTPAY-ROLE': 'manager'
223
+ 'BOOTPAY-ROLE': 'user'
223
224
  }
224
225
 
225
226
  def _mall_headers(self, user_jwt: Optional[str] = None, idempotency_key: Optional[str] = None) -> Dict[str, str]:
@@ -1,5 +1,5 @@
1
1
  # Commerce API Types
2
- from typing import TypedDict, Optional, List, Any, Dict
2
+ from typing import TypedDict, Optional, List, Any, Dict, Literal
3
3
 
4
4
  # Common Types
5
5
  class ListParams(TypedDict, total=False):
@@ -1688,6 +1688,8 @@ class AlimtalkWebhookUpdateParams(TypedDict, total=False):
1688
1688
  # 목록에 없는 값은 저장 시 조용히 버려진다. 비우면 기본 구독셋(301·302·303·304·310·311).
1689
1689
  events: List[int]
1690
1690
  enabled: bool
1691
+ # 건별 재시도 횟수(1~25, 기본 10). 범위 밖이면 11304. 생략하면 기존 값을 유지한다(26-09-30)
1692
+ retry_count: int
1691
1693
 
1692
1694
 
1693
1695
  class AlimtalkWebhookDeliveryListParams(TypedDict, total=False):
@@ -1695,3 +1697,136 @@ class AlimtalkWebhookDeliveryListParams(TypedDict, total=False):
1695
1697
  page: int
1696
1698
  # 서버 기본 20, 최대 100
1697
1699
  limit: int
1700
+
1701
+
1702
+ # Bank Deposit Types (/v1/sdk/deposits)
1703
+ BANK_DEPOSIT_WEBHOOK_EVENT_RECEIVED = 400
1704
+ BANK_DEPOSIT_WEBHOOK_EVENT_MATCHED = 401
1705
+ BANK_DEPOSIT_WEBHOOK_EVENT_AMBIGUOUS = 402
1706
+ BANK_DEPOSIT_WEBHOOK_EVENT_UNMATCHED = 403
1707
+ BANK_DEPOSIT_WEBHOOK_EVENT_CONFIRMED_MANUALLY = 404
1708
+ BANK_DEPOSIT_WEBHOOK_EVENT_EXPIRED = 405
1709
+ BANK_DEPOSIT_WEBHOOK_EVENT_CANCELLED = 406
1710
+ BANK_DEPOSIT_WEBHOOK_EVENT_REVERTED = 407
1711
+
1712
+
1713
+ class DepositAccount(TypedDict, total=False):
1714
+ id: str
1715
+ bank_name: str
1716
+ account_no: str
1717
+ holder: str
1718
+ # 대표 계좌 여부 — 계좌 목록 응답에만 실린다
1719
+ primary: bool
1720
+
1721
+
1722
+ class DepositAccountsResponse(TypedDict, total=False):
1723
+ accounts: List[DepositAccount]
1724
+
1725
+
1726
+ class DepositWebhookSecretResponse(TypedDict, total=False):
1727
+ # 원문은 발급 응답에서만 받는다. 다시 발급하면 바뀐다.
1728
+ secret: str
1729
+
1730
+
1731
+ class DepositNotify(TypedDict, total=False):
1732
+ deposit_guide: bool
1733
+ deposit_paid: bool
1734
+
1735
+
1736
+ class DepositNotifyParams(DepositNotify, total=False):
1737
+ # 하나라도 True 면 buyer_phone 필수(18134), link 는 https · 500자 이하(18135)
1738
+ link: str
1739
+
1740
+
1741
+ class _DepositCreateRequired(TypedDict):
1742
+ merchant_order_id: str
1743
+ price: float
1744
+ depositor: str
1745
+
1746
+
1747
+ class DepositCreateParams(_DepositCreateRequired, total=False):
1748
+ order_name: str
1749
+ memo: str
1750
+ # 미지정시 입금자명
1751
+ buyer_name: str
1752
+ buyer_phone: str
1753
+ # 입금 안내·확인 메일 (무료). 잘못된 형식은 18136
1754
+ buyer_email: str
1755
+ # accounts() 의 id — 미지정시 대표 계좌
1756
+ account_id: str
1757
+ # 먼저 webhook_secret() 으로 서명 비밀값을 발급받아야 한다 (18131)
1758
+ webhook_url: str
1759
+ notify: DepositNotifyParams
1760
+ idempotency_key: str
1761
+
1762
+
1763
+ class DepositNotice(TypedDict, total=False):
1764
+ # 주문에 연결된 입금 알림 — 웹훅 본문에만 실린다
1765
+ key: str
1766
+ bank_name: str
1767
+ account_tail: str
1768
+ depositor: str
1769
+ amount: float
1770
+ received_at: Optional[str]
1771
+
1772
+
1773
+ class CommerceDeposit(TypedDict, total=False):
1774
+ merchant_order_id: str
1775
+ order_number: str
1776
+ order_name: str
1777
+ amount: float
1778
+ status: Literal['awaiting', 'paid', 'expired', 'cancelled']
1779
+ buyer_name: str
1780
+ depositor: str
1781
+ account: Optional[DepositAccount]
1782
+ deadline: Optional[str]
1783
+ deadline_label: Optional[str]
1784
+ paid_at: Optional[str]
1785
+ notify: Optional[DepositNotify]
1786
+ deposit: Optional[DepositNotice]
1787
+
1788
+
1789
+ # Payout Types (/v1/sdk/payouts)
1790
+ PayoutStatus = Literal[
1791
+ 'ready', 'held', 'sending', 'requested', 'processing',
1792
+ 'succeeded', 'failed', 'cancelled', 'unknown', 'void'
1793
+ ]
1794
+
1795
+
1796
+ class _PayoutCreateRequired(TypedDict):
1797
+ sub_seller_id: str
1798
+ amount: int
1799
+ merchant_payout_id: str
1800
+
1801
+
1802
+ class PayoutCreateParams(_PayoutCreateRequired, total=False):
1803
+ order_number: str
1804
+ # 지급 예정일 YYYY-MM-DD (미지정시 서버 기준 오늘)
1805
+ payout_date: str
1806
+ idempotency_key: str
1807
+
1808
+
1809
+ PayoutListParams = TypedDict('PayoutListParams', {
1810
+ 'status': PayoutStatus,
1811
+ 'sub_seller_id': str,
1812
+ # 지급 예정일 범위 YYYY-MM-DD (포함)
1813
+ 'from': str,
1814
+ 'to': str,
1815
+ 'page': int,
1816
+ 'limit': int,
1817
+ 'idempotency_key': str,
1818
+ }, total=False)
1819
+
1820
+
1821
+ class CommercePayout(TypedDict, total=False):
1822
+ id: str
1823
+ merchant_payout_id: str
1824
+ sub_seller_id: str
1825
+ amount: int
1826
+ status: PayoutStatus
1827
+ payout_date: str
1828
+ order_number: Optional[str]
1829
+ last_code: Optional[str]
1830
+ last_message: Optional[str]
1831
+ created_at: str
1832
+ updated_at: str
@@ -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.9.0'
14
+ SDK_VERSION = '2.10.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 사용자를 위해 그대로 지원한다.