bootpay 3.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/Gemfile +1 -1
- data/bootpay.gemspec +0 -1
- data/lib/bootpay/concern/token.rb +8 -8
- data/lib/bootpay_store/bootpay-store-rest-client.rb +15 -0
- data/lib/bootpay_store/concern/alimtalk_message.rb +4 -4
- data/lib/bootpay_store/concern/alimtalk_official.rb +4 -4
- data/lib/bootpay_store/concern/alimtalk_optout.rb +5 -5
- data/lib/bootpay_store/concern/alimtalk_send.rb +17 -8
- data/lib/bootpay_store/concern/alimtalk_sender.rb +8 -8
- data/lib/bootpay_store/concern/alimtalk_template.rb +11 -11
- data/lib/bootpay_store/concern/alimtalk_webhook.rb +6 -6
- data/lib/bootpay_store/concern/faq.rb +97 -0
- data/lib/bootpay_store/concern/inquiry.rb +138 -0
- data/lib/bootpay_store/concern/notice.rb +100 -0
- data/lib/bootpay_store/concern/product_qna.rb +149 -0
- data/lib/bootpay_store/concern/product_review.rb +145 -0
- data/lib/bootpay_store/concern/rest.rb +14 -3
- data/lib/bootpay_store/concern.rb +10 -0
- data/lib/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c451a531a23a1a127987e92afb1980687ebd31a484326da574520dc41e78da87
|
|
4
|
+
data.tar.gz: 0d0e6b71cd59143809355360bedd0f9f43da13a6a02cf9c9d8952edbecb6c7d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ff9f2a74b66f0644633b605edff8e440ea29546cffba5cf7432597ea5b6ebbf8172cdc1598989b64d48c3bfd5091cbe9cec4f048965ccd032e93328faf5a4f6
|
|
7
|
+
data.tar.gz: 9ef546f66306f41e58e8fcaffc5ac1595eab60140056a1552d8a0f7b78ecd27c6c7843803fb1015149d4b86ac98a22d3350be6e1146eccd42514cedf4a146c41
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
### 3.1.0
|
|
2
|
+
|
|
3
|
+
- 커머스 게시판 API 28종 SDK 메서드 추가 — FAQ · 공지사항 · 1:1 문의 · 상품문의(Q&A) · 상품평.
|
|
4
|
+
`BootpayStore::RestClient` 에 아래 메서드가 생긴다.
|
|
5
|
+
- FAQ(5): `faqs` `faq_detail` `faq_create` `faq_update` `faq_delete`
|
|
6
|
+
- 공지사항(5): `notices` `notice_detail` `notice_create` `notice_update` `notice_delete`
|
|
7
|
+
- 1:1 문의(6): `inquiries` `inquiry_detail` `inquiry_create` `inquiry_update` `inquiry_delete` `inquiry_answer`
|
|
8
|
+
- 상품문의(6): `product_qnas` `product_qna_detail` `product_qna_create` `product_qna_update` `product_qna_delete` `product_qna_answer`
|
|
9
|
+
- 상품평(6): `reviews` `review_detail` `review_create` `review_update` `review_delete` `review_reply`
|
|
10
|
+
공통 규칙: 목록·단건·삭제 조회 계열은 `supervisor: true` 를 주면 운영자 모드(`Bootpay-Role: supervisor`)로
|
|
11
|
+
비공개·숨김 글까지 다루고, 기본값은 고객 모드다. 등록·수정 계열 중 `faq_*` · `notice_*` 와
|
|
12
|
+
`*_answer` / `review_reply` 는 운영자 전용이다. 회원 대상 메서드는 `user_id` · `login_id` · `user_jwt`
|
|
13
|
+
중 하나로 회원을 지정하며, `product_qna_create` 는 몰이 허용할 때 `guest_name` + `guest_password` 로
|
|
14
|
+
비회원 작성도 된다. 모든 메서드가 `idempotency_key` 를 받고 안 주면 UUID 를 자동 생성한다.
|
|
15
|
+
기존 상품 상세의 공개 상품평 목록(`GET /v1/products/:id/reviews`)은 그대로 쓴다.
|
|
16
|
+
- `alimtalk_send` / `alimtalk_send_bulk` 에 `webhook_url` 인자 추가(선택). 주면 그 건(bulk 는 요청 전체)의
|
|
17
|
+
발송 성공·실패·문자 대체발송·예약취소 웹훅이 **이 주소로만** 가고 프로젝트 웹훅 설정은 쓰이지 않는다.
|
|
18
|
+
`https` 만 허용하며 2,000자를 넘으면 `3028` 로 거부된다(bulk 는 요청 전체가 거부). 서명은 프로젝트
|
|
19
|
+
시크릿으로 하며, 시크릿만 필요하면 `alimtalk_webhook_rotate_secret` 으로 설정 없이 발급받을 수 있다.
|
|
20
|
+
⚠️ 같은 `ref_id` 로 이미 접수·성공한 건을 다시 요청하면 기존 접수가 그대로 돌아와 새 주소는 무시된다.
|
|
21
|
+
기본값이 `nil` 이라 기존 호출은 그대로 동작한다.
|
|
22
|
+
- 알림톡 API(`alimtalk_*` 메서드 35종) 요청 호스트 변경 — `/alimtalk/*` 경로는 커머스 API(`api.bootapi.com/v1`)가
|
|
23
|
+
아니라 메시지 API(`message.bootapi.com`, 경로에 `/v1` 없음)로 보낸다. development 는 `dev-m.bootapi.com`,
|
|
24
|
+
stage 는 `stage-m.bootapi.com`. 경로·파라미터·응답은 그대로이며 메서드 시그니처 변경 없음.
|
|
25
|
+
옛 주소(`api.bootapi.com/v1/alimtalk/*`)는 410 으로 응답한다.
|
|
26
|
+
- `BootpayStore::RestClient::MESSAGE_API` 상수와 `set_message_api_url(url)` 추가 — 알림톡 호스트만 따로 바꿀 수 있다.
|
|
27
|
+
그 외 API 는 기존처럼 `API` / `set_api_url` 을 쓴다.
|
|
28
|
+
|
|
29
|
+
### 3.0.1
|
|
30
|
+
|
|
31
|
+
`bootpay` 이름으로 낸 3.0.0 을 실제 배포본으로 점검하다 나온 수정 2건. **API 변경 없음.**
|
|
32
|
+
|
|
33
|
+
- `basic_or_request_access_token` 이 항상 `Bootpay::Response` 를 돌려준다.
|
|
34
|
+
client_key/secret_key 를 쓰면 Basic Auth 라 토큰 요청이 필요 없는데, 그 분기에서
|
|
35
|
+
`success?` 만 정의된 **맨 `Object`** 를 돌려주고 있었다. 다른 메서드처럼 `.data` 를 부르면
|
|
36
|
+
`NoMethodError` 로 죽는다. `success?` 동작은 그대로다(둘 다 true).
|
|
37
|
+
- gemspec 의 `required_ruby_version` 제거. 3.0.0 에서 `>= 2.6.0` 으로 선언했는데 **근거 없는
|
|
38
|
+
추정값**이었다 — 실제 의존성 체인은 그보다 높은 Ruby 를 요구한다. 정확한 하한을 확인하기 전까지
|
|
39
|
+
잘못된 값을 박아 두는 대신 선언하지 않는다(3.0.0 이전과 같은 상태).
|
|
40
|
+
- Gemfile 주석의 옛 gemspec 파일명 정정.
|
|
41
|
+
|
|
42
|
+
검증(development, 읽기 전용): PG 토큰·Commerce 토큰·상품 조회·알림톡 수신거부/발신프로필 조회가
|
|
43
|
+
모두 도메인 응답으로 성공. 알림톡 메서드 35개 로드 확인.
|
|
44
|
+
|
|
1
45
|
### 3.0.0
|
|
2
46
|
|
|
3
47
|
2025-03 부터 alpha.1~alpha.4 로 쌓아 온 3.0 라인을 **정식 릴리스**한다.
|
data/Gemfile
CHANGED
data/bootpay.gemspec
CHANGED
|
@@ -12,7 +12,6 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = "부트페이 공식 Ruby 서버사이드 모듈입니다. 결제조회, 취소, 빌링키 결제시 사용됩니다."
|
|
13
13
|
spec.license = "MIT"
|
|
14
14
|
spec.homepage = "https://www.bootpay.ai"
|
|
15
|
-
spec.required_ruby_version = ">= 2.6.0"
|
|
16
15
|
|
|
17
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
17
|
spec.metadata["source_code_uri"] = "https://github.com/bootpay/backend-ruby/tree/2-x-development"
|
|
@@ -17,17 +17,17 @@ module Bootpay::Concern::Token
|
|
|
17
17
|
response
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
# client_key/secret_key 를 쓰면 Basic Auth 라 토큰 발급이 필요 없다.
|
|
21
|
+
# 그 경우 요청 없이 성공 응답을 돌려주고, application_id/private_key 면 실제로 토큰을 받는다.
|
|
22
|
+
#
|
|
23
|
+
# ⚠️ 반환 타입은 항상 Bootpay::Response 다. 종전에는 client_key 분기에서 success? 만 정의된
|
|
24
|
+
# 맨 Object 를 돌려줘, 다른 메서드처럼 `.data` 를 부르면 NoMethodError 로 죽었다.
|
|
21
25
|
# Comment by GOSOMI
|
|
22
26
|
# @date: 2026-03-11
|
|
23
27
|
def basic_or_request_access_token
|
|
24
|
-
if @use_client_key
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
else
|
|
29
|
-
request_access_token
|
|
30
|
-
end
|
|
28
|
+
return Bootpay::Response.new(true, {}) if @use_client_key
|
|
29
|
+
|
|
30
|
+
request_access_token
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -18,6 +18,15 @@ module BootpayStore
|
|
|
18
18
|
production: 'https://api.bootapi.com/v1'
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
# 알림톡 API 전용 — 메시지 API 가 직접 받는다(경로에 /v1 없음). concern/rest.rb#api_base_url 이 고른다.
|
|
22
|
+
# @date: 26-09-18
|
|
23
|
+
MESSAGE_API =
|
|
24
|
+
{
|
|
25
|
+
development: 'https://dev-m.bootapi.com',
|
|
26
|
+
stage: 'https://stage-m.bootapi.com',
|
|
27
|
+
production: 'https://message.bootapi.com'
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
SDK_VERSION = '5.0.0'
|
|
22
31
|
|
|
23
32
|
def initialize(client_key: nil, private_key: nil, server_key: nil, secret_key: nil, mode: 'production')
|
|
@@ -36,6 +45,12 @@ module BootpayStore
|
|
|
36
45
|
API[@mode.to_sym] = url
|
|
37
46
|
end
|
|
38
47
|
|
|
48
|
+
# 알림톡(메시지 API) URL을 변경
|
|
49
|
+
# @date: 26-09-18
|
|
50
|
+
def set_message_api_url(url)
|
|
51
|
+
MESSAGE_API[@mode.to_sym] = url
|
|
52
|
+
end
|
|
53
|
+
|
|
39
54
|
# API 버전을 설정한다
|
|
40
55
|
# Comment by Gosomi
|
|
41
56
|
# Date: 2022-07-29
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkMessage
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 알림톡 발송내역·집계 — GET /
|
|
4
|
+
# 알림톡 발송내역·집계 — GET /alimtalk/messages 계열
|
|
5
5
|
#
|
|
6
6
|
# **유료** 알림톡만 조회된다(무료 커머스 알림톡은 포함되지 않는다).
|
|
7
7
|
# 상태는 벤더 결과 동기화로 확정되므로 접수 직후에는 requested 로 보인다.
|
|
@@ -9,7 +9,7 @@ module BootpayStore::Concern::AlimtalkMessage
|
|
|
9
9
|
# @comment_by Claude (alfred)
|
|
10
10
|
# @date: 26-08-27
|
|
11
11
|
included do
|
|
12
|
-
# 발송내역 목록을 조회한다 (GET /
|
|
12
|
+
# 발송내역 목록을 조회한다 (GET /alimtalk/messages)
|
|
13
13
|
# status: requested·success·failed·canceled
|
|
14
14
|
# to: 수신번호(하이픈 무관, 정확 매칭) / ref_id: 발송 시 넘긴 멱등키
|
|
15
15
|
# ⚠️ 기간 기본값은 최근 30일이고 최대 조회 폭은 92일이다 — 초과분은 거부하지 않고 시작일을 당겨 잘라낸다.
|
|
@@ -34,7 +34,7 @@ module BootpayStore::Concern::AlimtalkMessage
|
|
|
34
34
|
)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
# 기간 집계를 조회한다 (GET /
|
|
37
|
+
# 기간 집계를 조회한다 (GET /alimtalk/messages/stats)
|
|
38
38
|
# 일자별 집계 원장에서 읽으므로 응답이 빠르다.
|
|
39
39
|
# 응답: { period:, totals: { sent, success, failed, fallback, opted_out_hit, rejected, canceled, success_rate },
|
|
40
40
|
# daily: [...], billing: { billable_count, unit_price, fallback_count, ..., amount } }
|
|
@@ -52,7 +52,7 @@ module BootpayStore::Concern::AlimtalkMessage
|
|
|
52
52
|
)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
# 단건 발송 결과를 조회한다 (GET /
|
|
55
|
+
# 단건 발송 결과를 조회한다 (GET /alimtalk/messages/:receipt_id)
|
|
56
56
|
# 실패 사유는 error_code·error_message 에 담긴다.
|
|
57
57
|
# fallback_type 은 폴백이 꺼진 건이면 null, 켜진 건이면 LMS 다.
|
|
58
58
|
# 다른 프로젝트의 건이거나 없으면 404(3025).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkOfficial
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 부트페이 공식 알림톡 템플릿 카탈로그 — GET/POST /
|
|
4
|
+
# 부트페이 공식 알림톡 템플릿 카탈로그 — GET/POST /alimtalk/official 계열
|
|
5
5
|
#
|
|
6
6
|
# 부트페이가 미리 카카오 승인을 받아 둔 템플릿이라, 그룹키가 등록된 채널이면 **검수 없이 즉시 발송**된다.
|
|
7
7
|
# `alimtalk_sender_create` 로 채널을 등록하면 그룹 등록이 함께 끝나므로 따로 채택할 것이 없다.
|
|
@@ -12,7 +12,7 @@ module BootpayStore::Concern::AlimtalkOfficial
|
|
|
12
12
|
# @comment_by Claude (alfred)
|
|
13
13
|
# @date: 26-08-27
|
|
14
14
|
included do
|
|
15
|
-
# 공식 템플릿을 검색한다 (GET /
|
|
15
|
+
# 공식 템플릿을 검색한다 (GET /alimtalk/official)
|
|
16
16
|
# keyword 는 본문·이름·분류를 부분일치(대소문자 무시)로 훑는다.
|
|
17
17
|
# msg_type 은 BA(기본형)·EX(부가정보형)만 존재한다 — 그룹 템플릿이라 AD/MI 는 쓸 수 없다.
|
|
18
18
|
# ksp_id 를 주면 그 채널의 변수 예문 사전으로 variable_examples 를 채워 준다(표시용).
|
|
@@ -33,7 +33,7 @@ module BootpayStore::Concern::AlimtalkOfficial
|
|
|
33
33
|
)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
# 보내려는 문구로 공식 템플릿을 추천받는다 (POST /
|
|
36
|
+
# 보내려는 문구로 공식 템플릿을 추천받는다 (POST /alimtalk/official/recommend)
|
|
37
37
|
# 유사도 score(0~1) 내림차순으로 돌려준다.
|
|
38
38
|
def alimtalk_official_recommend(text:, category: nil, limit: nil, ksp_id: nil)
|
|
39
39
|
request(
|
|
@@ -49,7 +49,7 @@ module BootpayStore::Concern::AlimtalkOfficial
|
|
|
49
49
|
)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
# 공식 템플릿 상세를 조회한다 (GET /
|
|
52
|
+
# 공식 템플릿 상세를 조회한다 (GET /alimtalk/official/:code)
|
|
53
53
|
# code 는 서버 채번 코드(슬래시를 포함하지 않는다). 없거나 미노출이면 404(3015).
|
|
54
54
|
def alimtalk_official_detail(code:, ksp_id: nil)
|
|
55
55
|
request(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkOptout
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 알림톡 수신거부 — /
|
|
4
|
+
# 알림톡 수신거부 — /alimtalk/optouts 계열 (가맹점 CRM 수신거부 동기화용)
|
|
5
5
|
#
|
|
6
6
|
# 발송 판정과 **같은 기준**으로 다룬다 — 부트페이 전역(global) + 내 프로젝트.
|
|
7
7
|
# ⚠️ 전역 건은 **조회는 되지만 해제할 수 없다**(releasable: false).
|
|
@@ -10,7 +10,7 @@ module BootpayStore::Concern::AlimtalkOptout
|
|
|
10
10
|
# @comment_by Claude (alfred)
|
|
11
11
|
# @date: 26-08-27
|
|
12
12
|
included do
|
|
13
|
-
# 수신거부 목록을 조회한다 (GET /
|
|
13
|
+
# 수신거부 목록을 조회한다 (GET /alimtalk/optouts)
|
|
14
14
|
# phone 은 숫자만 남겨 **부분일치**로 찾는다(정확 매칭이 아니다). 50건 단위로 페이징된다.
|
|
15
15
|
# 응답: { list: [{ id:, phone:, scope:, global:, releasable:, source:, reason:, opted_out_at:, created_at: }],
|
|
16
16
|
# count:, page: }
|
|
@@ -26,7 +26,7 @@ module BootpayStore::Concern::AlimtalkOptout
|
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# 수신거부를 등록한다 (POST /
|
|
29
|
+
# 수신거부를 등록한다 (POST /alimtalk/optouts)
|
|
30
30
|
# 내 프로젝트 스코프로 등록된다(source: api). 같은 번호를 다시 등록해도 멱등이다.
|
|
31
31
|
def alimtalk_optout_create(phone:, reason: nil)
|
|
32
32
|
request(
|
|
@@ -40,7 +40,7 @@ module BootpayStore::Concern::AlimtalkOptout
|
|
|
40
40
|
)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# 발송 전에 수신거부를 사전 확인한다 (POST /
|
|
43
|
+
# 발송 전에 수신거부를 사전 확인한다 (POST /alimtalk/optouts/check)
|
|
44
44
|
# 발송 판정과 **같은 축**으로 대조하므로, 벌크에서 skipped 로 낭비될 건을 미리 뺄 수 있다.
|
|
45
45
|
# 단건(phone)·다건(phones) 모두 받는다. ⚠️ 1회 최대 1,000건이고 넘으면 -48 이다(중복은 서버가 제거).
|
|
46
46
|
# 응답: { list: [{ phone:, opted_out:, global:, releasable:, opted_out_at: }], count:, opted_out_count: }
|
|
@@ -56,7 +56,7 @@ module BootpayStore::Concern::AlimtalkOptout
|
|
|
56
56
|
)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
# 수신거부를 해제한다 (DELETE /
|
|
59
|
+
# 수신거부를 해제한다 (DELETE /alimtalk/optouts/:phone)
|
|
60
60
|
# 내 프로젝트 스코프 건만 해제되며 멱등이다(없어도 성공).
|
|
61
61
|
# ⚠️ 전역 차단은 해제되지 않고 global_blocked: true 로 알려 준다 —
|
|
62
62
|
# "지웠는데 여전히 막히는" 상태를 응답으로 드러내기 위함이다.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkSend
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 알림톡 발송 — POST /
|
|
4
|
+
# 알림톡 발송 — POST /alimtalk/send · /send/bulk · DELETE /send/:receipt_id
|
|
5
5
|
#
|
|
6
6
|
# ⚠️ **실제로 카카오톡이 발송되고 과금된다. 샌드박스가 없다.**
|
|
7
7
|
#
|
|
@@ -18,16 +18,21 @@ module BootpayStore::Concern::AlimtalkSend
|
|
|
18
18
|
# @comment_by Claude (alfred)
|
|
19
19
|
# @date: 26-08-27
|
|
20
20
|
included do
|
|
21
|
-
# 단건 발송 (POST /
|
|
21
|
+
# 단건 발송 (POST /alimtalk/send)
|
|
22
22
|
# variables: { company_name: '부트페이몰', user_name: '홍길동' } 형태의 치환값
|
|
23
23
|
# ref_id: 가맹점 발송 식별자 — **멱등 키**로 쓰인다
|
|
24
24
|
# reserved_at: 예약 발송 시각(ISO8601). 생략하면 즉시 발송
|
|
25
25
|
# fallback: 알림톡 실패 시 문자(LMS) 대체발송 여부.
|
|
26
26
|
# ⚠️ **미지정(nil)과 false 는 다르다** — nil 이면 프로젝트 기본값을 따르고, false 는 명시적으로 끈다.
|
|
27
27
|
# 켜면 발신번호가 등록돼 있어야 하며 없으면 3030 으로 거부된다. 대체 문자에는 수신거부 링크가 자동 포함된다.
|
|
28
|
+
# webhook_url: 이 건의 결과 웹훅을 받을 주소(26-09-21).
|
|
29
|
+
# 주면 발송 성공·실패·문자 대체발송·예약취소 웹훅이 **이 주소로만** 간다(프로젝트 웹훅 설정은 쓰이지 않는다).
|
|
30
|
+
# https 만 허용하며 2,000자를 넘으면 3028 로 거부된다. 서명은 프로젝트 시크릿으로 하고,
|
|
31
|
+
# 시크릿만 필요하면 alimtalk_webhook_rotate_secret 으로 설정 없이 발급받을 수 있다.
|
|
32
|
+
# ⚠️ 같은 ref_id 로 이미 접수·성공한 건을 다시 요청하면 기존 접수가 그대로 돌아와 새 주소는 무시된다.
|
|
28
33
|
# 응답: { receipt_id:, ref_id:, to:, status: } — 접수 직후 status 는 requested
|
|
29
34
|
def alimtalk_send(template_code:, to:, variables: nil, ref_id: nil, fallback: nil,
|
|
30
|
-
reserved_at: nil, sender_key: nil, user_id: nil)
|
|
35
|
+
reserved_at: nil, sender_key: nil, user_id: nil, webhook_url: nil)
|
|
31
36
|
request(
|
|
32
37
|
uri: 'alimtalk/send',
|
|
33
38
|
method: :post,
|
|
@@ -40,21 +45,24 @@ module BootpayStore::Concern::AlimtalkSend
|
|
|
40
45
|
fallback: fallback, # compact 은 nil 만 걷어내므로 false 는 그대로 전달된다
|
|
41
46
|
reserved_at: reserved_at,
|
|
42
47
|
sender_key: sender_key,
|
|
43
|
-
user_id: user_id
|
|
48
|
+
user_id: user_id,
|
|
49
|
+
webhook_url: webhook_url
|
|
44
50
|
}.compact
|
|
45
51
|
)
|
|
46
52
|
end
|
|
47
53
|
|
|
48
|
-
# 벌크 발송 (POST /
|
|
54
|
+
# 벌크 발송 (POST /alimtalk/send/bulk) — 1요청 = N수신자
|
|
49
55
|
# recipients: [{ to: '01012345678', ref_id: 'bulk-0001', variables: { ... } }, ...]
|
|
50
56
|
# ⚠️ 수신자 수만큼 실제 발송되고 과금된다.
|
|
51
57
|
# - 쿼터를 넘으면 요청 시점에 **전체 거부**된다(3022) — 일부만 나가지 않는다.
|
|
52
58
|
# - 개별 수신자의 실패는 건별 rejected 로 표시되고 나머지는 정상 발송된다.
|
|
53
59
|
# - 수신거부 번호는 skipped 이며 **과금되지 않고 발송 기록도 만들지 않는다**.
|
|
54
60
|
# - fallback 은 요청 단위로 한 번만 판정한다 — 발신번호가 없으면 요청 전체가 3030 으로 거부된다.
|
|
61
|
+
# - webhook_url 도 요청 단위 하나다 — 이 요청으로 나간 모든 수신자 건의 결과 웹훅이 그 주소로 간다.
|
|
62
|
+
# 형식이 틀리면(https 아님·2,000자 초과) 요청 전체가 3028 로 거부된다(26-09-21).
|
|
55
63
|
# 응답: { count:, requested:, skipped:, rejected:, receipts: [...] }
|
|
56
64
|
def alimtalk_send_bulk(template_code:, recipients:, fallback: nil, reserved_at: nil,
|
|
57
|
-
sender_key: nil, user_id: nil)
|
|
65
|
+
sender_key: nil, user_id: nil, webhook_url: nil)
|
|
58
66
|
request(
|
|
59
67
|
uri: 'alimtalk/send/bulk',
|
|
60
68
|
method: :post,
|
|
@@ -65,12 +73,13 @@ module BootpayStore::Concern::AlimtalkSend
|
|
|
65
73
|
fallback: fallback,
|
|
66
74
|
reserved_at: reserved_at,
|
|
67
75
|
sender_key: sender_key,
|
|
68
|
-
user_id: user_id
|
|
76
|
+
user_id: user_id,
|
|
77
|
+
webhook_url: webhook_url
|
|
69
78
|
}.compact
|
|
70
79
|
)
|
|
71
80
|
end
|
|
72
81
|
|
|
73
|
-
# 예약 발송을 취소한다 (DELETE /
|
|
82
|
+
# 예약 발송을 취소한다 (DELETE /alimtalk/send/:receipt_id)
|
|
74
83
|
# 접수(READY) 상태의 예약 건만 취소할 수 있다 — 이미 전송에 들어갔으면 3023 이다.
|
|
75
84
|
def alimtalk_send_cancel(receipt_id:)
|
|
76
85
|
request(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkSender
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 알림톡 발신프로필(카카오채널) 생명주기 — GET /
|
|
4
|
+
# 알림톡 발신프로필(카카오채널) 생명주기 — GET /alimtalk/categories · /senders 계열
|
|
5
5
|
#
|
|
6
6
|
# 카테고리 조회 → OTP 발송 → 발신프로필 등록 → 목록/상세 → 연동 해지 순으로 쓴다.
|
|
7
7
|
# 등록이 끝나면 서버가 그룹키 등록까지 자동으로 하므로, 공식 템플릿은 별도 채택 없이 바로 발송된다.
|
|
@@ -16,7 +16,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
16
16
|
# @comment_by Claude (alfred)
|
|
17
17
|
# @date: 26-08-27
|
|
18
18
|
included do
|
|
19
|
-
# 카카오 카테고리 목록을 조회한다 (GET /
|
|
19
|
+
# 카카오 카테고리 목록을 조회한다 (GET /alimtalk/categories)
|
|
20
20
|
# 발신프로필 등록 시 필요한 category_code 후보다. 벤더 응답을 그대로 프록시한다.
|
|
21
21
|
def alimtalk_categories
|
|
22
22
|
request(
|
|
@@ -26,7 +26,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# 채널 관리자폰으로 OTP 를 발송한다 (POST /
|
|
29
|
+
# 채널 관리자폰으로 OTP 를 발송한다 (POST /alimtalk/senders/otp)
|
|
30
30
|
# ⚠️ 실제로 문자가 나간다. 여기서 받은 인증번호를 alimtalk_sender_create 의 otp 로 넘긴다.
|
|
31
31
|
def alimtalk_sender_otp(yellow_id:, phone:)
|
|
32
32
|
request(
|
|
@@ -40,7 +40,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
40
40
|
)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# 발신프로필을 등록한다 (POST /
|
|
43
|
+
# 발신프로필을 등록한다 (POST /alimtalk/senders)
|
|
44
44
|
# ⚠️ 카카오에 발신프로필이 실제 등록된다. 같은 yellow_id 를 다시 등록하면 기존 프로필을 재사용한다(dedup).
|
|
45
45
|
# 등록 성공 시 그룹키 등록까지 서버가 수행하므로 공식 카탈로그 전체를 바로 발송할 수 있다.
|
|
46
46
|
def alimtalk_sender_create(otp:, yellow_id:, phone:, category_code:)
|
|
@@ -57,7 +57,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
57
57
|
)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# 연동한 채널 목록을 조회한다 (GET /
|
|
60
|
+
# 연동한 채널 목록을 조회한다 (GET /alimtalk/senders)
|
|
61
61
|
# 자체 DB 만 조회하며 벤더를 호출하지 않는다. 응답은 { list: [...], count: N }.
|
|
62
62
|
def alimtalk_sender_list
|
|
63
63
|
request(
|
|
@@ -67,7 +67,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
67
67
|
)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# 채널 상세를 조회한다 (GET /
|
|
70
|
+
# 채널 상세를 조회한다 (GET /alimtalk/senders/:id)
|
|
71
71
|
# sync: true 면 벤더에서 채널 상태를 다시 읽어 반영한다(느리다). 미지정이면 자체 DB 만 본다.
|
|
72
72
|
# ⚠️ 미연동/미존재 채널은 404, 다른 프로젝트의 채널은 403 으로 오며 둘 다 error_code 는 3024 다.
|
|
73
73
|
def alimtalk_sender_detail(ksp_id:, sync: nil)
|
|
@@ -79,7 +79,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
79
79
|
)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
# 채널 연동을 해지한다 (DELETE /
|
|
82
|
+
# 채널 연동을 해지한다 (DELETE /alimtalk/senders/:id)
|
|
83
83
|
# 이 프로젝트와의 연동만 끊는다 — 채널 모델과 템플릿은 보존된다. 성공 시 본문은 null 이다.
|
|
84
84
|
def alimtalk_sender_release(ksp_id:)
|
|
85
85
|
request(
|
|
@@ -89,7 +89,7 @@ module BootpayStore::Concern::AlimtalkSender
|
|
|
89
89
|
)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
# 채널 변수 예문 사전을 갱신한다 (PUT /
|
|
92
|
+
# 채널 변수 예문 사전을 갱신한다 (PUT /alimtalk/senders/:id/variable_examples)
|
|
93
93
|
# 템플릿 미리보기에서 #{user_name} 대신 '홍길동' 처럼 읽히게 하는 **표시용** 값이다.
|
|
94
94
|
# ⚠️ 발송값이 아니다 — 벤더로 전송되지 않으므로 검수 상태와 무관하다. 보낸 키만 덮어쓴다(부분 갱신).
|
|
95
95
|
# examples: { user_name: '홍길동', company_name: '부트페이몰' } — 키에 '.' 이나 선행 '$' 는 쓸 수 없다.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkTemplate
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 가맹점 자체 알림톡 템플릿 CRUD·등록·검수 — /
|
|
4
|
+
# 가맹점 자체 알림톡 템플릿 CRUD·등록·검수 — /alimtalk/templates 계열
|
|
5
5
|
#
|
|
6
6
|
# 흐름: (초안 생성 → 확인 → 대행사 등록) → 검수 요청 → 승인(APR) → 발송 가능
|
|
7
7
|
# `alimtalk_template_create(register: false)` 로 초안만 만들고, 내용을 확인한 뒤
|
|
@@ -13,7 +13,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
13
13
|
# @comment_by Claude (alfred)
|
|
14
14
|
# @date: 26-08-27
|
|
15
15
|
included do
|
|
16
|
-
# 내 자체 템플릿 목록을 조회한다 (GET /
|
|
16
|
+
# 내 자체 템플릿 목록을 조회한다 (GET /alimtalk/templates)
|
|
17
17
|
# ins: 검수상태 필터 — 1 REG(등록) / 2 REQ(검수요청) / 3 APR(승인) / 4 KRR(등록거절) / 5 REJ(승인반려).
|
|
18
18
|
# 숫자·숫자문자열·벤더 문자열('APR' 등)을 모두 받는다. 해석 못 하는 값은 필터 없음으로 떨어진다.
|
|
19
19
|
# keyword: 코드·이름·본문·분류 부분일치. sort: latest(기본)·oldest·code.
|
|
@@ -31,7 +31,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
# 자체 템플릿을 생성한다 (POST /
|
|
34
|
+
# 자체 템플릿을 생성한다 (POST /alimtalk/templates)
|
|
35
35
|
# ⚠️ register 를 false 로 주지 않으면 대행사·카카오에 **실제 등록**된다(되돌리려면 삭제해야 한다).
|
|
36
36
|
#
|
|
37
37
|
# emphasize_type: NONE·TEXT(강조표기형)·IMAGE(이미지형)·ITEM_LIST(아이템리스트형)
|
|
@@ -76,7 +76,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
76
76
|
)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
# 자체 템플릿 상세를 조회한다 (GET /
|
|
79
|
+
# 자체 템플릿 상세를 조회한다 (GET /alimtalk/templates/:id)
|
|
80
80
|
# template_id 는 문서 id 이고, ObjectId 형식이 아니면 **템플릿 코드**로 해석한다.
|
|
81
81
|
# ⚠️ sync 는 서버 기본값이 **true** 라 조회만 해도 벤더 상태 동기화가 일어난다.
|
|
82
82
|
# 초안(등록 전)을 조회할 때는 sync: false 를 권장한다.
|
|
@@ -89,7 +89,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
89
89
|
)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
-
# 자체 템플릿을 수정한다 (PUT /
|
|
92
|
+
# 자체 템플릿을 수정한다 (PUT /alimtalk/templates/:id)
|
|
93
93
|
# ⚠️ **부분 수정이 아니다.** 보내지 않은 필드는 nil 로 덮어써지므로 항상 전체 필드를 보낸다.
|
|
94
94
|
# ⚠️ 등록된 템플릿을 수정하면 벤더에도 수정 요청이 나간다.
|
|
95
95
|
# 수정 가능 상태는 초안 / REG(등록) / REJ(승인반려) / KRR(등록거절) 뿐이다 — APR·REQ 는 거부된다.
|
|
@@ -127,7 +127,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
127
127
|
)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
# 자체 템플릿을 삭제한다 (DELETE /
|
|
130
|
+
# 자체 템플릿을 삭제한다 (DELETE /alimtalk/templates/:id)
|
|
131
131
|
# 초안(등록 전)은 대행사 거부와 무관하게 로컬에서 삭제된다.
|
|
132
132
|
# ⚠️ 등록분은 **대행사 삭제가 성공해야** 삭제된다 — 승인(APR) 템플릿은 카카오가 거부하므로
|
|
133
133
|
# 500(3013)이 오고 템플릿은 남는다. 같은 코드가 대행사에 선점된 채 로컬만 사라지는 것을 막기 위함이다.
|
|
@@ -139,7 +139,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
139
139
|
)
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
-
# 초안을 대행사에 등록한다 (POST /
|
|
142
|
+
# 초안을 대행사에 등록한다 (POST /alimtalk/templates/:id/register)
|
|
143
143
|
# ⚠️ 대행사·카카오에 실제 등록된다. 등록 전(초안) 상태에서만 호출할 수 있다.
|
|
144
144
|
def alimtalk_template_register(template_id:)
|
|
145
145
|
request(
|
|
@@ -149,7 +149,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
149
149
|
)
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
-
# 검수를 요청한다 (POST /
|
|
152
|
+
# 검수를 요청한다 (POST /alimtalk/templates/:id/inspect)
|
|
153
153
|
# ⚠️ **카카오에 검수를 요청하며 취소할 수 없다.**
|
|
154
154
|
# 대행사 등록이 끝난 대기(R) + REG(등록) 상태에서만 호출할 수 있다 — 초안은 먼저 register 를 부른다.
|
|
155
155
|
# 반려(REJ/KRR)된 건은 재요청이 아니라 **수정 후 재요청**이다. 반려 사유는 응답의 comments 에 담긴다.
|
|
@@ -161,7 +161,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
161
161
|
)
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
# 템플릿 목록을 내보낸다 (GET /
|
|
164
|
+
# 템플릿 목록을 내보낸다 (GET /alimtalk/templates/export)
|
|
165
165
|
# scope: private(기본, 내 채널 자체 템플릿)·official(공식 카탈로그)·all
|
|
166
166
|
# ⚠️ 기본 format 을 **json 으로 둔다** — 서버 기본은 csv 지만, csv 본문은 JSON 이 아니라서
|
|
167
167
|
# 공용 request 의 파싱을 통과하지 못한다. csv 를 주면 파싱 없이 원문 문자열을 담아 돌려준다.
|
|
@@ -186,7 +186,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
186
186
|
)
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
# 이미지형 템플릿의 원본 이미지를 올린다 (POST /
|
|
189
|
+
# 이미지형 템플릿의 원본 이미지를 올린다 (POST /alimtalk/templates/image)
|
|
190
190
|
# 돌려받은 image_url 을 템플릿 생성/수정의 storage_image_url 로 넘긴다.
|
|
191
191
|
# 규격을 업로드 **전에** 서버가 검사한다 — jpg/png · 500KB 이하 · 가로 500px 이상 · 2:1.
|
|
192
192
|
# image 는 파일 경로(String)·IO·HTTP::FormData::File 을 모두 받는다.
|
|
@@ -199,7 +199,7 @@ module BootpayStore::Concern::AlimtalkTemplate
|
|
|
199
199
|
headers: { 'Bootpay-Role' => 'user' })
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
-
# 아이템리스트형의 하이라이트 썸네일을 올린다 (POST /
|
|
202
|
+
# 아이템리스트형의 하이라이트 썸네일을 올린다 (POST /alimtalk/templates/highlight_image)
|
|
203
203
|
# ⚠️ 본문 이미지와 **규격이 다르다** — jpg/png · 500KB 이하 · 가로 **108px** 이상 · **1:1**.
|
|
204
204
|
# 본문 이미지 엔드포인트로 올리면 거부된다.
|
|
205
205
|
# 돌려받은 image_url 은 item_highlight.storage_image_url 로 넘긴다.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module BootpayStore::Concern::AlimtalkWebhook
|
|
2
2
|
extend ActiveSupport::Concern
|
|
3
3
|
|
|
4
|
-
# 알림톡 발송결과·검수결과 웹훅 설정 — /
|
|
4
|
+
# 알림톡 발송결과·검수결과 웹훅 설정 — /alimtalk/webhook 계열
|
|
5
5
|
#
|
|
6
6
|
# ⚠️ **주문·구독 통합 웹훅과 완전히 별개다.** 알림톡 이벤트를 기존 주문 웹훅 URL 로 태우면
|
|
7
7
|
# 그 수신 서버가 모르는 payload 를 받아 기존 연동이 깨진다. 그래서 수신 URL 을 따로 둔다.
|
|
@@ -15,7 +15,7 @@ module BootpayStore::Concern::AlimtalkWebhook
|
|
|
15
15
|
# @comment_by Claude (alfred)
|
|
16
16
|
# @date: 26-08-27
|
|
17
17
|
included do
|
|
18
|
-
# 웹훅 설정을 조회한다 (GET /
|
|
18
|
+
# 웹훅 설정을 조회한다 (GET /alimtalk/webhook)
|
|
19
19
|
# 시크릿은 앞 12자만 노출된다. 미설정이면 { configured: false } 로 온다.
|
|
20
20
|
def alimtalk_webhook_detail
|
|
21
21
|
request(
|
|
@@ -25,7 +25,7 @@ module BootpayStore::Concern::AlimtalkWebhook
|
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# 웹훅 설정을 저장한다 (PUT /
|
|
28
|
+
# 웹훅 설정을 저장한다 (PUT /alimtalk/webhook)
|
|
29
29
|
# url 은 **https 만** 허용한다(아니면 3028). 최초 저장 시 서명 시크릿이 자동 발급된다.
|
|
30
30
|
# events: 구독할 이벤트 코드. 목록에 없는 값은 저장 시 조용히 버려진다(유령 구독 방지).
|
|
31
31
|
# 300 발송 접수(기본 미구독) / 301 전달 성공 / 302 전달 실패 / 303 예약 취소 /
|
|
@@ -44,7 +44,7 @@ module BootpayStore::Concern::AlimtalkWebhook
|
|
|
44
44
|
)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# 테스트 이벤트를 1건 발송한다 (POST /
|
|
47
|
+
# 테스트 이벤트를 1건 발송한다 (POST /alimtalk/webhook/test)
|
|
48
48
|
# ⚠️ **설정된 URL 로 실제 HTTP 요청이 나간다.** 구독 여부와 무관하게 보낸다.
|
|
49
49
|
# 웹훅이 설정돼 있지 않으면 3029. 응답: { delivery_id:, url:, queued: }
|
|
50
50
|
def alimtalk_webhook_test
|
|
@@ -55,7 +55,7 @@ module BootpayStore::Concern::AlimtalkWebhook
|
|
|
55
55
|
)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
# 서명 시크릿을 재발급한다 (POST /
|
|
58
|
+
# 서명 시크릿을 재발급한다 (POST /alimtalk/webhook/secret)
|
|
59
59
|
# ⚠️ **이 응답에서만 secret 원문을 돌려준다**(이후 조회는 마스킹된다).
|
|
60
60
|
# ⚠️ 이미 큐에 있는 전송 건은 발송 당시 시크릿으로 서명된다.
|
|
61
61
|
def alimtalk_webhook_rotate_secret
|
|
@@ -66,7 +66,7 @@ module BootpayStore::Concern::AlimtalkWebhook
|
|
|
66
66
|
)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
# 웹훅 전송 이력을 조회한다 (GET /
|
|
69
|
+
# 웹훅 전송 이력을 조회한다 (GET /alimtalk/webhook/deliveries)
|
|
70
70
|
# 성공·실패를 모두 남긴다. 응답: { list: [{ delivery_id:, event:, event_code:, url:, status:,
|
|
71
71
|
# retry_count:, max_retry:, tags:, created_at: }], count:, page:, per: }
|
|
72
72
|
def alimtalk_webhook_deliveries(page: nil, limit: nil)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module BootpayStore::Concern::Faq
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
# FAQ 목록 (GET /v1/faqs)
|
|
6
|
+
# @comment_by Claude
|
|
7
|
+
# @date: 26-09-29
|
|
8
|
+
# supervisor: true 면 운영자 모드(Bootpay-Role: supervisor) — view: 'all' 로 비공개 FAQ 까지 조회할 수 있다.
|
|
9
|
+
# 고객 모드는 몰 FAQ 사용여부가 꺼져 있으면 BOARD_FEATURE_DISABLED 로 거절된다.
|
|
10
|
+
def faqs(page: 1, limit: 20, keyword: nil, view: nil, supervisor: false, idempotency_key: nil)
|
|
11
|
+
request(
|
|
12
|
+
uri: 'faqs',
|
|
13
|
+
method: :get,
|
|
14
|
+
headers: {
|
|
15
|
+
'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
|
|
16
|
+
'Bootpay-Role' => supervisor ? 'supervisor' : 'user'
|
|
17
|
+
},
|
|
18
|
+
params: {
|
|
19
|
+
page: page,
|
|
20
|
+
limit: limit,
|
|
21
|
+
keyword: keyword,
|
|
22
|
+
view: view
|
|
23
|
+
}.compact
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# FAQ 단건 (GET /v1/faqs/:id)
|
|
28
|
+
# @comment_by Claude
|
|
29
|
+
# @date: 26-09-29
|
|
30
|
+
# 비공개 FAQ 는 supervisor: true 일 때만 보인다(아니면 404 POST_NOT_FOUND).
|
|
31
|
+
def faq_detail(faq_id:, supervisor: false, idempotency_key: nil)
|
|
32
|
+
request(
|
|
33
|
+
uri: "faqs/#{faq_id}",
|
|
34
|
+
method: :get,
|
|
35
|
+
headers: {
|
|
36
|
+
'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
|
|
37
|
+
'Bootpay-Role' => supervisor ? 'supervisor' : 'user'
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# FAQ 등록 (POST /v1/faqs) — supervisor 전용
|
|
43
|
+
# @comment_by Claude
|
|
44
|
+
# @date: 26-09-29
|
|
45
|
+
def faq_create(title:, content:, images: nil, is_display: nil, idempotency_key: nil)
|
|
46
|
+
request(
|
|
47
|
+
uri: 'faqs',
|
|
48
|
+
method: :post,
|
|
49
|
+
headers: {
|
|
50
|
+
'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
|
|
51
|
+
'Bootpay-Role' => 'supervisor'
|
|
52
|
+
},
|
|
53
|
+
payload: {
|
|
54
|
+
title: title,
|
|
55
|
+
content: content,
|
|
56
|
+
images: images,
|
|
57
|
+
is_display: is_display
|
|
58
|
+
}.compact
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# FAQ 수정 (PUT /v1/faqs/:id) — supervisor 전용
|
|
63
|
+
# @comment_by Claude
|
|
64
|
+
# @date: 26-09-29
|
|
65
|
+
# 보낸 필드만 바뀐다. images 는 보내면 목록 전체를 교체한다([] 면 모두 삭제).
|
|
66
|
+
def faq_update(faq_id:, title: nil, content: nil, images: nil, is_display: nil, idempotency_key: nil)
|
|
67
|
+
request(
|
|
68
|
+
uri: "faqs/#{faq_id}",
|
|
69
|
+
method: :put,
|
|
70
|
+
headers: {
|
|
71
|
+
'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
|
|
72
|
+
'Bootpay-Role' => 'supervisor'
|
|
73
|
+
},
|
|
74
|
+
payload: {
|
|
75
|
+
title: title,
|
|
76
|
+
content: content,
|
|
77
|
+
images: images,
|
|
78
|
+
is_display: is_display
|
|
79
|
+
}.compact
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# FAQ 삭제 (DELETE /v1/faqs/:id) — supervisor 전용
|
|
84
|
+
# @comment_by Claude
|
|
85
|
+
# @date: 26-09-29
|
|
86
|
+
def faq_delete(faq_id:, idempotency_key: nil)
|
|
87
|
+
request(
|
|
88
|
+
uri: "faqs/#{faq_id}",
|
|
89
|
+
method: :delete,
|
|
90
|
+
headers: {
|
|
91
|
+
'Idempotency-Key' => idempotency_key.presence || SecureRandom.uuid,
|
|
92
|
+
'Bootpay-Role' => 'supervisor'
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|