bootpay 1.2.0 → 3.1.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -9
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +107 -21
  5. data/CODE_OF_CONDUCT.md +1 -1
  6. data/Gemfile +1 -3
  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 +22 -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 +62 -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 +92 -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/faq.rb +97 -0
  47. data/lib/bootpay_store/concern/inquiry.rb +138 -0
  48. data/lib/bootpay_store/concern/invoice.rb +102 -0
  49. data/lib/bootpay_store/concern/mall_setting.rb +385 -0
  50. data/lib/bootpay_store/concern/notice.rb +100 -0
  51. data/lib/bootpay_store/concern/order.rb +74 -0
  52. data/lib/bootpay_store/concern/order_subscription.rb +391 -0
  53. data/lib/bootpay_store/concern/payment.rb +119 -0
  54. data/lib/bootpay_store/concern/product.rb +175 -0
  55. data/lib/bootpay_store/concern/product_qna.rb +149 -0
  56. data/lib/bootpay_store/concern/product_review.rb +145 -0
  57. data/lib/bootpay_store/concern/rest.rb +139 -0
  58. data/lib/bootpay_store/concern/store.rb +31 -0
  59. data/lib/bootpay_store/concern/supervisor.rb +211 -0
  60. data/lib/bootpay_store/concern/token.rb +24 -0
  61. data/lib/bootpay_store/concern/user.rb +325 -0
  62. data/lib/bootpay_store/concern/user_group.rb +221 -0
  63. data/lib/bootpay_store/concern/webhook.rb +17 -0
  64. data/lib/bootpay_store/concern.rb +55 -0
  65. data/lib/bootpay_store/response.rb +14 -0
  66. data/lib/version.rb +9 -0
  67. metadata +82 -49
  68. data/.DS_Store +0 -0
  69. data/.env.example +0 -23
  70. data/lib/.DS_Store +0 -0
  71. data/lib/bootpay/.DS_Store +0 -0
  72. data/lib/bootpay/authentication.rb +0 -61
  73. data/lib/bootpay/automatic_transfer.rb +0 -56
  74. data/lib/bootpay/billing.rb +0 -157
  75. data/lib/bootpay/cancel.rb +0 -29
  76. data/lib/bootpay/cash_receipt.rb +0 -77
  77. data/lib/bootpay/commerce/cart.rb +0 -20
  78. data/lib/bootpay/commerce/category.rb +0 -39
  79. data/lib/bootpay/commerce/commerce_resource.rb +0 -196
  80. data/lib/bootpay/commerce/coupon.rb +0 -41
  81. data/lib/bootpay/commerce/invoice.rb +0 -46
  82. data/lib/bootpay/commerce/order.rb +0 -61
  83. data/lib/bootpay/commerce/order_cancel.rb +0 -52
  84. data/lib/bootpay/commerce/order_subscription.rb +0 -117
  85. data/lib/bootpay/commerce/order_subscription_adjustment.rb +0 -29
  86. data/lib/bootpay/commerce/order_subscription_bill.rb +0 -50
  87. data/lib/bootpay/commerce/order_subscription_request.rb +0 -60
  88. data/lib/bootpay/commerce/point.rb +0 -36
  89. data/lib/bootpay/commerce/product.rb +0 -63
  90. data/lib/bootpay/commerce/store.rb +0 -21
  91. data/lib/bootpay/commerce/user.rb +0 -75
  92. data/lib/bootpay/commerce/user_group.rb +0 -70
  93. data/lib/bootpay/easy.rb +0 -25
  94. data/lib/bootpay/escrow.rb +0 -24
  95. data/lib/bootpay/link.rb +0 -60
  96. data/lib/bootpay/naverpay.rb +0 -17
  97. data/lib/bootpay/payment_resource.rb +0 -31
  98. data/lib/bootpay/reseller.rb +0 -12
  99. data/lib/bootpay/rest.rb +0 -46
  100. data/lib/bootpay/submit.rb +0 -20
  101. data/lib/bootpay/token.rb +0 -33
  102. data/lib/bootpay/verification.rb +0 -55
  103. data/lib/bootpay/version.rb +0 -25
  104. data/lib/bootpay/wallet.rb +0 -56
  105. data/lib/bootpay_commerce.rb +0 -152
  106. /data/lib/{response.rb → bootpay/response.rb} +0 -0
@@ -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
@@ -0,0 +1,325 @@
1
+ module BootpayStore::Concern::User
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # 회원 로그인 (V1 API)
6
+ # Comment by Codex
7
+ # @date: 2026-02-23
8
+ # @date: 26-08-14 uri 'user/login' → 'users/login' 로 정정.
9
+ # v1 에는 단수 user/* 라우트가 없다(bin/rails routes 전수 확인). 로그인은 POST /v1/users/login (v1/users/login#create).
10
+ # ⚠️ POST /v1/users/session 은 resource :session 이 만들어낸 라우트일 뿐 sessions_controller 에 create 액션이 없다.
11
+ # 라우트 존재만 보고 그리로 보내면 안 된다 — show(GET)/destroy(DELETE) 만 정의돼 있다.
12
+ # ⚠️ 서버(LoginService)는 login_id·password 만 읽는다. corporate_type 은 전달돼도 무시된다.
13
+ # /mall/user/login 은 스토어프론트 스코프라 서버사이드 SDK(base=/v1)와 무관하다.
14
+ def user_login(login_id:, password:, corporate_type: 0, idempotency_key: nil)
15
+ request(
16
+ uri: 'users/login',
17
+ method: :post,
18
+ headers: {
19
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid
20
+ },
21
+ payload: {
22
+ login_id: login_id,
23
+ password: password,
24
+ corporate_type: corporate_type
25
+ }.compact
26
+ )
27
+ end
28
+
29
+ # 회원 세션 조회 (V1 Mall API)
30
+ # Comment by Codex
31
+ # @date: 2026-02-23
32
+ # @date: 26-08-14 uri 'user/session' → 'users/session' 로 정정 (GET /v1/users/session).
33
+ def user_session(user_jwt: nil, idempotency_key: nil)
34
+ request(
35
+ uri: 'users/session',
36
+ method: :get,
37
+ headers: {
38
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
39
+ 'Bootpay-User-JWT' => user_jwt
40
+ }.compact
41
+ )
42
+ end
43
+
44
+ # 회원 로그아웃 (V1 Mall API)
45
+ # Comment by Codex
46
+ # @date: 2026-02-23
47
+ # @date: 26-08-14 uri 'user/session' → 'users/session' 로 정정 (DELETE /v1/users/session).
48
+ def user_logout(user_jwt:, idempotency_key: nil)
49
+ request(
50
+ uri: 'users/session',
51
+ method: :delete,
52
+ headers: {
53
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
54
+ 'Bootpay-User-JWT' => user_jwt
55
+ }
56
+ )
57
+ end
58
+
59
+ # 회원가입 (V1 API) — 일반 회원가입용
60
+ # Comment by Codex
61
+ # @date: 2026-02-23
62
+ # @date: 26-08-14 uri 'user/join' → 'users/join' 로 정정 (POST /v1/users/join).
63
+ # ⚠️ external_user_sign_in 과 같은 엔드포인트를 부른다. 중복이 아니라 용도가 다르다 —
64
+ # 이쪽은 password/corporate_type/group 을 쓰는 일반 회원가입, 저쪽은 uid/login_email/login_pw 를 쓰는 외부 uid 연동 가입이다.
65
+ # 서버가 파라미터 조합으로 분기하므로 둘 다 유지한다(26-08-14 사용자 결정). 매뉴얼 customer/register.md 는 external_user_sign_in 에 대응.
66
+ def user_join(login_id:, password:, name:, email: nil, phone: nil, nickname: nil, gender: nil, birth: nil, corporate_type: 0,
67
+ group: nil, idempotency_key: nil)
68
+ request(
69
+ uri: 'users/join',
70
+ method: :post,
71
+ headers: {
72
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid
73
+ },
74
+ payload: {
75
+ login_id: login_id,
76
+ password: password,
77
+ name: name,
78
+ email: email,
79
+ phone: phone,
80
+ nickname: nickname,
81
+ gender: gender,
82
+ birth: birth,
83
+ corporate_type: corporate_type,
84
+ group: group
85
+ }.compact
86
+ )
87
+ end
88
+
89
+ # 회원가입 중복 확인 (V1 API) — key 를 인자로 받는 일반형
90
+ # type: email-exist, id-exist, phone-exist, uid-exist, group-business-number-exist
91
+ # Comment by Codex
92
+ # @date: 2026-02-23
93
+ # @date: 26-08-14 uri 'user/join/#{type}' → 'users/join/#{type}' 로 정정 (GET /v1/users/join/:id).
94
+ # ⚠️ email_exist·id_exist·phone_exist·uid_exist·group_business_number_exist 전용형 5종과 기능이 겹치지만 둘 다 유지한다(26-08-14 사용자 결정).
95
+ # 일반형은 서버에 새 key 가 생겨도 SDK 수정 없이 쓸 수 있고, 매뉴얼 customer/check-exist.md 가 key/value 형태를 안내하므로 이쪽에 대응한다.
96
+ def user_join_check(type:, pk:, idempotency_key: nil)
97
+ request(
98
+ uri: "users/join/#{type}",
99
+ method: :get,
100
+ headers: {
101
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid
102
+ },
103
+ params: {
104
+ pk: pk
105
+ }
106
+ )
107
+ end
108
+
109
+ # UserId로 로그인을 시도한다
110
+ # Comment by GOSOMI
111
+ # @date: 2025-04-25
112
+ def login_by_user_id(user_id:, membership_type: 'member', corporate_type: 'individual', idempotency_key: nil)
113
+ request(
114
+ uri: "users/login/token",
115
+ headers: {
116
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
117
+ 'Bootpay-Role' => 'user'
118
+ },
119
+ payload:
120
+ {
121
+ user_id: user_id,
122
+ membership_type: membership_type,
123
+ corporate_type: corporate_type
124
+ }
125
+ )
126
+ end
127
+
128
+ # 회원 목록 정보를 가져온다
129
+ # Comment by GOSOMI
130
+ # @date: 2025-06-16
131
+ # @date: 26-08-26 회원등급 필터 키 정정. 서버(v1/users_controller#index)가 읽는 이름은
132
+ # membership_type 인데 SDK 는 member_type 을 보내고 있어 등급 필터가 조용히 무시됐다
133
+ # (에러 없이 전체 목록이 돌아온다). 기존 호출 호환을 위해 member_type 인자는 남기고 별칭으로 매핑한다.
134
+ def users(membership_type: nil, member_type: nil, keyword: nil, page: 1, limit: 20, idempotency_key: nil)
135
+ request(
136
+ uri: 'users',
137
+ method: :get,
138
+ headers: {
139
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
140
+ 'Bootpay-Role' => 'user'
141
+ },
142
+ params:
143
+ {
144
+ membership_type: membership_type.presence || member_type,
145
+ keyword: keyword,
146
+ page: page,
147
+ limit: limit
148
+ }.compact
149
+ )
150
+ end
151
+
152
+ # 회원정보를 조회한다
153
+ # Comment by GOSOMI
154
+ # @date: 2025-06-16
155
+ def lookup_user(user_id:, idempotency_key: nil)
156
+ request(
157
+ uri: "users/#{user_id}",
158
+ method: :get,
159
+ headers: {
160
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
161
+ }
162
+ )
163
+ end
164
+
165
+ # 회원가입
166
+ # Comment by GOSOMI
167
+ # @date: 2025-06-16
168
+ def external_user_sign_in(idempotency_key: nil, uid:, user_group_id: nil, is_group_admin: false,
169
+ name:, phone:, email:, tel: nil, nickname: nil, comment: nil,
170
+ gender: nil, birth: nil, individual_extension: nil, login_id:, login_email:,
171
+ login_pw: nil, join_at: nil)
172
+ request(
173
+ uri: 'users/join',
174
+ method: :post,
175
+ headers: {
176
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
177
+ 'Bootpay-Role' => 'user'
178
+ },
179
+ payload:
180
+ {
181
+ uid: uid,
182
+ user_group_id: user_group_id,
183
+ is_group_admin: is_group_admin,
184
+ name: name,
185
+ phone: phone,
186
+ email: email,
187
+ tel: tel,
188
+ nickname: nickname,
189
+ comment: comment,
190
+ gender: gender,
191
+ birth: birth,
192
+ individual_extension: individual_extension,
193
+ login_id: login_id,
194
+ login_email: login_email,
195
+ login_pw: login_pw,
196
+ join_at: join_at
197
+ }.compact
198
+ )
199
+ end
200
+
201
+ # 회원 탈퇴
202
+ # Comment by GOSOMI
203
+ # @date: 2025-06-16
204
+ def withdraw(user_id:, idempotency_key: nil)
205
+ request(
206
+ uri: "users/#{user_id}",
207
+ method: :delete,
208
+ headers: {
209
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
210
+ 'Bootpay-Role' => 'user'
211
+ }
212
+ )
213
+ end
214
+
215
+ # 이메일 중복검사
216
+ # Comment by GOSOMI
217
+ # @date: 2025-06-18
218
+ def email_exist(email:, idempotency_key: nil)
219
+ request(
220
+ uri: 'users/join/email-exist',
221
+ method: :get,
222
+ headers: {
223
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
224
+ 'Bootpay-Role' => 'user'
225
+ },
226
+ params: { pk: email }
227
+ )
228
+ end
229
+
230
+ # ID 중복검사
231
+ # Comment by GOSOMI
232
+ # @date: 2025-06-18
233
+ def id_exist(id:, idempotency_key: nil)
234
+ request(
235
+ uri: 'users/join/id-exist',
236
+ method: :get,
237
+ headers: {
238
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
239
+ 'Bootpay-Role' => 'user'
240
+ },
241
+ params: { pk: id }
242
+ )
243
+ end
244
+
245
+ # 전화번호 중복검사
246
+ # Comment by GOSOMI
247
+ # @date: 2025-06-18
248
+ def phone_exist(phone:, idempotency_key: nil)
249
+ request(
250
+ uri: 'users/join/phone-exist',
251
+ method: :get,
252
+ headers: {
253
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
254
+ 'Bootpay-Role' => 'user'
255
+ },
256
+ params: { pk: phone }
257
+ )
258
+ end
259
+
260
+ # 그룹 사업자 번호 중복검사
261
+ # Comment by GOSOMI
262
+ # @date: 2025-06-18
263
+ def group_business_number_exist(business_number:, idempotency: nil)
264
+ request(
265
+ uri: 'users/join/group-business-number-exist',
266
+ method: :get,
267
+ headers: {
268
+ 'Idempotency-Key' => idempotency.presence || SecureRandom.uuid,
269
+ 'Bootpay-Role' => 'user'
270
+ },
271
+ params: { pk: business_number }
272
+ )
273
+ end
274
+
275
+ # 외부 uid(ex_uid) 중복검사 (GET /v1/users/join/uid-exist)
276
+ # @comment_by Claude (alfred)
277
+ # @date: 26-08-14
278
+ # email/id/phone/group-business-number 전용형과 같은 패턴. 이걸로 *_exist 계열이 5종 완성된다.
279
+ def uid_exist(uid:, idempotency_key: nil)
280
+ request(
281
+ uri: 'users/join/uid-exist',
282
+ method: :get,
283
+ headers: {
284
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
285
+ 'Bootpay-Role' => 'user'
286
+ },
287
+ params: { pk: uid }
288
+ )
289
+ end
290
+
291
+ # 회원 정보를 수정한다 (PUT /v1/users/:id)
292
+ # @comment_by Claude (alfred)
293
+ # @date: 26-08-14
294
+ # 컨트롤러 build_user_params 기준. 사업자 정보는 group: { company_name:, business_number:, registration_number: } 로 중첩 전달.
295
+ # 바뀐 값만 보내면 된다.
296
+ def user_update(user_id:, login_id: nil, login_pw: nil, name: nil, phone: nil, email: nil,
297
+ tel: nil, nickname: nil, bank_username: nil, bank_account: nil, bank_code: nil,
298
+ comment: nil, gender: nil, birth: nil, group: nil, idempotency_key: nil, **attrs)
299
+ request(
300
+ uri: "users/#{user_id}",
301
+ method: :put,
302
+ headers: {
303
+ 'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
304
+ 'Bootpay-Role' => 'user'
305
+ },
306
+ payload: {
307
+ login_id: login_id,
308
+ login_pw: login_pw,
309
+ name: name,
310
+ phone: phone,
311
+ email: email,
312
+ tel: tel,
313
+ nickname: nickname,
314
+ bank_username: bank_username,
315
+ bank_account: bank_account,
316
+ bank_code: bank_code,
317
+ comment: comment,
318
+ gender: gender,
319
+ birth: birth,
320
+ group: group
321
+ }.merge(attrs).compact
322
+ )
323
+ end
324
+ end
325
+ end