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 +4 -4
- data/CHANGELOG.md +19 -0
- data/lib/generators/tosspayments2/install/install_generator.rb +49 -1
- data/lib/generators/tosspayments2/install/templates/checkout.html.erb +16 -126
- data/lib/generators/tosspayments2/install/templates/index.html.erb +0 -45
- data/lib/generators/tosspayments2/install/templates/new.html.erb +0 -16
- data/lib/generators/tosspayments2/install/templates/payments_controller.rb +1 -1
- data/lib/generators/tosspayments2/install/templates/show.html.erb +0 -99
- data/lib/generators/tosspayments2/install/templates/tosspayments.css +263 -0
- data/lib/generators/tosspayments2/install/templates/tosspayments_checkout_controller.js +64 -0
- data/lib/tosspayments2/rails/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 497e3e4cb07bfd7f0d3c7c9b18ce50219f0a0be23e36b36155c56187bcaf600d
|
4
|
+
data.tar.gz: 74c3dd98db76fc485e5375fe637edb482393eeba757d7b4a701cae40612934d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
12
|
-
|
13
|
-
|
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>
|
@@ -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
|
+
}
|
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.
|
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
|