kcppayments_rails 0.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 (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +49 -0
  4. data/Rakefile +4 -0
  5. data/lib/generators/kcppayments_rails/install/install_generator.rb +21 -0
  6. data/lib/generators/kcppayments_rails/install/templates/kcp_controller.js +54 -0
  7. data/lib/generators/kcppayments_rails/install/templates/kcppayments_rails.rb +14 -0
  8. data/lib/kcppayments_rails/client.rb +49 -0
  9. data/lib/kcppayments_rails/configuration.rb +24 -0
  10. data/lib/kcppayments_rails/engine.rb +21 -0
  11. data/lib/kcppayments_rails/helpers/kcp_helper.rb +33 -0
  12. data/lib/kcppayments_rails/version.rb +5 -0
  13. data/lib/kcppayments_rails.rb +26 -0
  14. data/sig/kcppayments_rails.rbs +4 -0
  15. data/test_app/.dockerignore +51 -0
  16. data/test_app/.github/dependabot.yml +12 -0
  17. data/test_app/.github/workflows/ci.yml +55 -0
  18. data/test_app/.gitignore +87 -0
  19. data/test_app/.kamal/hooks/docker-setup.sample +3 -0
  20. data/test_app/.kamal/hooks/post-app-boot.sample +3 -0
  21. data/test_app/.kamal/hooks/post-deploy.sample +14 -0
  22. data/test_app/.kamal/hooks/post-proxy-reboot.sample +3 -0
  23. data/test_app/.kamal/hooks/pre-app-boot.sample +3 -0
  24. data/test_app/.kamal/hooks/pre-build.sample +51 -0
  25. data/test_app/.kamal/hooks/pre-connect.sample +47 -0
  26. data/test_app/.kamal/hooks/pre-deploy.sample +122 -0
  27. data/test_app/.kamal/hooks/pre-proxy-reboot.sample +3 -0
  28. data/test_app/.kamal/secrets +17 -0
  29. data/test_app/.rubocop.yml +8 -0
  30. data/test_app/.ruby-version +1 -0
  31. data/test_app/Dockerfile +72 -0
  32. data/test_app/Gemfile +60 -0
  33. data/test_app/Gemfile.lock +370 -0
  34. data/test_app/README.md +181 -0
  35. data/test_app/Rakefile +6 -0
  36. data/test_app/app/assets/images/.keep +0 -0
  37. data/test_app/app/assets/stylesheets/application.css +10 -0
  38. data/test_app/app/controllers/application_controller.rb +4 -0
  39. data/test_app/app/controllers/concerns/.keep +0 -0
  40. data/test_app/app/controllers/orders_controller.rb +31 -0
  41. data/test_app/app/controllers/payments_controller.rb +120 -0
  42. data/test_app/app/helpers/application_helper.rb +2 -0
  43. data/test_app/app/helpers/orders_helper.rb +2 -0
  44. data/test_app/app/helpers/payments_helper.rb +2 -0
  45. data/test_app/app/javascript/application.js +3 -0
  46. data/test_app/app/javascript/controllers/application.js +9 -0
  47. data/test_app/app/javascript/controllers/hello_controller.js +7 -0
  48. data/test_app/app/javascript/controllers/index.js +4 -0
  49. data/test_app/app/javascript/controllers/kcp_controller.js +310 -0
  50. data/test_app/app/jobs/application_job.rb +7 -0
  51. data/test_app/app/mailers/application_mailer.rb +4 -0
  52. data/test_app/app/models/application_record.rb +3 -0
  53. data/test_app/app/models/concerns/.keep +0 -0
  54. data/test_app/app/models/order.rb +67 -0
  55. data/test_app/app/views/layouts/application.html.erb +28 -0
  56. data/test_app/app/views/layouts/mailer.html.erb +13 -0
  57. data/test_app/app/views/layouts/mailer.text.erb +1 -0
  58. data/test_app/app/views/orders/create.html.erb +2 -0
  59. data/test_app/app/views/orders/index.html.erb +41 -0
  60. data/test_app/app/views/orders/new.html.erb +44 -0
  61. data/test_app/app/views/orders/show.html.erb +36 -0
  62. data/test_app/app/views/payments/_receipt_links.html.erb +31 -0
  63. data/test_app/app/views/payments/callback.html.erb +2 -0
  64. data/test_app/app/views/payments/create.html.erb +2 -0
  65. data/test_app/app/views/payments/failure.html.erb +20 -0
  66. data/test_app/app/views/payments/new.html.erb +145 -0
  67. data/test_app/app/views/payments/receipt.html.erb +156 -0
  68. data/test_app/app/views/payments/success.html.erb +34 -0
  69. data/test_app/app/views/pwa/manifest.json.erb +22 -0
  70. data/test_app/app/views/pwa/service-worker.js +26 -0
  71. data/test_app/bin/brakeman +7 -0
  72. data/test_app/bin/dev +2 -0
  73. data/test_app/bin/docker-entrypoint +14 -0
  74. data/test_app/bin/importmap +4 -0
  75. data/test_app/bin/jobs +6 -0
  76. data/test_app/bin/kamal +16 -0
  77. data/test_app/bin/rails +4 -0
  78. data/test_app/bin/rake +4 -0
  79. data/test_app/bin/rubocop +8 -0
  80. data/test_app/bin/setup +65 -0
  81. data/test_app/bin/thrust +5 -0
  82. data/test_app/config/application.rb +42 -0
  83. data/test_app/config/boot.rb +4 -0
  84. data/test_app/config/cable.yml +17 -0
  85. data/test_app/config/cache.yml +16 -0
  86. data/test_app/config/credentials.yml.enc +1 -0
  87. data/test_app/config/database.yml +41 -0
  88. data/test_app/config/deploy.yml +116 -0
  89. data/test_app/config/environment.rb +5 -0
  90. data/test_app/config/environments/development.rb +74 -0
  91. data/test_app/config/environments/production.rb +90 -0
  92. data/test_app/config/environments/test.rb +53 -0
  93. data/test_app/config/importmap.rb +7 -0
  94. data/test_app/config/initializers/assets.rb +7 -0
  95. data/test_app/config/initializers/content_security_policy.rb +25 -0
  96. data/test_app/config/initializers/filter_parameter_logging.rb +8 -0
  97. data/test_app/config/initializers/inflections.rb +16 -0
  98. data/test_app/config/initializers/kcppayments_rails.rb +29 -0
  99. data/test_app/config/locales/en.yml +31 -0
  100. data/test_app/config/master.key +1 -0
  101. data/test_app/config/puma.rb +41 -0
  102. data/test_app/config/queue.yml +18 -0
  103. data/test_app/config/recurring.yml +15 -0
  104. data/test_app/config/routes.rb +29 -0
  105. data/test_app/config/storage.yml +34 -0
  106. data/test_app/config.ru +6 -0
  107. data/test_app/db/cable_schema.rb +11 -0
  108. data/test_app/db/cache_schema.rb +14 -0
  109. data/test_app/db/migrate/20250827075913_create_orders.rb +16 -0
  110. data/test_app/db/migrate/20250827121258_add_kcp_fields_to_orders.rb +6 -0
  111. data/test_app/db/queue_schema.rb +129 -0
  112. data/test_app/db/schema.rb +28 -0
  113. data/test_app/db/seeds.rb +80 -0
  114. data/test_app/lib/tasks/.keep +0 -0
  115. data/test_app/log/.keep +0 -0
  116. data/test_app/public/.well-known/appspecific/com.chrome.devtools.json +1 -0
  117. data/test_app/public/400.html +114 -0
  118. data/test_app/public/404.html +114 -0
  119. data/test_app/public/406-unsupported-browser.html +114 -0
  120. data/test_app/public/422.html +114 -0
  121. data/test_app/public/500.html +114 -0
  122. data/test_app/public/icon.png +0 -0
  123. data/test_app/public/icon.svg +3 -0
  124. data/test_app/public/robots.txt +1 -0
  125. data/test_app/script/.keep +0 -0
  126. data/test_app/tmp/.keep +0 -0
  127. data/test_app/tmp/restart.txt +0 -0
  128. data/test_app/vendor/.keep +0 -0
  129. data/test_app/vendor/javascript/.keep +0 -0
  130. metadata +184 -0
@@ -0,0 +1,181 @@
1
+ # KCP Payments Rails 테스트 애플리케이션
2
+
3
+ 이 애플리케이션은 `kcppayments_rails` 젬을 테스트하기 위한 Rails 8 샘플 애플리케이션입니다.
4
+
5
+ ## 시작하기
6
+
7
+ ### 1. 설치 및 설정
8
+
9
+ ```bash
10
+ # 테스트 앱 디렉토리로 이동
11
+ cd test_app
12
+
13
+ # 의존성 설치 (이미 완료됨)
14
+ bundle install
15
+
16
+ # 데이터베이스 마이그레이션 (이미 완료됨)
17
+ bin/rails db:migrate
18
+
19
+ # 샘플 데이터 생성 (이미 완료됨)
20
+ bin/rails db:seed
21
+ ```
22
+
23
+ ### 2. KCP 테스트 계정 설정
24
+
25
+ `config/initializers/kcppayments_rails.rb` 파일에서 KCP 테스트 계정 정보를 설정하세요:
26
+
27
+ ```ruby
28
+ KcppaymentsRails.configure do |config|
29
+ config.site_cd = "T0000" # KCP에서 발급받은 테스트 사이트 코드
30
+ config.site_key = "3grptw1.zW0GSo4PQdaGvsF__" # KCP에서 발급받은 테스트 사이트 키
31
+ end
32
+ ```
33
+
34
+ ### 3. 애플리케이션 실행
35
+
36
+ ```bash
37
+ bin/rails server
38
+ ```
39
+
40
+ 브라우저에서 http://localhost:3000 접속
41
+
42
+ ## 기능 테스트
43
+
44
+ ### 주요 페이지
45
+
46
+ 1. **주문 목록** (http://localhost:3000)
47
+ - 모든 주문 조회
48
+ - 새 주문 생성
49
+ - 주문별 결제 진행
50
+
51
+ 2. **새 주문 생성** (http://localhost:3000/orders/new)
52
+ - 상품명, 금액, 구매자 정보 입력
53
+ - 면세 금액 설정 가능
54
+
55
+ 3. **결제 페이지**
56
+ - KCP 결제 모듈 통합
57
+ - 신용카드, 계좌이체, 가상계좌 선택 가능
58
+ - Stimulus 컨트롤러를 통한 결제 처리
59
+
60
+ ### 테스트 시나리오
61
+
62
+ #### 1. 일반 결제 테스트
63
+ - 주문 목록에서 "노트북 - MacBook Pro 14인치" 선택
64
+ - "결제하기" 클릭
65
+ - 결제 수단 선택 후 결제 진행
66
+
67
+ #### 2. 면세 상품 테스트
68
+ - 주문 목록에서 "도서 - Ruby on Rails 완벽 가이드" 선택
69
+ - 전액 면세 처리 확인
70
+ - 결제 진행
71
+
72
+ #### 3. 부분 면세 테스트
73
+ - 주문 목록에서 "식료품 세트" 선택
74
+ - 부분 면세 금액 확인 (30,000원)
75
+ - 결제 진행
76
+
77
+ ## 주요 파일 구조
78
+
79
+ ```
80
+ test_app/
81
+ ├── app/
82
+ │ ├── controllers/
83
+ │ │ ├── orders_controller.rb # 주문 관리
84
+ │ │ └── payments_controller.rb # 결제 처리
85
+ │ ├── models/
86
+ │ │ └── order.rb # 주문 모델
87
+ │ ├── views/
88
+ │ │ ├── orders/ # 주문 관련 뷰
89
+ │ │ └── payments/ # 결제 관련 뷰
90
+ │ └── javascript/
91
+ │ └── controllers/
92
+ │ └── kcp_controller.js # KCP Stimulus 컨트롤러
93
+ ├── config/
94
+ │ ├── routes.rb # 라우팅 설정
95
+ │ └── initializers/
96
+ │ └── kcppayments_rails.rb # KCP 설정
97
+ └── db/
98
+ └── seeds.rb # 샘플 데이터
99
+ ```
100
+
101
+ ## KCP 헬퍼 사용 예시
102
+
103
+ ### 뷰에서 KCP 스크립트 로드
104
+ ```erb
105
+ <%= kcp_script_tag %>
106
+ ```
107
+
108
+ ### 결제 폼에 KCP 데이터 속성 추가
109
+ ```erb
110
+ <%= form_with html: kcp_form_attrs(
111
+ order_id: @order.order_no,
112
+ amount: @order.amount,
113
+ buyer_name: @order.buyer_name,
114
+ buyer_email: @order.buyer_email,
115
+ product_name: @order.product_name,
116
+ tax_free_amount: @order.tax_free_amount
117
+ ) do |form| %>
118
+ <!-- 폼 내용 -->
119
+ <% end %>
120
+ ```
121
+
122
+ ## 데모 모드
123
+
124
+ ### KCP 계정이 없는 경우
125
+ 이 테스트 앱은 **데모 모드**를 제공합니다:
126
+
127
+ 1. **자동 감지**: KCP JavaScript가 로드되지 않으면 자동으로 데모 모드로 전환
128
+ 2. **시뮬레이션**: 실제 결제 플로우를 시뮬레이션하여 젬 기능 테스트 가능
129
+ 3. **완전한 테스트**: 주문 생성부터 결제 완료까지 전체 플로우 확인
130
+
131
+ **데모 모드에서 테스트 가능한 기능:**
132
+ - ✅ KCP 헬퍼 메소드 (`kcp_script_tag`, `kcp_form_attrs`)
133
+ - ✅ Stimulus 컨트롤러 통합
134
+ - ✅ 결제 폼 데이터 속성
135
+ - ✅ 결제 성공/실패 플로우
136
+ - ✅ 주문 상태 업데이트
137
+
138
+ ## 실제 KCP 연동
139
+
140
+ ### 1. KCP 계정 설정
141
+ 실제 운영을 위해서는 KCP에서 발급받은 계정이 필요합니다:
142
+
143
+ ```ruby
144
+ # config/initializers/kcppayments_rails.rb
145
+ KcppaymentsRails.configure do |config|
146
+ config.site_cd = "실제_사이트_코드"
147
+ config.site_key = "실제_사이트_키"
148
+ end
149
+ ```
150
+
151
+ ### 2. 도메인 등록
152
+ - KCP 관리자 페이지에서 서비스 도메인 등록 필요
153
+ - localhost는 테스트 계정에서만 허용되는 경우가 많음
154
+
155
+ ## 문제 해결
156
+
157
+ ### 결제 모듈이 로드되지 않는 경우
158
+ - KCP 사이트 코드와 키가 올바른지 확인
159
+ - 도메인이 KCP에 등록되어 있는지 확인
160
+ - JavaScript 콘솔에서 에러 확인
161
+ - 네트워크 탭에서 KCP 스크립트 로드 확인
162
+ - **데모 모드로 기본 기능 테스트 가능**
163
+
164
+ ### 결제 후 콜백이 작동하지 않는 경우
165
+ - `payments_controller.rb`의 `callback` 액션 확인
166
+ - CSRF 토큰 검증이 스킵되었는지 확인
167
+ - 서버 로그에서 파라미터 확인
168
+
169
+ ## 추가 개발
170
+
171
+ 이 테스트 앱을 기반으로 다음과 같은 기능을 추가할 수 있습니다:
172
+
173
+ - 결제 취소 기능
174
+ - 결제 내역 조회
175
+ - 영수증 발급
176
+ - 정기 결제
177
+ - 부분 취소
178
+
179
+ ## 라이선스
180
+
181
+ 이 테스트 애플리케이션은 `kcppayments_rails` 젬의 일부입니다.
data/test_app/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
File without changes
@@ -0,0 +1,10 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css.
3
+ *
4
+ * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
5
+ * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
6
+ * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
7
+ * depending on specificity.
8
+ *
9
+ * Consider organizing styles into separate files for maintainability.
10
+ */
@@ -0,0 +1,4 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
3
+ allow_browser versions: :modern
4
+ end
File without changes
@@ -0,0 +1,31 @@
1
+ class OrdersController < ApplicationController
2
+ def index
3
+ @orders = Order.all
4
+ end
5
+
6
+ def show
7
+ @order = Order.find(params[:id])
8
+ end
9
+
10
+ def new
11
+ @order = Order.new
12
+ end
13
+
14
+ def create
15
+ @order = Order.new(order_params)
16
+ @order.order_no = "ORD#{Time.now.to_i}"
17
+ @order.status = "pending"
18
+
19
+ if @order.save
20
+ redirect_to @order, notice: '주문이 생성되었습니다.'
21
+ else
22
+ render :new, status: :unprocessable_entity
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def order_params
29
+ params.require(:order).permit(:product_name, :amount, :buyer_name, :buyer_email, :tax_free_amount)
30
+ end
31
+ end
@@ -0,0 +1,120 @@
1
+ class PaymentsController < ApplicationController
2
+ skip_before_action :verify_authenticity_token, only: [:callback]
3
+
4
+ def new
5
+ @order = Order.find(params[:order_id])
6
+ end
7
+
8
+ def create
9
+ @order = Order.find(params[:order_id])
10
+
11
+ # 이 액션은 실제로는 KCP JavaScript에서 결제창을 열기 전에 호출되거나
12
+ # 결제 정보를 서버에서 검증할 때 사용될 수 있습니다.
13
+ # 지금은 결제 폼을 다시 보여줍니다.
14
+
15
+ flash.now[:notice] = "결제를 진행해주세요."
16
+ render :new
17
+ end
18
+
19
+ def callback
20
+ # KCP 결제 콜백 처리
21
+ order = Order.find_by(order_no: params[:ordr_idxx])
22
+
23
+ if params[:res_cd] == "0000"
24
+ order.update(
25
+ status: "paid",
26
+ payment_method: params[:pay_method],
27
+ kcp_transaction_no: params[:tno] # KCP 거래번호 저장
28
+ )
29
+ redirect_to success_payments_path(order_id: order.id)
30
+ else
31
+ order.update(status: "failed")
32
+ redirect_to failure_payments_path(order_id: order.id)
33
+ end
34
+ end
35
+
36
+ def success
37
+ begin
38
+ @order = if params[:order_no].present?
39
+ Order.find_by!(order_no: params[:order_no])
40
+ elsif params[:order_id].present?
41
+ Order.find(params[:order_id])
42
+ else
43
+ Rails.logger.error "PaymentsController#success called without order_id or order_no parameters"
44
+ redirect_to orders_path, alert: "주문 정보를 찾을 수 없습니다." and return
45
+ end
46
+
47
+ # 데모 모드인 경우 주문을 결제 완료로 업데이트
48
+ if params[:demo] == 'true'
49
+ # 데모용 KCP 거래번호 생성 (14자리 숫자 형식: YYYYMMDDHHMMSS)
50
+ demo_tno = Time.current.strftime('%Y%m%d%H%M%S')
51
+
52
+ @order.update(
53
+ status: "paid",
54
+ payment_method: "데모 결제 (#{get_payment_method_name(@order)})",
55
+ kcp_transaction_no: demo_tno
56
+ )
57
+ @demo_mode = true
58
+ end
59
+ rescue ActiveRecord::RecordNotFound
60
+ Rails.logger.error "Order not found with params: #{params.inspect}"
61
+ redirect_to orders_path, alert: "주문을 찾을 수 없습니다." and return
62
+ rescue => e
63
+ Rails.logger.error "Unexpected error in success action: #{e.message}"
64
+ Rails.logger.error e.backtrace.join("\n")
65
+ redirect_to orders_path, alert: "처리 중 오류가 발생했습니다." and return
66
+ end
67
+ end
68
+
69
+ def failure
70
+ begin
71
+ @order = if params[:order_no].present?
72
+ Order.find_by!(order_no: params[:order_no])
73
+ elsif params[:order_id].present?
74
+ Order.find(params[:order_id])
75
+ else
76
+ Rails.logger.error "PaymentsController#failure called without order_id or order_no parameters"
77
+ redirect_to orders_path, alert: "주문 정보를 찾을 수 없습니다." and return
78
+ end
79
+ rescue ActiveRecord::RecordNotFound
80
+ Rails.logger.error "Order not found with params: #{params.inspect}"
81
+ redirect_to orders_path, alert: "주문을 찾을 수 없습니다." and return
82
+ rescue => e
83
+ Rails.logger.error "Unexpected error in failure action: #{e.message}"
84
+ redirect_to orders_path, alert: "처리 중 오류가 발생했습니다." and return
85
+ end
86
+ end
87
+
88
+ def receipt
89
+ Rails.logger.debug "Receipt action called with params: #{params.inspect}"
90
+
91
+ # 더 안전한 방식으로 주문 조회
92
+ @order = Order.find_by(id: params[:id])
93
+ Rails.logger.debug "Found order: #{@order.inspect}"
94
+
95
+ # 주문이 존재하지 않거나 결제가 완료되지 않은 경우
96
+ if @order.nil?
97
+ Rails.logger.debug "Order not found, redirecting"
98
+ redirect_to orders_path, alert: "주문을 찾을 수 없습니다." and return
99
+ elsif @order.status != "paid"
100
+ Rails.logger.debug "Order status is not paid: #{@order.status}"
101
+ redirect_to orders_path, alert: "결제가 완료된 주문만 영수증을 출력할 수 있습니다." and return
102
+ else
103
+ Rails.logger.debug "Rendering receipt for order: #{@order.id}"
104
+ # 영수증 출력용 레이아웃 없이 렌더링
105
+ render layout: false
106
+ end
107
+ rescue => e
108
+ Rails.logger.error "Unexpected error in receipt action: #{e.message}"
109
+ Rails.logger.error e.backtrace.join("\n")
110
+ @order = nil # 확실히 nil로 설정
111
+ redirect_to orders_path, alert: "오류가 발생했습니다." and return
112
+ end
113
+
114
+ private
115
+
116
+ def get_payment_method_name(order)
117
+ # 임시로 신용카드로 설정 (실제로는 선택된 결제 수단 기반)
118
+ "신용카드"
119
+ end
120
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ module OrdersHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ module PaymentsHelper
2
+ end
@@ -0,0 +1,3 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+ import "controllers"
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.element.textContent = "Hello World!"
6
+ }
7
+ }
@@ -0,0 +1,4 @@
1
+ // Import and register all your controllers from the importmap via controllers/**/*_controller
2
+ import { application } from "controllers/application"
3
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
4
+ eagerLoadControllersFrom("controllers", application)