bootpay 1.2.0 → 3.0.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -9
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +63 -21
  5. data/CODE_OF_CONDUCT.md +1 -1
  6. data/Gemfile +2 -4
  7. data/LICENSE.txt +1 -1
  8. data/README.md +309 -196
  9. data/Rakefile +3 -3
  10. data/bin/console +1 -1
  11. data/bootpay.gemspec +23 -20
  12. data/lib/bootpay/bootpay-rest-client.rb +51 -0
  13. data/lib/bootpay/concern/authenticate.rb +69 -0
  14. data/lib/bootpay/concern/cash_receipt.rb +83 -0
  15. data/lib/bootpay/concern/easy.rb +18 -0
  16. data/lib/bootpay/concern/escrow.rb +25 -0
  17. data/lib/bootpay/concern/payment.rb +126 -0
  18. data/lib/bootpay/concern/reseller.rb +97 -0
  19. data/lib/bootpay/concern/rest.rb +56 -0
  20. data/lib/bootpay/concern/sdk.rb +52 -0
  21. data/lib/bootpay/concern/seller.rb +15 -0
  22. data/lib/bootpay/concern/service.rb +15 -0
  23. data/lib/bootpay/concern/subscription.rb +211 -0
  24. data/lib/bootpay/concern/token.rb +33 -0
  25. data/lib/bootpay/concern/user_token.rb +23 -0
  26. data/lib/bootpay/concern/wallet.rb +20 -0
  27. data/lib/bootpay/concern/webhook.rb +18 -0
  28. data/lib/bootpay/concern.rb +35 -0
  29. data/lib/bootpay-backend-ruby.rb +6 -0
  30. data/lib/bootpay.rb +10 -66
  31. data/lib/bootpay_storage/bootpay-storage-rest-client.rb +46 -0
  32. data/lib/bootpay_storage/concern/csv.rb +18 -0
  33. data/lib/bootpay_storage/concern/image.rb +30 -0
  34. data/lib/bootpay_storage/concern/rest.rb +176 -0
  35. data/lib/bootpay_storage/concern/token.rb +9 -0
  36. data/lib/bootpay_storage/concern.rb +12 -0
  37. data/lib/bootpay_storage/response.rb +14 -0
  38. data/lib/bootpay_store/bootpay-store-rest-client.rb +47 -0
  39. data/lib/bootpay_store/concern/alimtalk_message.rb +67 -0
  40. data/lib/bootpay_store/concern/alimtalk_official.rb +63 -0
  41. data/lib/bootpay_store/concern/alimtalk_optout.rb +72 -0
  42. data/lib/bootpay_store/concern/alimtalk_send.rb +83 -0
  43. data/lib/bootpay_store/concern/alimtalk_sender.rb +105 -0
  44. data/lib/bootpay_store/concern/alimtalk_template.rb +215 -0
  45. data/lib/bootpay_store/concern/alimtalk_webhook.rb +84 -0
  46. data/lib/bootpay_store/concern/invoice.rb +102 -0
  47. data/lib/bootpay_store/concern/mall_setting.rb +385 -0
  48. data/lib/bootpay_store/concern/order.rb +74 -0
  49. data/lib/bootpay_store/concern/order_subscription.rb +391 -0
  50. data/lib/bootpay_store/concern/payment.rb +119 -0
  51. data/lib/bootpay_store/concern/product.rb +175 -0
  52. data/lib/bootpay_store/concern/rest.rb +128 -0
  53. data/lib/bootpay_store/concern/store.rb +31 -0
  54. data/lib/bootpay_store/concern/supervisor.rb +211 -0
  55. data/lib/bootpay_store/concern/token.rb +24 -0
  56. data/lib/bootpay_store/concern/user.rb +325 -0
  57. data/lib/bootpay_store/concern/user_group.rb +221 -0
  58. data/lib/bootpay_store/concern/webhook.rb +17 -0
  59. data/lib/bootpay_store/concern.rb +45 -0
  60. data/lib/bootpay_store/response.rb +14 -0
  61. data/lib/version.rb +9 -0
  62. metadata +77 -49
  63. data/.DS_Store +0 -0
  64. data/.env.example +0 -23
  65. data/lib/.DS_Store +0 -0
  66. data/lib/bootpay/.DS_Store +0 -0
  67. data/lib/bootpay/authentication.rb +0 -61
  68. data/lib/bootpay/automatic_transfer.rb +0 -56
  69. data/lib/bootpay/billing.rb +0 -157
  70. data/lib/bootpay/cancel.rb +0 -29
  71. data/lib/bootpay/cash_receipt.rb +0 -77
  72. data/lib/bootpay/commerce/cart.rb +0 -20
  73. data/lib/bootpay/commerce/category.rb +0 -39
  74. data/lib/bootpay/commerce/commerce_resource.rb +0 -196
  75. data/lib/bootpay/commerce/coupon.rb +0 -41
  76. data/lib/bootpay/commerce/invoice.rb +0 -46
  77. data/lib/bootpay/commerce/order.rb +0 -61
  78. data/lib/bootpay/commerce/order_cancel.rb +0 -52
  79. data/lib/bootpay/commerce/order_subscription.rb +0 -117
  80. data/lib/bootpay/commerce/order_subscription_adjustment.rb +0 -29
  81. data/lib/bootpay/commerce/order_subscription_bill.rb +0 -50
  82. data/lib/bootpay/commerce/order_subscription_request.rb +0 -60
  83. data/lib/bootpay/commerce/point.rb +0 -36
  84. data/lib/bootpay/commerce/product.rb +0 -63
  85. data/lib/bootpay/commerce/store.rb +0 -21
  86. data/lib/bootpay/commerce/user.rb +0 -75
  87. data/lib/bootpay/commerce/user_group.rb +0 -70
  88. data/lib/bootpay/easy.rb +0 -25
  89. data/lib/bootpay/escrow.rb +0 -24
  90. data/lib/bootpay/link.rb +0 -60
  91. data/lib/bootpay/naverpay.rb +0 -17
  92. data/lib/bootpay/payment_resource.rb +0 -31
  93. data/lib/bootpay/reseller.rb +0 -12
  94. data/lib/bootpay/rest.rb +0 -46
  95. data/lib/bootpay/submit.rb +0 -20
  96. data/lib/bootpay/token.rb +0 -33
  97. data/lib/bootpay/verification.rb +0 -55
  98. data/lib/bootpay/version.rb +0 -25
  99. data/lib/bootpay/wallet.rb +0 -56
  100. data/lib/bootpay_commerce.rb +0 -152
  101. /data/lib/{response.rb → bootpay/response.rb} +0 -0
@@ -0,0 +1,175 @@
1
+ module BootpayStore::Concern::Product
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # 상품 목록을 조회한다 (V1 Mall API)
6
+ # Comment by Codex
7
+ # @date: 2026-02-23
8
+ # @date: 26-08-14 ⚠️ keyword 는 서버(v1/products_controller#index)가 읽지 않는다.
9
+ # 컨트롤러는 page/limit/category_id/ex_uid/sort 만 사용 — keyword 를 보내도 조용히 무시된다.
10
+ # 하위호환 때문에 인자는 남겨두되, 검색이 필요하면 서버 지원 추가가 선행되어야 한다.
11
+ # @date: 26-08-26 ex_uid 인자 추가. 컨트롤러가 params[:ex_uid] 를 읽는데 SDK 에 인자가 없어
12
+ # 외부 UID 로 상품을 찾을 수 없었다.
13
+ def products(page: 1, limit: 20, category_id: nil, ex_uid: nil, sort: nil, keyword: nil,
14
+ user_jwt: nil, idempotency_key: nil)
15
+ request(
16
+ uri: 'products',
17
+ method: :get,
18
+ headers: {
19
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
20
+ 'Bootpay-User-JWT' => user_jwt
21
+ }.compact,
22
+ params: {
23
+ page: page,
24
+ limit: limit,
25
+ category_id: category_id,
26
+ ex_uid: ex_uid,
27
+ sort: sort,
28
+ keyword: keyword
29
+ }.compact
30
+ )
31
+ end
32
+
33
+ # 상품 상세를 조회한다 (V1 Mall API)
34
+ # Comment by Codex
35
+ # @date: 2026-02-23
36
+ def product_detail(product_id:, user_jwt: nil, idempotency_key: nil)
37
+ request(
38
+ uri: "products/#{product_id}",
39
+ method: :get,
40
+ headers: {
41
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
42
+ 'Bootpay-User-JWT' => user_jwt
43
+ }.compact
44
+ )
45
+ end
46
+
47
+ # 상품 정보를 가져온다
48
+ # Comment by GOSOMI
49
+ # @date: 2025-10-10
50
+ # @date: 26-08-14 product_detail 과 uri·동작이 같다(차이는 user_jwt 인자 유무).
51
+ # 중복이지만 기존 사용자가 있을 수 있어 제거하지 않는다 — 신규 코드는 product_detail 을 쓸 것.
52
+ # @date: 26-08-26 user_jwt 를 여기에도 추가했다. 매뉴얼이 GET /v1/products/:id 에 user_jwt 를
53
+ # 안내하는데 이 메서드만 헤더를 안 보내 회원 컨텍스트 조회가 안 됐다. 이제 product_detail 과 동작이 같다.
54
+ def lookup_product(product_id:, user_jwt: nil, idempotency_key: nil)
55
+ request(
56
+ uri: "products/#{product_id}",
57
+ method: :get,
58
+ headers: {
59
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
60
+ 'Bootpay-User-JWT' => user_jwt
61
+ }.compact
62
+ )
63
+ end
64
+
65
+ # 상품을 등록한다 (POST /v1/products)
66
+ # @comment_by Claude (alfred)
67
+ # @date: 26-08-14
68
+ # images 가 있으면 multipart, 없으면 JSON 으로 보낸다.
69
+ # 컨트롤러가 읽는 필드는 _product_params 참조 — 여기 명시하지 않은 값도 attrs 로 그대로 전달된다.
70
+ def product_create(name:, display_price: nil, desc: nil, content: nil, category_id: nil,
71
+ type: nil, stock: nil, status_sale: nil, status_display: nil,
72
+ use_subscription: nil, subscription_setting_id: nil,
73
+ images: nil, save_by: nil, idempotency_key: nil, **attrs)
74
+ payload = {
75
+ name: name,
76
+ display_price: display_price,
77
+ desc: desc,
78
+ content: content,
79
+ category_id: category_id,
80
+ type: type,
81
+ stock: stock,
82
+ status_sale: status_sale,
83
+ status_display: status_display,
84
+ use_subscription: use_subscription,
85
+ subscription_setting_id: subscription_setting_id,
86
+ save_by: save_by
87
+ }.merge(attrs).compact
88
+
89
+ headers = {
90
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
91
+ 'Bootpay-Role' => 'manager'
92
+ }
93
+
94
+ if images.present?
95
+ form = payload.each_with_object({}) { |(k, v), h| h[k.to_s] = multipart_value(v) }
96
+ Array(images).each_with_index { |image, i| form["images[#{i}]"] = multipart_file(image) }
97
+ post_multipart(uri: 'products', form: form, headers: headers)
98
+ else
99
+ request(uri: 'products', method: :post, headers: headers, payload: payload)
100
+ end
101
+ end
102
+
103
+ # 상품을 수정한다 (PUT /v1/products/:id)
104
+ # @comment_by Claude (alfred)
105
+ # @date: 26-08-14
106
+ # 바뀐 값만 보내면 된다. category_id 는 키 존재 여부로 '해제 의사'를 판별하므로 주의.
107
+ def product_update(product_id:, name: nil, display_price: nil, desc: nil, content: nil,
108
+ category_id: nil, stock: nil, status_sale: nil, status_display: nil,
109
+ save_by: nil, idempotency_key: nil, **attrs)
110
+ request(
111
+ uri: "products/#{product_id}",
112
+ method: :put,
113
+ headers: {
114
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
115
+ 'Bootpay-Role' => 'manager'
116
+ },
117
+ payload: {
118
+ name: name,
119
+ display_price: display_price,
120
+ desc: desc,
121
+ content: content,
122
+ category_id: category_id,
123
+ stock: stock,
124
+ status_sale: status_sale,
125
+ status_display: status_display,
126
+ save_by: save_by
127
+ }.merge(attrs).compact
128
+ )
129
+ end
130
+
131
+ # 상품을 삭제한다 (DELETE /v1/products/:id)
132
+ # @comment_by Claude (alfred)
133
+ # @date: 26-08-14
134
+ def product_delete(product_id:, idempotency_key: nil)
135
+ request(
136
+ uri: "products/#{product_id}",
137
+ method: :delete,
138
+ headers: {
139
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
140
+ 'Bootpay-Role' => 'manager'
141
+ }
142
+ )
143
+ end
144
+
145
+ # 상품 판매/노출 상태를 변경한다 (PUT /v1/products/:id/status)
146
+ # @comment_by Claude (alfred)
147
+ # @date: 26-08-14
148
+ # 컨트롤러 _status_params 기준. 재고(stock)는 여기가 아니라 product_update 로 바꾼다.
149
+ def product_status(product_id:, status_sale: nil, status_display: nil, status_frozen: nil,
150
+ status_review: nil, use_display_period: nil, display_start_at: nil, display_end_at: nil,
151
+ use_sale_period: nil, sale_start_at: nil, sale_end_at: nil,
152
+ idempotency_key: nil, **attrs)
153
+ request(
154
+ uri: "products/#{product_id}/status",
155
+ method: :put,
156
+ headers: {
157
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
158
+ 'Bootpay-Role' => 'manager'
159
+ },
160
+ payload: {
161
+ status_sale: status_sale,
162
+ status_display: status_display,
163
+ status_frozen: status_frozen,
164
+ status_review: status_review,
165
+ use_display_period: use_display_period,
166
+ display_start_at: display_start_at,
167
+ display_end_at: display_end_at,
168
+ use_sale_period: use_sale_period,
169
+ sale_start_at: sale_start_at,
170
+ sale_end_at: sale_end_at
171
+ }.merge(attrs).compact
172
+ )
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,128 @@
1
+ module BootpayStore::Concern::Rest
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ private
6
+
7
+ # HTTP Request 기본 Method
8
+ # Comment by Gosomi
9
+ # Date: 2021-05-21
10
+ def request(method: :post, uri:, payload: {}, headers: {}, params: nil)
11
+ response = HTTP.headers(
12
+ {
13
+ Authorization: @token.present? ? "Bearer #{@token}" : "Basic #{basic_authentification}",
14
+ content_type: 'application/json',
15
+ accept: 'application/json',
16
+ bootpay_api_version: @api_version,
17
+ bootpay_sdk_version: Bootpay::V2_VERSION,
18
+ bootpay_sdk_type: '300'
19
+ }.merge!(headers).compact
20
+ ).send(
21
+ method.to_sym,
22
+ [BootpayStore::RestClient::API[@mode.to_sym], uri].join('/'),
23
+ json: payload,
24
+ params: params
25
+ )
26
+ BootpayStore::Response.new(
27
+ response.status.to_i == 200,
28
+ JSON.parse(response.body.to_s, symbolize_names: true)
29
+ )
30
+ rescue Exception => e
31
+ BootpayStore::Response.new(
32
+ false,
33
+ message: "부트페이 API 서버와의 통신이 실패하였습니다. 오류 메세지: #{e.message}",
34
+ backtrace: e.backtrace.join("\n")
35
+ )
36
+ end
37
+
38
+ # JSON 이 아닌 본문(CSV 등)을 파싱하지 않고 그대로 받는다.
39
+ # @comment_by Claude (alfred)
40
+ # @date: 26-08-27
41
+ # ⚠️ 공용 request 는 응답을 무조건 JSON.parse 하므로, CSV 를 돌려주는 엔드포인트
42
+ # (알림톡 템플릿 내보내기 format=csv)에서는 파싱 실패가 rescue 로 흘러
43
+ # **"부트페이 API 서버와의 통신이 실패하였습니다"** 라는 틀린 메시지로 보고된다.
44
+ # 실제로는 성공한 요청이라 그대로 두면 원인을 못 찾는다. 그래서 원문 경로를 따로 둔다.
45
+ # 성공 시 data 는 { body: '<원문 문자열>', content_type: '...' } 다.
46
+ def request_raw(uri:, method: :get, params: nil, headers: {})
47
+ response = HTTP.headers(
48
+ {
49
+ Authorization: @token.present? ? "Bearer #{@token}" : "Basic #{basic_authentification}",
50
+ accept: '*/*',
51
+ bootpay_api_version: @api_version,
52
+ bootpay_sdk_version: Bootpay::V2_VERSION,
53
+ bootpay_sdk_type: '300'
54
+ }.merge!(headers).compact
55
+ ).send(
56
+ method.to_sym,
57
+ [BootpayStore::RestClient::API[@mode.to_sym], uri].join('/'),
58
+ params: params
59
+ )
60
+ BootpayStore::Response.new(
61
+ response.status.to_i == 200,
62
+ body: response.body.to_s,
63
+ content_type: response.headers['Content-Type'].to_s
64
+ )
65
+ rescue Exception => e
66
+ BootpayStore::Response.new(
67
+ false,
68
+ message: "부트페이 API 서버와의 통신이 실패하였습니다. 오류 메세지: #{e.message}",
69
+ backtrace: e.backtrace.join("\n")
70
+ )
71
+ end
72
+
73
+ # multipart/form-data 전송 (파일 업로드용)
74
+ # @comment_by Claude (alfred)
75
+ # @date: 26-08-14
76
+ # ⚠️ content_type 을 명시하지 않는다. 명시하면 HTTP.rb 가 붙이는 boundary 가 사라져 본문이 깨진다.
77
+ # (Node SDK 는 인터셉터가 Content-Type 을 덮어써 본문이 null 로 전송되는 버그가 있다 — 같은 함정)
78
+ # 기존 request 는 json: 고정이라 손대지 않고 별도 메서드로 둔다.
79
+ def post_multipart(uri:, form: {}, headers: {})
80
+ response = HTTP.headers(
81
+ {
82
+ Authorization: @token.present? ? "Bearer #{@token}" : "Basic #{basic_authentification}",
83
+ accept: 'application/json',
84
+ bootpay_api_version: @api_version,
85
+ bootpay_sdk_version: Bootpay::V2_VERSION,
86
+ bootpay_sdk_type: '300'
87
+ }.merge!(headers).compact
88
+ ).post(
89
+ [BootpayStore::RestClient::API[@mode.to_sym], uri].join('/'),
90
+ form: form
91
+ )
92
+ BootpayStore::Response.new(
93
+ response.status.to_i == 200,
94
+ JSON.parse(response.body.to_s, symbolize_names: true)
95
+ )
96
+ rescue Exception => e
97
+ BootpayStore::Response.new(
98
+ false,
99
+ message: "부트페이 API 서버와의 통신이 실패하였습니다. 오류 메세지: #{e.message}",
100
+ backtrace: e.backtrace.join("\n")
101
+ )
102
+ end
103
+
104
+ # multipart form 값 정규화 — 파일은 FormData::File 로, 나머지는 문자열로.
105
+ # 파일은 경로(String)·IO·HTTP::FormData::File 셋 다 받는다.
106
+ # @comment_by Claude (alfred)
107
+ # @date: 26-08-14
108
+ def multipart_file(value)
109
+ return value if value.is_a?(HTTP::FormData::File)
110
+ HTTP::FormData::File.new(value)
111
+ end
112
+
113
+ def multipart_value(value)
114
+ case value
115
+ when Array, Hash then value.to_json
116
+ when TrueClass, FalseClass then value.to_s
117
+ else value.to_s
118
+ end
119
+ end
120
+
121
+ # basic authenticate 추가
122
+ # Comment by GOSOMI
123
+ # @date: 2026-02-20
124
+ def basic_authentification
125
+ Base64.strict_encode64("#{@client_key}:#{@secret_key}")
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,31 @@
1
+ module BootpayStore::Concern::Store
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # 가맹점 기본 정보를 조회한다 (/v1/store)
6
+ # Comment by Codex
7
+ # @date: 2026-02-23
8
+ def get_store(idempotency_key: nil)
9
+ request(
10
+ uri: 'store',
11
+ method: :get,
12
+ headers: {
13
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid
14
+ }
15
+ )
16
+ end
17
+
18
+ # 가맹점 상세 정보를 조회한다 (/v1/store/detail)
19
+ # Comment by Codex
20
+ # @date: 2026-02-23
21
+ def get_store_detail(idempotency_key: nil)
22
+ request(
23
+ uri: 'store/detail',
24
+ method: :get,
25
+ headers: {
26
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid
27
+ }
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,211 @@
1
+ module BootpayStore::Concern::Supervisor
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # 주문 취소
6
+ # Comment by GOSOMI
7
+ # @date: 2025-04-04
8
+ # param force: 결제취소 실패시 주문을 강제로 취소 상태로 변경
9
+ def supervisor_request_order_cancel(idempotency_key: nil, cancel_id: nil, order_number:, cancel_products: nil, cancel_price: nil,
10
+ cancel_tax_free_price: 0, cancel_requester: '시스템', cancel_message: '요청취소', cancel_immediately: false,
11
+ cancel_order_subscription_bills: nil, force: false)
12
+ request(
13
+ uri: 'order/cancel',
14
+ headers: {
15
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
16
+ 'Bootpay-Role' => 'supervisor'
17
+ },
18
+ payload:
19
+ {
20
+ order_number: order_number,
21
+ request_cancel_parameters: {
22
+ cancel_id: cancel_id,
23
+ cancel_products: cancel_products,
24
+ cancel_order_subscription_bills: cancel_order_subscription_bills,
25
+ cancel_price: cancel_price,
26
+ cancel_tax_free_price: cancel_tax_free_price,
27
+ cancel_requester: cancel_requester,
28
+ cancel_message: cancel_message,
29
+ cancel_immediately: cancel_immediately,
30
+ force: force
31
+ }.compact
32
+ }.compact
33
+ )
34
+ end
35
+
36
+ # bill 주문 취소
37
+ # 전체취소 하거나, 부분취소할 수 있다.
38
+ # 부분취소는 상품으로하거나 금액으로 할 수 있는데, 상품으로 취소되면 배송비 등이 자동 계산되지만, 금액으로 취소는 환불개념이라 자동계산 되지 않는다.
39
+ # Comment by ehowlsla
40
+ # @date: 2025-04-04
41
+ def supervisor_request_order_subscription_bill_cancel(idempotency_key: nil, cancel_id: nil, order_subscription_bill_id:, cancel_products: [], cancel_price: nil,
42
+ cancel_tax_free_price: nil, cancel_requester: '시스템', cancel_message: '요청취소', cancel_immediately: false)
43
+
44
+ request(
45
+ uri: 'order_subscriptions/bill/cancel',
46
+ headers: {
47
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
48
+ 'Bootpay-Role' => 'supervisor'
49
+ },
50
+ payload:
51
+ {
52
+ order_subscription_bill_id: order_subscription_bill_id,
53
+ cancel_id: cancel_id,
54
+ cancel_products: cancel_products,
55
+ cancel_price: cancel_price,
56
+ cancel_tax_free_price: cancel_tax_free_price,
57
+ cancel_requester: cancel_requester,
58
+ cancel_message: cancel_message,
59
+ cancel_immediately: cancel_immediately
60
+ }.compact
61
+ )
62
+
63
+ end
64
+
65
+ # 구독 단건 관리자 승인
66
+ # 관리자 승인일 경우 bill 생성 후 (선불이면) 결제를 진행해야함
67
+ # Comment by ehowlsla
68
+ # @date: 2025-11-19
69
+ def supervisor_request_order_subscription_approve(idempotency_key: nil, order_subscription_id:, reason: nil)
70
+ request(
71
+ uri: "order_subscriptions/#{order_subscription_id}/approve",
72
+ method: :put,
73
+ headers: {
74
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
75
+ 'Bootpay-Role' => 'supervisor'
76
+ },
77
+ payload:
78
+ {
79
+ reason: reason
80
+ }.compact
81
+ )
82
+ end
83
+
84
+ # 구독 단건 승인 거절
85
+ # 요청된 구독건에 대해 거절 처리
86
+ # 만약 생성된 bill 이 있다면 함께 취소 처리
87
+ # Comment by ehowlsla
88
+ # @date: 2025-11-9
89
+ def supervisor_request_order_subscription_reject(idempotency_key: nil, order_subscription_id:, reason: nil)
90
+ request(
91
+ uri: "order_subscriptions/#{order_subscription_id}/reject",
92
+ method: :put,
93
+ headers: {
94
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
95
+ 'Bootpay-Role' => 'supervisor'
96
+ },
97
+ payload:
98
+ {
99
+ reason: reason
100
+ }.compact
101
+ )
102
+ end
103
+
104
+ # 구독 해지 실행
105
+ # Comment by GOSOMI
106
+ # @date: 2026-01-21
107
+ def supervisor_request_order_subscription_terminate(idempotency_key: nil, order_subscription_id:, reason: nil,
108
+ termination_fee: nil, last_bill_refund_price: nil, final_fee: nil,
109
+ service_end_at: nil, cancel_date: nil)
110
+ request(
111
+ uri: "order_subscriptions/#{order_subscription_id}/terminate",
112
+ method: :put,
113
+ headers: {
114
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
115
+ 'Bootpay-Role' => 'supervisor'
116
+ },
117
+ payload:
118
+ {
119
+ reason: reason,
120
+ termination_fee: termination_fee,
121
+ last_bill_refund_price: last_bill_refund_price,
122
+ final_fee: final_fee,
123
+ service_end_at: service_end_at,
124
+ cancel_date: cancel_date
125
+ }.compact
126
+ )
127
+ end
128
+
129
+ # 구독 멈춤
130
+ # Comment by GOSOMI
131
+ # @date: 2026-01-22
132
+ def supervisor_request_order_subscription_pause(idempotency_key: nil, order_subscription_id:, reason: nil,
133
+ paused_at:, expected_resume_at: nil)
134
+ request(
135
+ uri: "order_subscriptions/#{order_subscription_id}/pause",
136
+ method: :put,
137
+ headers: {
138
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
139
+ 'Bootpay-Role' => 'supervisor'
140
+ },
141
+ payload:
142
+ {
143
+ reason: reason,
144
+ paused_at: paused_at,
145
+ expected_resume_at: expected_resume_at
146
+ }.compact
147
+ )
148
+ end
149
+
150
+ # 구독 재개
151
+ # Comment by GOSOMI
152
+ # @date: 2026-01-22
153
+ def supervisor_request_order_subscription_resume(idempotency_key: nil, order_subscription_id:, reason: nil)
154
+ request(
155
+ uri: "order_subscriptions/#{order_subscription_id}/resume",
156
+ method: :put,
157
+ headers: {
158
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
159
+ 'Bootpay-Role' => 'supervisor'
160
+ },
161
+ payload:
162
+ {
163
+ reason: reason
164
+ }.compact
165
+ )
166
+ end
167
+
168
+ # 수시결제(온디맨드) charge_key 즉시 결제
169
+ # charge_key는 body로만 전송한다 (URL/query 금지 — 액세스 로그 노출 방지)
170
+ # Comment by GOSOMI
171
+ # @date: 2026-07-23
172
+ def supervisor_request_order_subscription_charge(idempotency_key: nil, charge_key:, price:,
173
+ tax_free_price: nil, user: nil, metadata: nil)
174
+ request(
175
+ uri: 'order_subscriptions/charge',
176
+ headers: {
177
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
178
+ 'Bootpay-Role' => 'supervisor'
179
+ },
180
+ payload:
181
+ {
182
+ charge_key: charge_key,
183
+ price: price,
184
+ tax_free_price: tax_free_price,
185
+ user: user,
186
+ metadata: metadata
187
+ }.compact
188
+ )
189
+ end
190
+
191
+ # 수시결제(온디맨드) charge_key 해지
192
+ # 해지 이후 해당 키로의 재결제는 불가능하다
193
+ # Comment by GOSOMI
194
+ # @date: 2026-07-23
195
+ def supervisor_request_order_subscription_charge_revoke(idempotency_key: nil, charge_key:, user: nil)
196
+ request(
197
+ uri: 'order_subscriptions/charge',
198
+ method: :delete,
199
+ headers: {
200
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
201
+ 'Bootpay-Role' => 'supervisor'
202
+ },
203
+ payload:
204
+ {
205
+ charge_key: charge_key,
206
+ user: user
207
+ }.compact
208
+ )
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,24 @@
1
+ module BootpayStore::Concern::Token
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Access Token을 요청한다
6
+ # Comment by Gosomi
7
+ # Date: 2021-05-21
8
+ # @deprecated
9
+ def request_access_token
10
+ response = request(
11
+ uri: 'request/token',
12
+ headers: {
13
+ Authorization: "Basic #{Base64.strict_encode64("#{@client_key}:#{@secret_key}")}"
14
+ }
15
+ )
16
+ @token = response.data[:access_token] if response.success?
17
+ response
18
+ end
19
+
20
+ def set_token(token)
21
+ @token = token
22
+ end
23
+ end
24
+ end