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.
- checksums.yaml +4 -4
- data/.gitignore +8 -9
- data/.rspec +2 -0
- data/CHANGELOG.md +63 -21
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +2 -4
- data/LICENSE.txt +1 -1
- data/README.md +309 -196
- data/Rakefile +3 -3
- data/bin/console +1 -1
- data/bootpay.gemspec +23 -20
- data/lib/bootpay/bootpay-rest-client.rb +51 -0
- data/lib/bootpay/concern/authenticate.rb +69 -0
- data/lib/bootpay/concern/cash_receipt.rb +83 -0
- data/lib/bootpay/concern/easy.rb +18 -0
- data/lib/bootpay/concern/escrow.rb +25 -0
- data/lib/bootpay/concern/payment.rb +126 -0
- data/lib/bootpay/concern/reseller.rb +97 -0
- data/lib/bootpay/concern/rest.rb +56 -0
- data/lib/bootpay/concern/sdk.rb +52 -0
- data/lib/bootpay/concern/seller.rb +15 -0
- data/lib/bootpay/concern/service.rb +15 -0
- data/lib/bootpay/concern/subscription.rb +211 -0
- data/lib/bootpay/concern/token.rb +33 -0
- data/lib/bootpay/concern/user_token.rb +23 -0
- data/lib/bootpay/concern/wallet.rb +20 -0
- data/lib/bootpay/concern/webhook.rb +18 -0
- data/lib/bootpay/concern.rb +35 -0
- data/lib/bootpay-backend-ruby.rb +6 -0
- data/lib/bootpay.rb +10 -66
- data/lib/bootpay_storage/bootpay-storage-rest-client.rb +46 -0
- data/lib/bootpay_storage/concern/csv.rb +18 -0
- data/lib/bootpay_storage/concern/image.rb +30 -0
- data/lib/bootpay_storage/concern/rest.rb +176 -0
- data/lib/bootpay_storage/concern/token.rb +9 -0
- data/lib/bootpay_storage/concern.rb +12 -0
- data/lib/bootpay_storage/response.rb +14 -0
- data/lib/bootpay_store/bootpay-store-rest-client.rb +47 -0
- data/lib/bootpay_store/concern/alimtalk_message.rb +67 -0
- data/lib/bootpay_store/concern/alimtalk_official.rb +63 -0
- data/lib/bootpay_store/concern/alimtalk_optout.rb +72 -0
- data/lib/bootpay_store/concern/alimtalk_send.rb +83 -0
- data/lib/bootpay_store/concern/alimtalk_sender.rb +105 -0
- data/lib/bootpay_store/concern/alimtalk_template.rb +215 -0
- data/lib/bootpay_store/concern/alimtalk_webhook.rb +84 -0
- data/lib/bootpay_store/concern/invoice.rb +102 -0
- data/lib/bootpay_store/concern/mall_setting.rb +385 -0
- data/lib/bootpay_store/concern/order.rb +74 -0
- data/lib/bootpay_store/concern/order_subscription.rb +391 -0
- data/lib/bootpay_store/concern/payment.rb +119 -0
- data/lib/bootpay_store/concern/product.rb +175 -0
- data/lib/bootpay_store/concern/rest.rb +128 -0
- data/lib/bootpay_store/concern/store.rb +31 -0
- data/lib/bootpay_store/concern/supervisor.rb +211 -0
- data/lib/bootpay_store/concern/token.rb +24 -0
- data/lib/bootpay_store/concern/user.rb +325 -0
- data/lib/bootpay_store/concern/user_group.rb +221 -0
- data/lib/bootpay_store/concern/webhook.rb +17 -0
- data/lib/bootpay_store/concern.rb +45 -0
- data/lib/bootpay_store/response.rb +14 -0
- data/lib/version.rb +9 -0
- metadata +77 -49
- data/.DS_Store +0 -0
- data/.env.example +0 -23
- data/lib/.DS_Store +0 -0
- data/lib/bootpay/.DS_Store +0 -0
- data/lib/bootpay/authentication.rb +0 -61
- data/lib/bootpay/automatic_transfer.rb +0 -56
- data/lib/bootpay/billing.rb +0 -157
- data/lib/bootpay/cancel.rb +0 -29
- data/lib/bootpay/cash_receipt.rb +0 -77
- data/lib/bootpay/commerce/cart.rb +0 -20
- data/lib/bootpay/commerce/category.rb +0 -39
- data/lib/bootpay/commerce/commerce_resource.rb +0 -196
- data/lib/bootpay/commerce/coupon.rb +0 -41
- data/lib/bootpay/commerce/invoice.rb +0 -46
- data/lib/bootpay/commerce/order.rb +0 -61
- data/lib/bootpay/commerce/order_cancel.rb +0 -52
- data/lib/bootpay/commerce/order_subscription.rb +0 -117
- data/lib/bootpay/commerce/order_subscription_adjustment.rb +0 -29
- data/lib/bootpay/commerce/order_subscription_bill.rb +0 -50
- data/lib/bootpay/commerce/order_subscription_request.rb +0 -60
- data/lib/bootpay/commerce/point.rb +0 -36
- data/lib/bootpay/commerce/product.rb +0 -63
- data/lib/bootpay/commerce/store.rb +0 -21
- data/lib/bootpay/commerce/user.rb +0 -75
- data/lib/bootpay/commerce/user_group.rb +0 -70
- data/lib/bootpay/easy.rb +0 -25
- data/lib/bootpay/escrow.rb +0 -24
- data/lib/bootpay/link.rb +0 -60
- data/lib/bootpay/naverpay.rb +0 -17
- data/lib/bootpay/payment_resource.rb +0 -31
- data/lib/bootpay/reseller.rb +0 -12
- data/lib/bootpay/rest.rb +0 -46
- data/lib/bootpay/submit.rb +0 -20
- data/lib/bootpay/token.rb +0 -33
- data/lib/bootpay/verification.rb +0 -55
- data/lib/bootpay/version.rb +0 -25
- data/lib/bootpay/wallet.rb +0 -56
- data/lib/bootpay_commerce.rb +0 -152
- /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
|