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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a5d64e09b079b3c89bf0ddc0bb536f1342b3e788d8b9bfa643c5142c804ca64
4
- data.tar.gz: 37fcbd0358d6a5753ab1ce45f7a1f0280b031c965fba113747d2ce55082e3209
3
+ metadata.gz: c451a531a23a1a127987e92afb1980687ebd31a484326da574520dc41e78da87
4
+ data.tar.gz: 0d0e6b71cd59143809355360bedd0f9f43da13a6a02cf9c9d8952edbecb6c7d0
5
5
  SHA512:
6
- metadata.gz: 78da8a9db47c735f82c767a78094b32237a713299efa49fdc741ff0c62a656cdf4cea995674a772eb900c0de800242fe55be1486f2e22fdfaa9e0d4d3027407d
7
- data.tar.gz: b39ee1794a50a0acf3656c432e6bfc8a135064ba460e7b5a4c84f577bdbedddae271b829005e6bcd5553c042f9148c50ce8d31f33c217e69b9575bcd0bdd0709
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in bootpay-rest-client.gemspec
5
+ # Specify your gem's dependencies in bootpay.gemspec
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
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
- Object.new.tap do |o|
26
- o.define_singleton_method(:success?) { true }
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 /v1/alimtalk/messages 계열
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 /v1/alimtalk/messages)
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 /v1/alimtalk/messages/stats)
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 /v1/alimtalk/messages/:receipt_id)
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 /v1/alimtalk/official 계열
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 /v1/alimtalk/official)
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 /v1/alimtalk/official/recommend)
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 /v1/alimtalk/official/:code)
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
- # 알림톡 수신거부 — /v1/alimtalk/optouts 계열 (가맹점 CRM 수신거부 동기화용)
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 /v1/alimtalk/optouts)
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 /v1/alimtalk/optouts)
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 /v1/alimtalk/optouts/check)
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 /v1/alimtalk/optouts/:phone)
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 /v1/alimtalk/send · /send/bulk · DELETE /send/:receipt_id
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 /v1/alimtalk/send)
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 /v1/alimtalk/send/bulk) — 1요청 = N수신자
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 /v1/alimtalk/send/:receipt_id)
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 /v1/alimtalk/categories · /senders 계열
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 /v1/alimtalk/categories)
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 /v1/alimtalk/senders/otp)
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 /v1/alimtalk/senders)
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 /v1/alimtalk/senders)
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 /v1/alimtalk/senders/:id)
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 /v1/alimtalk/senders/:id)
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 /v1/alimtalk/senders/:id/variable_examples)
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·등록·검수 — /v1/alimtalk/templates 계열
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 /v1/alimtalk/templates)
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 /v1/alimtalk/templates)
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 /v1/alimtalk/templates/:id)
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 /v1/alimtalk/templates/:id)
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 /v1/alimtalk/templates/:id)
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 /v1/alimtalk/templates/:id/register)
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 /v1/alimtalk/templates/:id/inspect)
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 /v1/alimtalk/templates/export)
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 /v1/alimtalk/templates/image)
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 /v1/alimtalk/templates/highlight_image)
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
- # 알림톡 발송결과·검수결과 웹훅 설정 — /v1/alimtalk/webhook 계열
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 /v1/alimtalk/webhook)
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 /v1/alimtalk/webhook)
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 /v1/alimtalk/webhook/test)
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 /v1/alimtalk/webhook/secret)
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 /v1/alimtalk/webhook/deliveries)
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