tosspayments2-rails 0.5.4 → 0.6.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: cb82f0d033bad60126af50821b849025ea3af57657cb80689cba3f49cb1b7d97
4
- data.tar.gz: 19a61b3288da058e9c9d4f1f900ff2eb712f73aef759247f92e1257dc5afec7e
3
+ metadata.gz: 497e3e4cb07bfd7f0d3c7c9b18ce50219f0a0be23e36b36155c56187bcaf600d
4
+ data.tar.gz: 74c3dd98db76fc485e5375fe637edb482393eeba757d7b4a701cae40612934d0
5
5
  SHA512:
6
- metadata.gz: dfd24b58428ce7dcb17444d863882f2db0b5be9d303b4b24150a6b24576d0f6d68baa8bfdef34f1336f554ce94353d17f91ba4254818369f0300a798167658ad
7
- data.tar.gz: 484359c4c87bd16efabde9bc651ff341a04df54eaa378721d818a0618598c837fa1ec5932f36da4e35754b87236f2e69f0fab3100e61126929f26ded9435c617
6
+ metadata.gz: 20a3e581603818167eb5623bd512b9cb4f1e487984c82a7b59ad522ec093871d7e15dafbc49e5fc3988e34d7df98b211b1d26678c48a14db5681e9bea5f66325
7
+ data.tar.gz: 5dee95dc836ecb8c8e44b59c9cf67cbcf573a79f675a4966a801bb381f0fbe206c01f47c3671b1f06361136066feb0a162a1b6ca00d590040d6cabb69fab1135
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  _No changes yet._
4
4
 
5
+ ## [0.6.0] - 2025-08-21
6
+ ### Added
7
+ - Stimulus.js controller integration for modern Rails frontend architecture
8
+ - Automatic generation of `tosspayments_checkout_controller.js` in generator
9
+ - CSS extraction and consolidation into `tosspayments.css` file
10
+ - Automatic CSS import into Rails application.css asset pipeline
11
+
12
+ ### Changed
13
+ - Refactored checkout.html.erb from inline JavaScript to Stimulus controller with data attributes
14
+ - Extracted all inline styles from ERB templates (index, new, show, checkout) into unified CSS file
15
+ - Enhanced Rails generator to create JavaScript and CSS assets automatically
16
+ - Improved code organization following Rails 7/8 conventions with Stimulus and asset pipeline
17
+
18
+ ### Improved
19
+ - Better maintainability with separated concerns (HTML, CSS, JavaScript)
20
+ - Enhanced reusability of Stimulus controller across different pages
21
+ - Optimized performance through asset pipeline caching
22
+ - Modern Rails development patterns with Hotwire/Stimulus integration
23
+
5
24
  ## [0.5.4] - 2025-08-21
6
25
  ### Fixed
7
26
  - Fix Rails engine configuration issue where `config.tosspayments2` was undefined
@@ -21,6 +21,52 @@ module Tosspayments2
21
21
  template 'payments_helper.rb', 'app/helpers/payments_helper.rb'
22
22
  end
23
23
 
24
+ def create_stimulus_controller
25
+ return unless options[:with_model]
26
+
27
+ # app/javascript/controllers 디렉토리 생성
28
+ empty_directory 'app/javascript/controllers'
29
+
30
+ # Stimulus 컨트롤러 파일 복사
31
+ template 'tosspayments_checkout_controller.js', 'app/javascript/controllers/tosspayments_checkout_controller.js'
32
+ end
33
+
34
+ def create_css_file
35
+ return unless options[:with_model]
36
+
37
+ # app/assets/stylesheets 디렉토리 생성
38
+ empty_directory 'app/assets/stylesheets'
39
+
40
+ # CSS 파일 복사
41
+ template 'tosspayments.css', 'app/assets/stylesheets/tosspayments.css'
42
+
43
+ # application.css에 import 추가
44
+ add_css_import_to_application
45
+ end
46
+
47
+ private
48
+
49
+ def add_css_import_to_application
50
+ application_css_path = 'app/assets/stylesheets/application.css'
51
+ import_line = ' *= require tosspayments'
52
+
53
+ if File.exist?(application_css_path)
54
+ unless File.read(application_css_path).include?(import_line)
55
+ # application.css의 *= require_tree . 라인 바로 위에 추가
56
+ content = File.read(application_css_path)
57
+ if content.include?('*= require_tree .')
58
+ content.gsub!('*= require_tree .', "#{import_line}\n *= require_tree .")
59
+ File.write(application_css_path, content)
60
+ else
61
+ append_to_file application_css_path, "\n#{import_line}\n"
62
+ end
63
+ say 'Added tosspayments.css import to application.css', :green
64
+ end
65
+ else
66
+ say "Warning: application.css not found. Please manually add '#{import_line}' to your CSS manifest.", :yellow
67
+ end
68
+ end
69
+
24
70
  def create_payment_model_and_migration
25
71
  return unless options[:with_model]
26
72
 
@@ -58,11 +104,13 @@ module Tosspayments2
58
104
  # 제너레이터 실행 시 자동으로 모델/마이그레이션/뷰 생성
59
105
  def install
60
106
  create_payment_model_and_migration
107
+ create_stimulus_controller
108
+ create_css_file
61
109
  add_payments_route
62
110
  end
63
111
 
64
112
  # Thor의 hook으로 install 메서드가 자동 실행되도록 설정
65
- def self.default_task
113
+ private_class_method def self.default_task
66
114
  :install
67
115
  end
68
116
  end
@@ -8,63 +8,25 @@
8
8
  </div>
9
9
 
10
10
  <!-- TossPayments 결제 위젯 영역 -->
11
- <div id="payment-methods"></div>
12
- <div id="agreement"></div>
13
- <button id="payment-button" class="btn btn-primary">결제하기</button>
11
+ <div data-controller="tosspayments-checkout"
12
+ data-tosspayments-checkout-client-key-value="<%%= Rails.application.credentials.dig(:tosspayments, :client_key) || ENV['TOSSPAYMENTS_CLIENT_KEY'] %>"
13
+ data-tosspayments-checkout-customer-key-value="customer_<%%= Time.current.to_i %>"
14
+ data-tosspayments-checkout-order-id-value="<%%= @payment.order_id %>"
15
+ data-tosspayments-checkout-order-name-value="결제 테스트 상품"
16
+ data-tosspayments-checkout-customer-name-value="고객"
17
+ data-tosspayments-checkout-amount-value="<%%= @payment.amount %>"
18
+ data-tosspayments-checkout-success-url-value="<%%= success_payments_url %>"
19
+ data-tosspayments-checkout-fail-url-value="<%%= fail_payments_url %>">
20
+
21
+ <div id="payment-methods" data-tosspayments-checkout-target="paymentMethods"></div>
22
+ <div id="agreement" data-tosspayments-checkout-target="agreement"></div>
23
+ <button data-tosspayments-checkout-target="paymentButton"
24
+ data-action="click->tosspayments-checkout#requestPayment"
25
+ class="btn btn-primary">결제하기</button>
26
+ </div>
14
27
 
15
28
  <%%= tosspayments_script_tag %>
16
29
 
17
- <script>
18
- document.addEventListener('DOMContentLoaded', async () => {
19
- // TossPayments 설정
20
- const clientKey = '<%%= Rails.application.credentials.dig(:tosspayments, :client_key) || ENV["TOSSPAYMENTS_CLIENT_KEY"] %>';
21
- const customerKey = 'customer_<%%= Time.current.to_i %>'; // 구매자 식별값
22
-
23
- if (!clientKey) {
24
- console.error('TossPayments 클라이언트 키가 설정되지 않았습니다.');
25
- alert('결제 서비스를 초기화할 수 없습니다. 관리자에게 문의하세요.');
26
- return;
27
- }
28
-
29
- try {
30
- // TossPayments 위젯 초기화
31
- const tosspayments = TossPayments(clientKey);
32
- const widgets = tosspayments.widgets({ customerKey: customerKey });
33
-
34
- // 결제 수단 렌더링
35
- await widgets.renderPaymentMethods({
36
- selector: '#payment-methods',
37
- variantKey: 'DEFAULT'
38
- });
39
-
40
- // 약관 동의 렌더링
41
- await widgets.renderAgreement({
42
- selector: '#agreement'
43
- });
44
-
45
- // 결제 버튼 이벤트 리스너
46
- document.getElementById('payment-button').addEventListener('click', async () => {
47
- try {
48
- await widgets.requestPayment({
49
- orderId: '<%%= @payment.order_id %>',
50
- orderName: '결제 테스트 상품',
51
- customerName: '고객',
52
- amount: <%%= @payment.amount %>,
53
- successUrl: '<%%= success_payments_url %>',
54
- failUrl: '<%%= fail_payments_url %>'
55
- });
56
- } catch (error) {
57
- console.error('결제 요청 실패:', error);
58
- alert('결제 요청에 실패했습니다: ' + (error.message || error.toString()));
59
- }
60
- });
61
- } catch (error) {
62
- console.error('TossPayments 초기화 실패:', error);
63
- alert('결제 서비스 초기화에 실패했습니다: ' + (error.message || error.toString()));
64
- }
65
- });
66
- </script>
67
-
68
30
  <%% else %>
69
31
  <div class="alert alert-danger">
70
32
  결제 정보를 찾을 수 없습니다.
@@ -74,75 +36,3 @@ document.addEventListener('DOMContentLoaded', async () => {
74
36
  </div>
75
37
  <%% end %>
76
38
 
77
- <style>
78
- .payment-info {
79
- background-color: #f8f9fa;
80
- padding: 1rem;
81
- border-radius: 0.25rem;
82
- margin-bottom: 2rem;
83
- }
84
-
85
- #payment-methods {
86
- margin-bottom: 1rem;
87
- }
88
-
89
- #agreement {
90
- margin-bottom: 1rem;
91
- }
92
-
93
- #payment-button {
94
- width: 100%;
95
- padding: 0.75rem;
96
- font-size: 1.1rem;
97
- font-weight: bold;
98
- }
99
-
100
- .btn {
101
- display: inline-block;
102
- padding: 0.375rem 0.75rem;
103
- margin-bottom: 0;
104
- font-size: 1rem;
105
- font-weight: 400;
106
- line-height: 1.5;
107
- text-align: center;
108
- text-decoration: none;
109
- vertical-align: middle;
110
- cursor: pointer;
111
- border: 1px solid transparent;
112
- border-radius: 0.25rem;
113
- }
114
-
115
- .btn-primary {
116
- color: #fff;
117
- background-color: #007bff;
118
- border-color: #007bff;
119
- }
120
-
121
- .btn-primary:hover {
122
- background-color: #0056b3;
123
- border-color: #004085;
124
- }
125
-
126
- .btn-secondary {
127
- color: #fff;
128
- background-color: #6c757d;
129
- border-color: #6c757d;
130
- }
131
-
132
- .alert {
133
- padding: 0.75rem 1.25rem;
134
- margin-bottom: 1rem;
135
- border: 1px solid transparent;
136
- border-radius: 0.25rem;
137
- }
138
-
139
- .alert-danger {
140
- color: #721c24;
141
- background-color: #f8d7da;
142
- border-color: #f5c6cb;
143
- }
144
-
145
- .actions {
146
- margin-top: 2rem;
147
- }
148
- </style>
@@ -46,48 +46,3 @@
46
46
  </tbody>
47
47
  </table>
48
48
 
49
- <script>
50
- function payment_status_class(status) {
51
- switch(status) {
52
- case 'confirmed': return 'bg-success';
53
- case 'pending': return 'bg-warning';
54
- case 'cancelled': return 'bg-secondary';
55
- case 'failed': return 'bg-danger';
56
- default: return 'bg-light';
57
- }
58
- }
59
-
60
- function payment_status_text(status) {
61
- switch(status) {
62
- case 'confirmed': return '완료';
63
- case 'pending': return '대기중';
64
- case 'cancelled': return '취소됨';
65
- case 'failed': return '실패';
66
- default: return status;
67
- }
68
- }
69
- </script>
70
-
71
- <%%# Bootstrap CSS가 없다면 기본 스타일링 %>
72
- <style>
73
- .table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
74
- .table th, .table td { padding: 0.75rem; border-bottom: 1px solid #dee2e6; text-align: left; }
75
- .table thead th { border-bottom: 2px solid #dee2e6; }
76
- .table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0, 0, 0, 0.05); }
77
- .btn { display: inline-block; padding: 0.375rem 0.75rem; margin-bottom: 0; font-size: 1rem;
78
- font-weight: 400; line-height: 1.5; text-align: center; text-decoration: none;
79
- vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 0.25rem; }
80
- .btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; }
81
- .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; }
82
- .btn-outline-primary { color: #007bff; border-color: #007bff; }
83
- .btn-outline-danger { color: #dc3545; border-color: #dc3545; }
84
- .badge { display: inline-block; padding: 0.25em 0.4em; font-size: 75%; font-weight: 700;
85
- line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline;
86
- border-radius: 0.25rem; }
87
- .bg-success { background-color: #28a745 !important; color: white; }
88
- .bg-warning { background-color: #ffc107 !important; color: black; }
89
- .bg-secondary { background-color: #6c757d !important; color: white; }
90
- .bg-danger { background-color: #dc3545 !important; color: white; }
91
- .mb-3 { margin-bottom: 1rem; }
92
- .text-center { text-align: center; }
93
- </style>
@@ -24,19 +24,3 @@
24
24
  </div>
25
25
  <%% end %>
26
26
 
27
- <style>
28
- .form-group { margin-bottom: 1rem; }
29
- .form-control { display: block; width: 100%; padding: 0.375rem 0.75rem; font-size: 1rem;
30
- line-height: 1.5; color: #495057; background-color: #fff; background-clip: padding-box;
31
- border: 1px solid #ced4da; border-radius: 0.25rem; }
32
- .form-text { display: block; margin-top: 0.25rem; font-size: 0.875em; color: #6c757d; }
33
- .alert { position: relative; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; }
34
- .alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; }
35
- .actions { margin-top: 1rem; }
36
- .btn { display: inline-block; padding: 0.375rem 0.75rem; margin-bottom: 0; font-size: 1rem;
37
- font-weight: 400; line-height: 1.5; text-align: center; text-decoration: none;
38
- vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 0.25rem; }
39
- .btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; }
40
- .btn-secondary { color: #fff; background-color: #6c757d; border-color: #6c757d; }
41
- label { display: inline-block; margin-bottom: 0.5rem; font-weight: bold; }
42
- </style>
@@ -71,7 +71,7 @@ class PaymentsController < ApplicationController
71
71
  def set_payment
72
72
  @payment = Payment.find_by(id: params[:id])
73
73
  return if @payment
74
-
74
+
75
75
  redirect_to payments_path, alert: '결제 정보를 찾을 수 없습니다.'
76
76
  end
77
77
 
@@ -61,102 +61,3 @@
61
61
  </div>
62
62
  <%% end %>
63
63
 
64
- <script>
65
- function payment_status_class(status) {
66
- switch(status) {
67
- case 'confirmed': return 'bg-success';
68
- case 'pending': return 'bg-warning';
69
- case 'cancelled': return 'bg-secondary';
70
- case 'failed': return 'bg-danger';
71
- default: return 'bg-light';
72
- }
73
- }
74
-
75
- function payment_status_text(status) {
76
- switch(status) {
77
- case 'confirmed': return '결제완료';
78
- case 'pending': return '결제대기';
79
- case 'cancelled': return '결제취소';
80
- case 'failed': return '결제실패';
81
- default: return status;
82
- }
83
- }
84
- </script>
85
-
86
- <style>
87
- .payment-detail { margin-bottom: 2rem; }
88
-
89
- .card {
90
- position: relative;
91
- display: flex;
92
- flex-direction: column;
93
- min-width: 0;
94
- word-wrap: break-word;
95
- background-color: #fff;
96
- background-clip: border-box;
97
- border: 1px solid rgba(0,0,0,.125);
98
- border-radius: 0.25rem;
99
- }
100
-
101
- .card-header {
102
- padding: 0.75rem 1.25rem;
103
- margin-bottom: 0;
104
- background-color: rgba(0,0,0,.03);
105
- border-bottom: 1px solid rgba(0,0,0,.125);
106
- display: flex;
107
- justify-content: space-between;
108
- align-items: center;
109
- }
110
-
111
- .card-body { padding: 1.25rem; }
112
-
113
- .row { display: flex; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; }
114
- .col-sm-3 { flex: 0 0 25%; max-width: 25%; padding-right: 15px; padding-left: 15px; }
115
- .col-sm-9 { flex: 0 0 75%; max-width: 75%; padding-right: 15px; padding-left: 15px; }
116
-
117
- dl { margin-bottom: 1rem; }
118
- dt { font-weight: 700; }
119
- dd { margin-bottom: 0.5rem; margin-left: 0; }
120
-
121
- .badge {
122
- display: inline-block;
123
- padding: 0.25em 0.4em;
124
- font-size: 75%;
125
- font-weight: 700;
126
- line-height: 1;
127
- text-align: center;
128
- white-space: nowrap;
129
- vertical-align: baseline;
130
- border-radius: 0.25rem;
131
- }
132
-
133
- .bg-success { background-color: #28a745 !important; color: white; }
134
- .bg-warning { background-color: #ffc107 !important; color: black; }
135
- .bg-secondary { background-color: #6c757d !important; color: white; }
136
- .bg-danger { background-color: #dc3545 !important; color: white; }
137
-
138
- .actions { margin-top: 2rem; }
139
-
140
- .btn {
141
- display: inline-block;
142
- padding: 0.375rem 0.75rem;
143
- margin-right: 0.5rem;
144
- margin-bottom: 0;
145
- font-size: 1rem;
146
- font-weight: 400;
147
- line-height: 1.5;
148
- text-align: center;
149
- text-decoration: none;
150
- vertical-align: middle;
151
- cursor: pointer;
152
- border: 1px solid transparent;
153
- border-radius: 0.25rem;
154
- }
155
-
156
- .btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; }
157
- .btn-secondary { color: #fff; background-color: #6c757d; border-color: #6c757d; }
158
- .btn-danger { color: #fff; background-color: #dc3545; border-color: #dc3545; }
159
-
160
- .alert { padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; }
161
- .alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; }
162
- </style>
@@ -0,0 +1,263 @@
1
+ /* TossPayments 결제 페이지 공통 스타일 */
2
+
3
+ /* === 결제 정보 영역 === */
4
+ .payment-info {
5
+ background-color: #f8f9fa;
6
+ padding: 1rem;
7
+ border-radius: 0.25rem;
8
+ margin-bottom: 2rem;
9
+ }
10
+
11
+ /* === 결제 위젯 영역 === */
12
+ #payment-methods {
13
+ margin-bottom: 1rem;
14
+ }
15
+
16
+ #agreement {
17
+ margin-bottom: 1rem;
18
+ }
19
+
20
+ #payment-button {
21
+ width: 100%;
22
+ padding: 0.75rem;
23
+ font-size: 1.1rem;
24
+ font-weight: bold;
25
+ }
26
+
27
+ /* === 공통 버튼 스타일 === */
28
+ .btn {
29
+ display: inline-block;
30
+ padding: 0.375rem 0.75rem;
31
+ margin-bottom: 0;
32
+ font-size: 1rem;
33
+ font-weight: 400;
34
+ line-height: 1.5;
35
+ text-align: center;
36
+ text-decoration: none;
37
+ vertical-align: middle;
38
+ cursor: pointer;
39
+ border: 1px solid transparent;
40
+ border-radius: 0.25rem;
41
+ }
42
+
43
+ .btn-primary {
44
+ color: #fff;
45
+ background-color: #007bff;
46
+ border-color: #007bff;
47
+ }
48
+
49
+ .btn-primary:hover {
50
+ background-color: #0056b3;
51
+ border-color: #004085;
52
+ }
53
+
54
+ .btn-secondary {
55
+ color: #fff;
56
+ background-color: #6c757d;
57
+ border-color: #6c757d;
58
+ }
59
+
60
+ .btn-danger {
61
+ color: #fff;
62
+ background-color: #dc3545;
63
+ border-color: #dc3545;
64
+ }
65
+
66
+ .btn-sm {
67
+ padding: 0.25rem 0.5rem;
68
+ font-size: 0.875rem;
69
+ }
70
+
71
+ .btn-outline-primary {
72
+ color: #007bff;
73
+ border-color: #007bff;
74
+ }
75
+
76
+ .btn-outline-danger {
77
+ color: #dc3545;
78
+ border-color: #dc3545;
79
+ }
80
+
81
+ /* === 알림 메시지 === */
82
+ .alert {
83
+ padding: 0.75rem 1.25rem;
84
+ margin-bottom: 1rem;
85
+ border: 1px solid transparent;
86
+ border-radius: 0.25rem;
87
+ }
88
+
89
+ .alert-danger {
90
+ color: #721c24;
91
+ background-color: #f8d7da;
92
+ border-color: #f5c6cb;
93
+ }
94
+
95
+ /* === 배지 (상태 표시) === */
96
+ .badge {
97
+ display: inline-block;
98
+ padding: 0.25em 0.4em;
99
+ font-size: 75%;
100
+ font-weight: 700;
101
+ line-height: 1;
102
+ text-align: center;
103
+ white-space: nowrap;
104
+ vertical-align: baseline;
105
+ border-radius: 0.25rem;
106
+ }
107
+
108
+ .bg-success {
109
+ background-color: #28a745 !important;
110
+ color: white;
111
+ }
112
+
113
+ .bg-warning {
114
+ background-color: #ffc107 !important;
115
+ color: black;
116
+ }
117
+
118
+ .bg-secondary {
119
+ background-color: #6c757d !important;
120
+ color: white;
121
+ }
122
+
123
+ .bg-danger {
124
+ background-color: #dc3545 !important;
125
+ color: white;
126
+ }
127
+
128
+ /* === 테이블 스타일 (index.html.erb) === */
129
+ .table {
130
+ width: 100%;
131
+ border-collapse: collapse;
132
+ margin-top: 1rem;
133
+ }
134
+
135
+ .table th, .table td {
136
+ padding: 0.75rem;
137
+ border-bottom: 1px solid #dee2e6;
138
+ text-align: left;
139
+ }
140
+
141
+ .table thead th {
142
+ border-bottom: 2px solid #dee2e6;
143
+ }
144
+
145
+ .table-striped tbody tr:nth-of-type(odd) {
146
+ background-color: rgba(0, 0, 0, 0.05);
147
+ }
148
+
149
+ /* === 폼 스타일 (new.html.erb) === */
150
+ .form-group {
151
+ margin-bottom: 1rem;
152
+ }
153
+
154
+ .form-control {
155
+ display: block;
156
+ width: 100%;
157
+ padding: 0.375rem 0.75rem;
158
+ font-size: 1rem;
159
+ line-height: 1.5;
160
+ color: #495057;
161
+ background-color: #fff;
162
+ background-clip: padding-box;
163
+ border: 1px solid #ced4da;
164
+ border-radius: 0.25rem;
165
+ }
166
+
167
+ .form-text {
168
+ display: block;
169
+ margin-top: 0.25rem;
170
+ font-size: 0.875em;
171
+ color: #6c757d;
172
+ }
173
+
174
+ label {
175
+ display: inline-block;
176
+ margin-bottom: 0.5rem;
177
+ font-weight: bold;
178
+ }
179
+
180
+ /* === 카드 스타일 (show.html.erb) === */
181
+ .payment-detail {
182
+ margin-bottom: 2rem;
183
+ }
184
+
185
+ .card {
186
+ position: relative;
187
+ display: flex;
188
+ flex-direction: column;
189
+ min-width: 0;
190
+ word-wrap: break-word;
191
+ background-color: #fff;
192
+ background-clip: border-box;
193
+ border: 1px solid rgba(0,0,0,.125);
194
+ border-radius: 0.25rem;
195
+ }
196
+
197
+ .card-header {
198
+ padding: 0.75rem 1.25rem;
199
+ margin-bottom: 0;
200
+ background-color: rgba(0,0,0,.03);
201
+ border-bottom: 1px solid rgba(0,0,0,.125);
202
+ display: flex;
203
+ justify-content: space-between;
204
+ align-items: center;
205
+ }
206
+
207
+ .card-body {
208
+ padding: 1.25rem;
209
+ }
210
+
211
+ /* === 그리드 시스템 === */
212
+ .row {
213
+ display: flex;
214
+ flex-wrap: wrap;
215
+ margin-right: -15px;
216
+ margin-left: -15px;
217
+ }
218
+
219
+ .col-sm-3 {
220
+ flex: 0 0 25%;
221
+ max-width: 25%;
222
+ padding-right: 15px;
223
+ padding-left: 15px;
224
+ }
225
+
226
+ .col-sm-9 {
227
+ flex: 0 0 75%;
228
+ max-width: 75%;
229
+ padding-right: 15px;
230
+ padding-left: 15px;
231
+ }
232
+
233
+ /* === 설명 목록 === */
234
+ dl {
235
+ margin-bottom: 1rem;
236
+ }
237
+
238
+ dt {
239
+ font-weight: 700;
240
+ }
241
+
242
+ dd {
243
+ margin-bottom: 0.5rem;
244
+ margin-left: 0;
245
+ }
246
+
247
+ /* === 공통 액션 영역 === */
248
+ .actions {
249
+ margin-top: 2rem;
250
+ }
251
+
252
+ .btn + .btn {
253
+ margin-left: 0.5rem;
254
+ }
255
+
256
+ /* === 유틸리티 클래스 === */
257
+ .mb-3 {
258
+ margin-bottom: 1rem;
259
+ }
260
+
261
+ .text-center {
262
+ text-align: center;
263
+ }
@@ -0,0 +1,64 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ clientKey: String,
6
+ customerKey: String,
7
+ orderId: String,
8
+ orderName: String,
9
+ customerName: String,
10
+ amount: Number,
11
+ successUrl: String,
12
+ failUrl: String
13
+ }
14
+
15
+ static targets = ["paymentMethods", "agreement", "paymentButton"]
16
+
17
+ connect() {
18
+ this.initializeTossPayments()
19
+ }
20
+
21
+ async initializeTossPayments() {
22
+ if (!this.clientKeyValue) {
23
+ console.error('TossPayments 클라이언트 키가 설정되지 않았습니다.')
24
+ alert('결제 서비스를 초기화할 수 없습니다. 관리자에게 문의하세요.')
25
+ return
26
+ }
27
+
28
+ try {
29
+ // TossPayments 위젯 초기화
30
+ const tosspayments = TossPayments(this.clientKeyValue)
31
+ this.widgets = tosspayments.widgets({ customerKey: this.customerKeyValue })
32
+
33
+ // 결제 수단 렌더링
34
+ await this.widgets.renderPaymentMethods({
35
+ selector: `#${this.paymentMethodsTarget.id}`,
36
+ variantKey: 'DEFAULT'
37
+ })
38
+
39
+ // 약관 동의 렌더링
40
+ await this.widgets.renderAgreement({
41
+ selector: `#${this.agreementTarget.id}`
42
+ })
43
+ } catch (error) {
44
+ console.error('TossPayments 초기화 실패:', error)
45
+ alert('결제 서비스 초기화에 실패했습니다: ' + (error.message || error.toString()))
46
+ }
47
+ }
48
+
49
+ async requestPayment() {
50
+ try {
51
+ await this.widgets.requestPayment({
52
+ orderId: this.orderIdValue,
53
+ orderName: this.orderNameValue,
54
+ customerName: this.customerNameValue,
55
+ amount: this.amountValue,
56
+ successUrl: this.successUrlValue,
57
+ failUrl: this.failUrlValue
58
+ })
59
+ } catch (error) {
60
+ console.error('결제 요청 실패:', error)
61
+ alert('결제 요청에 실패했습니다: ' + (error.message || error.toString()))
62
+ }
63
+ }
64
+ }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Tosspayments2
4
4
  module Rails
5
- VERSION = '0.5.4'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tosspayments2-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucius Choi
@@ -67,6 +67,8 @@ files:
67
67
  - lib/generators/tosspayments2/install/templates/payments_controller.rb
68
68
  - lib/generators/tosspayments2/install/templates/payments_helper.rb
69
69
  - lib/generators/tosspayments2/install/templates/show.html.erb
70
+ - lib/generators/tosspayments2/install/templates/tosspayments.css
71
+ - lib/generators/tosspayments2/install/templates/tosspayments_checkout_controller.js
70
72
  - lib/tosspayments2/rails.rb
71
73
  - lib/tosspayments2/rails/callback_verifier.rb
72
74
  - lib/tosspayments2/rails/client.rb