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,215 @@
1
+ module BootpayStore::Concern::AlimtalkTemplate
2
+ extend ActiveSupport::Concern
3
+
4
+ # 가맹점 자체 알림톡 템플릿 CRUD·등록·검수 — /v1/alimtalk/templates 계열
5
+ #
6
+ # 흐름: (초안 생성 → 확인 → 대행사 등록) → 검수 요청 → 승인(APR) → 발송 가능
7
+ # `alimtalk_template_create(register: false)` 로 초안만 만들고, 내용을 확인한 뒤
8
+ # `alimtalk_template_register` 로 올리는 것을 권장한다.
9
+ #
10
+ # ⚠️ `register` 를 명시적으로 false 로 주지 않으면 **생성 즉시 대행사·카카오에 실제 등록**된다.
11
+ # ⚠️ 본문 변수는 `#{변수명}` 형식이고 템플릿 전체에서 최대 40개다.
12
+ #
13
+ # @comment_by Claude (alfred)
14
+ # @date: 26-08-27
15
+ included do
16
+ # 내 자체 템플릿 목록을 조회한다 (GET /v1/alimtalk/templates)
17
+ # ins: 검수상태 필터 — 1 REG(등록) / 2 REQ(검수요청) / 3 APR(승인) / 4 KRR(등록거절) / 5 REJ(승인반려).
18
+ # 숫자·숫자문자열·벤더 문자열('APR' 등)을 모두 받는다. 해석 못 하는 값은 필터 없음으로 떨어진다.
19
+ # keyword: 코드·이름·본문·분류 부분일치. sort: latest(기본)·oldest·code.
20
+ # ⚠️ 페이지네이션이 없다 — 필터에 걸린 템플릿을 한 번에 모두 돌려준다.
21
+ def alimtalk_template_list(ins: nil, sort: nil, keyword: nil)
22
+ request(
23
+ uri: 'alimtalk/templates',
24
+ method: :get,
25
+ headers: { 'Bootpay-Role' => 'user' },
26
+ params: {
27
+ ins: ins,
28
+ sort: sort,
29
+ keyword: keyword
30
+ }.compact
31
+ )
32
+ end
33
+
34
+ # 자체 템플릿을 생성한다 (POST /v1/alimtalk/templates)
35
+ # ⚠️ register 를 false 로 주지 않으면 대행사·카카오에 **실제 등록**된다(되돌리려면 삭제해야 한다).
36
+ #
37
+ # emphasize_type: NONE·TEXT(강조표기형)·IMAGE(이미지형)·ITEM_LIST(아이템리스트형)
38
+ # - TEXT 는 emphasize_title·emphasize_subtitle 둘 다 필수(각 50자·40자)
39
+ # - IMAGE 는 이미지 필수 — alimtalk_template_image 로 올린 URL 을 storage_image_url 로 넘긴다
40
+ # - ITEM_LIST 는 template_item.list(2~10개) 필수 + template_header·item_highlight·이미지 중 하나 이상
41
+ # msg_type: BA(기본형)·EX(부가정보형, template_extra 필수)·AD(채널추가형)·MI(복합형)
42
+ # - AD·MI 는 채널추가(AC) 버튼이 필수다
43
+ # examples: 변수 예문(표시용). 주면 **모든 변수에 예문이 있어야** 한다(없으면 3017).
44
+ def alimtalk_template_create(ksp_id:, name: nil, content: nil, register: nil, buttons: nil,
45
+ msg_type: nil, emphasize_type: nil, emphasize_title: nil,
46
+ emphasize_subtitle: nil, template_extra: nil, template_header: nil,
47
+ item_highlight: nil, template_item: nil, image_url: nil,
48
+ storage_image_url: nil, security_flag: nil, category: nil, tags: nil,
49
+ examples: nil, template_code: nil, **attrs)
50
+ request(
51
+ uri: 'alimtalk/templates',
52
+ method: :post,
53
+ headers: { 'Bootpay-Role' => 'user' },
54
+ payload: {
55
+ ksp_id: ksp_id,
56
+ register: register,
57
+ name: name,
58
+ content: content,
59
+ buttons: buttons,
60
+ msg_type: msg_type,
61
+ emphasize_type: emphasize_type,
62
+ emphasize_title: emphasize_title,
63
+ emphasize_subtitle: emphasize_subtitle,
64
+ template_extra: template_extra,
65
+ template_header: template_header,
66
+ item_highlight: item_highlight,
67
+ template_item: template_item,
68
+ image_url: image_url,
69
+ storage_image_url: storage_image_url,
70
+ security_flag: security_flag,
71
+ category: category,
72
+ tags: tags,
73
+ examples: examples,
74
+ template_code: template_code
75
+ }.merge(attrs).compact
76
+ )
77
+ end
78
+
79
+ # 자체 템플릿 상세를 조회한다 (GET /v1/alimtalk/templates/:id)
80
+ # template_id 는 문서 id 이고, ObjectId 형식이 아니면 **템플릿 코드**로 해석한다.
81
+ # ⚠️ sync 는 서버 기본값이 **true** 라 조회만 해도 벤더 상태 동기화가 일어난다.
82
+ # 초안(등록 전)을 조회할 때는 sync: false 를 권장한다.
83
+ def alimtalk_template_detail(template_id:, sync: nil)
84
+ request(
85
+ uri: "alimtalk/templates/#{template_id}",
86
+ method: :get,
87
+ headers: { 'Bootpay-Role' => 'user' },
88
+ params: { sync: sync }.compact
89
+ )
90
+ end
91
+
92
+ # 자체 템플릿을 수정한다 (PUT /v1/alimtalk/templates/:id)
93
+ # ⚠️ **부분 수정이 아니다.** 보내지 않은 필드는 nil 로 덮어써지므로 항상 전체 필드를 보낸다.
94
+ # ⚠️ 등록된 템플릿을 수정하면 벤더에도 수정 요청이 나간다.
95
+ # 수정 가능 상태는 초안 / REG(등록) / REJ(승인반려) / KRR(등록거절) 뿐이다 — APR·REQ 는 거부된다.
96
+ # storage_image_url 을 빈 값으로 보내면 **이미지 삭제**로 처리되어 벤더에도 전달된다.
97
+ def alimtalk_template_update(template_id:, name: nil, content: nil, buttons: nil, msg_type: nil,
98
+ emphasize_type: nil, emphasize_title: nil, emphasize_subtitle: nil,
99
+ template_extra: nil, template_header: nil, item_highlight: nil,
100
+ template_item: nil, image_url: nil, storage_image_url: nil,
101
+ security_flag: nil, category: nil, tags: nil, examples: nil,
102
+ template_code: nil, **attrs)
103
+ request(
104
+ uri: "alimtalk/templates/#{template_id}",
105
+ method: :put,
106
+ headers: { 'Bootpay-Role' => 'user' },
107
+ payload: {
108
+ name: name,
109
+ content: content,
110
+ buttons: buttons,
111
+ msg_type: msg_type,
112
+ emphasize_type: emphasize_type,
113
+ emphasize_title: emphasize_title,
114
+ emphasize_subtitle: emphasize_subtitle,
115
+ template_extra: template_extra,
116
+ template_header: template_header,
117
+ item_highlight: item_highlight,
118
+ template_item: template_item,
119
+ image_url: image_url,
120
+ storage_image_url: storage_image_url,
121
+ security_flag: security_flag,
122
+ category: category,
123
+ tags: tags,
124
+ examples: examples,
125
+ template_code: template_code
126
+ }.merge(attrs).compact
127
+ )
128
+ end
129
+
130
+ # 자체 템플릿을 삭제한다 (DELETE /v1/alimtalk/templates/:id)
131
+ # 초안(등록 전)은 대행사 거부와 무관하게 로컬에서 삭제된다.
132
+ # ⚠️ 등록분은 **대행사 삭제가 성공해야** 삭제된다 — 승인(APR) 템플릿은 카카오가 거부하므로
133
+ # 500(3013)이 오고 템플릿은 남는다. 같은 코드가 대행사에 선점된 채 로컬만 사라지는 것을 막기 위함이다.
134
+ def alimtalk_template_delete(template_id:)
135
+ request(
136
+ uri: "alimtalk/templates/#{template_id}",
137
+ method: :delete,
138
+ headers: { 'Bootpay-Role' => 'user' }
139
+ )
140
+ end
141
+
142
+ # 초안을 대행사에 등록한다 (POST /v1/alimtalk/templates/:id/register)
143
+ # ⚠️ 대행사·카카오에 실제 등록된다. 등록 전(초안) 상태에서만 호출할 수 있다.
144
+ def alimtalk_template_register(template_id:)
145
+ request(
146
+ uri: "alimtalk/templates/#{template_id}/register",
147
+ method: :post,
148
+ headers: { 'Bootpay-Role' => 'user' }
149
+ )
150
+ end
151
+
152
+ # 검수를 요청한다 (POST /v1/alimtalk/templates/:id/inspect)
153
+ # ⚠️ **카카오에 검수를 요청하며 취소할 수 없다.**
154
+ # 대행사 등록이 끝난 대기(R) + REG(등록) 상태에서만 호출할 수 있다 — 초안은 먼저 register 를 부른다.
155
+ # 반려(REJ/KRR)된 건은 재요청이 아니라 **수정 후 재요청**이다. 반려 사유는 응답의 comments 에 담긴다.
156
+ def alimtalk_template_inspect(template_id:)
157
+ request(
158
+ uri: "alimtalk/templates/#{template_id}/inspect",
159
+ method: :post,
160
+ headers: { 'Bootpay-Role' => 'user' }
161
+ )
162
+ end
163
+
164
+ # 템플릿 목록을 내보낸다 (GET /v1/alimtalk/templates/export)
165
+ # scope: private(기본, 내 채널 자체 템플릿)·official(공식 카탈로그)·all
166
+ # ⚠️ 기본 format 을 **json 으로 둔다** — 서버 기본은 csv 지만, csv 본문은 JSON 이 아니라서
167
+ # 공용 request 의 파싱을 통과하지 못한다. csv 를 주면 파싱 없이 원문 문자열을 담아 돌려준다.
168
+ # 1회 5,000건을 넘으면 3031 로 거부되므로 채널·상태 필터로 좁힌다.
169
+ def alimtalk_template_export(format: 'json', scope: nil, ksp_id: nil, status: nil, include_content: nil)
170
+ params = {
171
+ format: format,
172
+ scope: scope,
173
+ ksp_id: ksp_id,
174
+ status: status,
175
+ include_content: include_content
176
+ }.compact
177
+
178
+ return request_raw(uri: 'alimtalk/templates/export', params: params,
179
+ headers: { 'Bootpay-Role' => 'user' }) if format.to_s == 'csv'
180
+
181
+ request(
182
+ uri: 'alimtalk/templates/export',
183
+ method: :get,
184
+ headers: { 'Bootpay-Role' => 'user' },
185
+ params: params
186
+ )
187
+ end
188
+
189
+ # 이미지형 템플릿의 원본 이미지를 올린다 (POST /v1/alimtalk/templates/image)
190
+ # 돌려받은 image_url 을 템플릿 생성/수정의 storage_image_url 로 넘긴다.
191
+ # 규격을 업로드 **전에** 서버가 검사한다 — jpg/png · 500KB 이하 · 가로 500px 이상 · 2:1.
192
+ # image 는 파일 경로(String)·IO·HTTP::FormData::File 을 모두 받는다.
193
+ # replace_url 을 주면 업로드 성공 후에 기존 파일을 지운다.
194
+ def alimtalk_template_image(image:, replace_url: nil)
195
+ form = { 'image' => multipart_file(image) }
196
+ form['replace_url'] = multipart_value(replace_url) if replace_url.present?
197
+
198
+ post_multipart(uri: 'alimtalk/templates/image', form: form,
199
+ headers: { 'Bootpay-Role' => 'user' })
200
+ end
201
+
202
+ # 아이템리스트형의 하이라이트 썸네일을 올린다 (POST /v1/alimtalk/templates/highlight_image)
203
+ # ⚠️ 본문 이미지와 **규격이 다르다** — jpg/png · 500KB 이하 · 가로 **108px** 이상 · **1:1**.
204
+ # 본문 이미지 엔드포인트로 올리면 거부된다.
205
+ # 돌려받은 image_url 은 item_highlight.storage_image_url 로 넘긴다.
206
+ # ⚠️ 썸네일을 붙이면 하이라이트 글자 한도가 줄어든다(타이틀 30→21, 설명 19→13).
207
+ def alimtalk_template_highlight_image(image:, replace_url: nil)
208
+ form = { 'image' => multipart_file(image) }
209
+ form['replace_url'] = multipart_value(replace_url) if replace_url.present?
210
+
211
+ post_multipart(uri: 'alimtalk/templates/highlight_image', form: form,
212
+ headers: { 'Bootpay-Role' => 'user' })
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,84 @@
1
+ module BootpayStore::Concern::AlimtalkWebhook
2
+ extend ActiveSupport::Concern
3
+
4
+ # 알림톡 발송결과·검수결과 웹훅 설정 — /v1/alimtalk/webhook 계열
5
+ #
6
+ # ⚠️ **주문·구독 통합 웹훅과 완전히 별개다.** 알림톡 이벤트를 기존 주문 웹훅 URL 로 태우면
7
+ # 그 수신 서버가 모르는 payload 를 받아 기존 연동이 깨진다. 그래서 수신 URL 을 따로 둔다.
8
+ # (`send_test_webhook` 은 주문 웹훅용이다 — 이 파일의 `alimtalk_webhook_test` 와 혼동하지 말 것)
9
+ #
10
+ # ## 서명 검증
11
+ # 요청에 다음 헤더가 붙는다.
12
+ # X-Bootpay-Signature: sha256=HMAC_SHA256(secret, "{X-Bootpay-Timestamp}.{raw_body}")
13
+ # 타임스탬프가 5분 이상 지난 요청은 거부한다(replay 방지).
14
+ #
15
+ # @comment_by Claude (alfred)
16
+ # @date: 26-08-27
17
+ included do
18
+ # 웹훅 설정을 조회한다 (GET /v1/alimtalk/webhook)
19
+ # 시크릿은 앞 12자만 노출된다. 미설정이면 { configured: false } 로 온다.
20
+ def alimtalk_webhook_detail
21
+ request(
22
+ uri: 'alimtalk/webhook',
23
+ method: :get,
24
+ headers: { 'Bootpay-Role' => 'user' }
25
+ )
26
+ end
27
+
28
+ # 웹훅 설정을 저장한다 (PUT /v1/alimtalk/webhook)
29
+ # url 은 **https 만** 허용한다(아니면 3028). 최초 저장 시 서명 시크릿이 자동 발급된다.
30
+ # events: 구독할 이벤트 코드. 목록에 없는 값은 저장 시 조용히 버려진다(유령 구독 방지).
31
+ # 300 발송 접수(기본 미구독) / 301 전달 성공 / 302 전달 실패 / 303 예약 취소 /
32
+ # 304 문자(LMS) 대체발송 전환 / 310 검수 승인 / 311 검수 반려 / 320 수신거부 등록(기본 미구독)
33
+ # events 를 비우면 기본 구독셋(301·302·303·304·310·311)이 적용된다.
34
+ def alimtalk_webhook_update(url: nil, events: nil, enabled: nil)
35
+ request(
36
+ uri: 'alimtalk/webhook',
37
+ method: :put,
38
+ headers: { 'Bootpay-Role' => 'user' },
39
+ payload: {
40
+ url: url,
41
+ events: events,
42
+ enabled: enabled
43
+ }.compact
44
+ )
45
+ end
46
+
47
+ # 테스트 이벤트를 1건 발송한다 (POST /v1/alimtalk/webhook/test)
48
+ # ⚠️ **설정된 URL 로 실제 HTTP 요청이 나간다.** 구독 여부와 무관하게 보낸다.
49
+ # 웹훅이 설정돼 있지 않으면 3029. 응답: { delivery_id:, url:, queued: }
50
+ def alimtalk_webhook_test
51
+ request(
52
+ uri: 'alimtalk/webhook/test',
53
+ method: :post,
54
+ headers: { 'Bootpay-Role' => 'user' }
55
+ )
56
+ end
57
+
58
+ # 서명 시크릿을 재발급한다 (POST /v1/alimtalk/webhook/secret)
59
+ # ⚠️ **이 응답에서만 secret 원문을 돌려준다**(이후 조회는 마스킹된다).
60
+ # ⚠️ 이미 큐에 있는 전송 건은 발송 당시 시크릿으로 서명된다.
61
+ def alimtalk_webhook_rotate_secret
62
+ request(
63
+ uri: 'alimtalk/webhook/secret',
64
+ method: :post,
65
+ headers: { 'Bootpay-Role' => 'user' }
66
+ )
67
+ end
68
+
69
+ # 웹훅 전송 이력을 조회한다 (GET /v1/alimtalk/webhook/deliveries)
70
+ # 성공·실패를 모두 남긴다. 응답: { list: [{ delivery_id:, event:, event_code:, url:, status:,
71
+ # retry_count:, max_retry:, tags:, created_at: }], count:, page:, per: }
72
+ def alimtalk_webhook_deliveries(page: nil, limit: nil)
73
+ request(
74
+ uri: 'alimtalk/webhook/deliveries',
75
+ method: :get,
76
+ headers: { 'Bootpay-Role' => 'user' },
77
+ params: {
78
+ page: page,
79
+ limit: limit # 서버 기본 20, 최대 100
80
+ }.compact
81
+ )
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,102 @@
1
+ module BootpayStore::Concern::Invoice
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # 청구서를 생성한다
6
+ # Comment by GOSOMI
7
+ # @date: 2025-10-03
8
+ def request_checkout(sdk: false, idempotency_key: nil, name:, memo: nil, user: {}, products: [], price: 0, tax_free_price: 0, delivery_price: 0,
9
+ redirect_url: nil, request_id: nil, use_notification: false, use_auto_login: false, expired_at: nil, metadata: {},
10
+ webhook_url: nil, header_content_type: 'application/json', usage_api_url: nil, extra: {})
11
+ request(
12
+ uri: 'invoices',
13
+ method: :post,
14
+ headers: {
15
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
16
+ 'Bootpay-Role' => 'user'
17
+ },
18
+ payload:
19
+ {
20
+ sdk: sdk,
21
+ name: name,
22
+ memo: memo,
23
+ user: user,
24
+ products: products,
25
+ price: price,
26
+ tax_free_price: tax_free_price,
27
+ delivery_price: delivery_price,
28
+ redirect_url: redirect_url,
29
+ request_id: request_id,
30
+ use_notification: use_notification,
31
+ use_auto_login: use_auto_login,
32
+ expired_at: expired_at,
33
+ metadata: metadata,
34
+ webhook_url: webhook_url,
35
+ header_content_type: header_content_type,
36
+ usage_api_url: usage_api_url,
37
+ extra: extra
38
+ }.compact
39
+ )
40
+ end
41
+
42
+ alias :create_invoice :request_checkout
43
+
44
+ # 청구서 목록을 조회한다 (GET /v1/invoices)
45
+ # @comment_by Claude (alfred)
46
+ # @date: 26-08-14
47
+ # 응답은 { list: [...], count: N } 구조다 ({ items, total } 아님 — Node SDK 타입선언이 틀렸던 지점).
48
+ # 서버 기본 limit 은 24.
49
+ def invoice_list(page: 1, limit: 24, keyword: nil, cs_type: nil, user_id: nil,
50
+ product_type: nil, css_at: nil, cse_at: nil, idempotency_key: nil)
51
+ request(
52
+ uri: 'invoices',
53
+ method: :get,
54
+ headers: {
55
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
56
+ 'Bootpay-Role' => 'user'
57
+ },
58
+ params: {
59
+ page: page,
60
+ limit: limit,
61
+ keyword: keyword,
62
+ cs_type: cs_type,
63
+ user_id: user_id,
64
+ product_type: product_type,
65
+ css_at: css_at,
66
+ cse_at: cse_at
67
+ }.compact
68
+ )
69
+ end
70
+
71
+ # 청구서 상세를 조회한다 (GET /v1/invoices/:id)
72
+ # @comment_by Claude (alfred)
73
+ # @date: 26-08-14
74
+ def invoice_detail(invoice_id:, idempotency_key: nil)
75
+ request(
76
+ uri: "invoices/#{invoice_id}",
77
+ method: :get,
78
+ headers: {
79
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
80
+ 'Bootpay-Role' => 'user'
81
+ }
82
+ )
83
+ end
84
+
85
+ # 청구서를 재안내한다 (POST /v1/invoices/:id/notify)
86
+ # @comment_by Claude (alfred)
87
+ # @date: 26-08-14
88
+ # send_types 미전달 시 서버가 빈 배열로 처리한다.
89
+ # ⚠️ 실제 고객에게 알림이 발송되므로 테스트 호출 주의.
90
+ def invoice_notify(invoice_id:, send_types: nil, idempotency_key: nil)
91
+ request(
92
+ uri: "invoices/#{invoice_id}/notify",
93
+ method: :post,
94
+ headers: {
95
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
96
+ 'Bootpay-Role' => 'user'
97
+ },
98
+ payload: { send_types: send_types }.compact
99
+ )
100
+ end
101
+ end
102
+ end