spree_gmo_pg 1.0.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 +7 -0
- data/.gitignore +5 -0
- data/Gemfile +5 -0
- data/LICENSE +673 -0
- data/README.md +58 -0
- data/app/controllers/spree/gmo_pg_callback_controller.rb +37 -0
- data/app/controllers/spree/payment_sources_controller.rb +47 -0
- data/app/controllers/spree_gmo_pg/checkout_controller_decorator.rb +40 -0
- data/app/javascript/spree_gmo_pg/controllers/gmo_pg_payment_controller.js +295 -0
- data/app/javascript/spree_gmo_pg/index.js +11 -0
- data/app/models/spree/payment_method/gmo_pg.rb +418 -0
- data/app/models/spree_gmo_pg/order_updater_decorator.rb +78 -0
- data/app/overrides/hide_preference_fields_for_payment_method_gmo_pg.rb +12 -0
- data/app/overrides/spree/gmo_pg_javascript_import.rb +28 -0
- data/app/views/spree/admin/payment_methods/custom_form_fields/_gmo_pg_form_fields.html.erb +63 -0
- data/app/views/spree/checkout/payment/_gmo_pg.html.erb +154 -0
- data/app/views/spree/checkout/payment/_saved_cards.html.erb +36 -0
- data/app/views/spree/gmo_pg_callback/callback.html.erb +51 -0
- data/app/views/spree/shared/_error.html.erb +3 -0
- data/config/importmap.rb +9 -0
- data/config/locales/en.yml +29 -0
- data/config/locales/ja.yml +29 -0
- data/config/routes.rb +10 -0
- data/lib/spree/payment_redirect_required.rb +23 -0
- data/lib/spree_gmo_pg/engine.rb +44 -0
- data/lib/spree_gmo_pg/version.rb +5 -0
- data/lib/spree_gmo_pg.rb +16 -0
- data/spec/controllers/spree/gmo_pg_callback_controller_spec.rb +46 -0
- data/spec/controllers/spree/payment_sources_controller_spec.rb +75 -0
- data/spec/factories/payment_method_factory.rb +19 -0
- data/spec/models/spree/order_updater_decorator_spec.rb +221 -0
- data/spec/models/spree/payment_method/gmo_pg_spec.rb +860 -0
- data/spec/spec_helper.rb +25 -0
- data/spree_gmo_pg.gemspec +32 -0
- metadata +152 -0
data/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SpreeGmoPg
|
|
2
|
+
|
|
3
|
+
`spree_gmo_pg` is a Spree extension that adds credit card payments through
|
|
4
|
+
[GMO Payment Gateway](https://www.gmo-pg.com/) (GMO-PG), one of the largest payment
|
|
5
|
+
providers in Japan.
|
|
6
|
+
|
|
7
|
+
Card details are tokenized in the browser by GMO's own JavaScript SDK, so raw card
|
|
8
|
+
numbers never reach your application.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Credit card payment via GMO-PG
|
|
13
|
+
- Tokenization in the browser using the GMO-PG Token SDK
|
|
14
|
+
- 3-D Secure 2.0, including the redirect-and-return flow
|
|
15
|
+
- Optional member management (saved cards) using GMO-PG site credentials
|
|
16
|
+
- Automatic capture for digital-only orders, via
|
|
17
|
+
[`spree_auto_capture_digital`](https://github.com/be-agile/spree_auto_capture_digital)
|
|
18
|
+
- Test mode that points at GMO's staging endpoints
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Add this line to your application's Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem 'spree_gmo_pg'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then run:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
bin/rails g spree_gmo_pg:install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Add the payment method from the Spree admin
|
|
38
|
+
(**Settings → Payment Methods → New**, provider `Spree::PaymentMethod::GmoPg`)
|
|
39
|
+
and fill in the credentials issued by GMO-PG:
|
|
40
|
+
|
|
41
|
+
| Setting | Meaning |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Shop ID / Shop Password | Identifies your shop. Required |
|
|
44
|
+
| Site ID / Site Password | Only needed if you want to store cards for members |
|
|
45
|
+
| Test mode | On by default. Uses GMO's staging endpoints and SDK |
|
|
46
|
+
|
|
47
|
+
Leave test mode on until GMO has approved your production account.
|
|
48
|
+
|
|
49
|
+
## Requirements
|
|
50
|
+
|
|
51
|
+
- Spree 5.3.6
|
|
52
|
+
- [`active_merchant_gmo_pg`](https://github.com/be-agile/active_merchant_gmo_pg) —
|
|
53
|
+
the ActiveMerchant gateway this extension builds on
|
|
54
|
+
- A GMO-PG merchant account
|
|
55
|
+
|
|
56
|
+
## Licence
|
|
57
|
+
|
|
58
|
+
AGPL-3.0-or-later. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
class GmoPgCallbackController < StoreController
|
|
5
|
+
skip_before_action :verify_authenticity_token, only: [ :callback ]
|
|
6
|
+
|
|
7
|
+
def callback
|
|
8
|
+
@order = Spree::Order.find_by(token: params[:token])
|
|
9
|
+
|
|
10
|
+
unless @order
|
|
11
|
+
Rails.logger.error "[GMO-PG 3DS Callback] Order not found: #{params[:token]}"
|
|
12
|
+
redirect_to spree.root_path, alert: 'ご注文が見つかりませんでした'
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
payment = @order.payments.valid.last
|
|
17
|
+
|
|
18
|
+
unless payment
|
|
19
|
+
Rails.logger.error "[GMO-PG 3DS Callback] Payment not found for order: #{@order.number}"
|
|
20
|
+
redirect_to spree.checkout_state_path(@order.state, token: @order.token)
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# GMO-PGから返ってきたAccessIDを検証
|
|
25
|
+
gmo_pg_data = payment.private_metadata[:gmo_pg] || {}
|
|
26
|
+
|
|
27
|
+
if params[:AccessID].present? && gmo_pg_data[:access_id] != params[:AccessID]
|
|
28
|
+
Rails.logger.warn "[GMO-PG 3DS Callback] AccessID mismatch! Expected: #{gmo_pg_data[:access_id]}, Got: #{params[:AccessID]}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Rails.logger.info "[GMO-PG 3DS Callback] Received callback for order: #{@order.number}"
|
|
32
|
+
|
|
33
|
+
# CheckoutController#updateに自動submitするHTMLを返す
|
|
34
|
+
render :callback, layout: false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
class PaymentSourcesController < Spree::StoreController
|
|
5
|
+
before_action :authenticate_user!
|
|
6
|
+
before_action :load_payment_source
|
|
7
|
+
|
|
8
|
+
def destroy
|
|
9
|
+
# 権限チェック: 自分のカードのみ削除可能
|
|
10
|
+
unless @payment_source.user_id == spree_current_user.id
|
|
11
|
+
return render turbo_stream: turbo_stream.update(
|
|
12
|
+
"saved-cards-#{@payment_source.payment_method_id}",
|
|
13
|
+
partial: 'spree/shared/error',
|
|
14
|
+
locals: { message: Spree.t('gmo_pg.unauthorized') }
|
|
15
|
+
), status: :forbidden
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
payment_method = @payment_source.payment_method
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
# disable_customer_profileでGMO-PG削除+DB削除を実行
|
|
22
|
+
payment_method.disable_customer_profile(@payment_source)
|
|
23
|
+
|
|
24
|
+
# 更新後のカード一覧を返す
|
|
25
|
+
@payment_sources = spree_current_user.payment_sources.where(payment_method: payment_method)
|
|
26
|
+
|
|
27
|
+
render turbo_stream: turbo_stream.update(
|
|
28
|
+
"saved-cards-#{payment_method.id}",
|
|
29
|
+
partial: 'spree/checkout/payment/saved_cards',
|
|
30
|
+
locals: { payment_method: payment_method, payment_sources: @payment_sources }
|
|
31
|
+
)
|
|
32
|
+
rescue Spree::Core::GatewayError => e
|
|
33
|
+
render turbo_stream: turbo_stream.update(
|
|
34
|
+
"saved-cards-#{payment_method.id}",
|
|
35
|
+
partial: 'spree/shared/error',
|
|
36
|
+
locals: { message: e.message }
|
|
37
|
+
), status: :unprocessable_entity
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def load_payment_source
|
|
44
|
+
@payment_source = Spree::CreditCard.includes(:payment_method).find(params[:id])
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreeGmoPg
|
|
4
|
+
module CheckoutControllerDecorator
|
|
5
|
+
# かつては他 engine と Spree::CheckoutControllerDecorator という同一定数を共有しており、
|
|
6
|
+
# ここで include + ActiveSupport::Concern を使うと共有モジュール全体が本体より後ろに入り、
|
|
7
|
+
# 他 engine の prepend(load_order 等)を無効化してしまうため prepend が必須だった。
|
|
8
|
+
# #1307 で engine ごとの名前空間に分離したのでその制約はなくなったが、他 engine の
|
|
9
|
+
# decorator と形を揃えて prepend + prepended フックのままにしている。
|
|
10
|
+
# @see https://github.com/be-agile/giga-repeat/issues/1307
|
|
11
|
+
def self.prepended(base)
|
|
12
|
+
# 3DSリダイレクトをハンドリング
|
|
13
|
+
base.around_action :handle_payment_redirect
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def handle_payment_redirect
|
|
19
|
+
yield # 通常のアクション実行
|
|
20
|
+
rescue Spree::PaymentRedirectRequired => e
|
|
21
|
+
# 3DSリダイレクトが必要な場合
|
|
22
|
+
Rails.logger.info "[CheckoutController] Redirecting to 3DS authentication: #{e.redirect_url}"
|
|
23
|
+
|
|
24
|
+
# process_payments! の with_lock トランザクションは本例外の伝播で既にロールバック済み。
|
|
25
|
+
# コールバックでの再処理に必要な Access 情報を、ここ(トランザクション外)で永続化する。
|
|
26
|
+
Spree::PaymentMethod::GmoPg.persist_3ds_redirect_data(e.payment, e.gmo_pg_data)
|
|
27
|
+
|
|
28
|
+
respond_to do |format|
|
|
29
|
+
format.html { redirect_to e.redirect_url, allow_other_host: true }
|
|
30
|
+
format.turbo_stream do
|
|
31
|
+
# Turboリクエストの場合はカスタムredirectアクションを使う
|
|
32
|
+
escaped_url = ERB::Util.html_escape(e.redirect_url)
|
|
33
|
+
render turbo_stream: "<turbo-stream action=\"redirect\" url=\"#{escaped_url}\"></turbo-stream>".html_safe
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Spree::CheckoutController.prepend(SpreeGmoPg::CheckoutControllerDecorator)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["numberInput", "expiryInput", "cvvInput", "nameInput", "token", "maskedCard", "errorContainer", "existingCardRadio", "newCardRadio", "newCardForm"]
|
|
5
|
+
static values = {
|
|
6
|
+
shopId: String,
|
|
7
|
+
testMode: { type: Boolean, default: true },
|
|
8
|
+
errorMessages: Object
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
// GMO-PG SDK初期化
|
|
13
|
+
if (typeof Multipayment !== 'undefined') {
|
|
14
|
+
// テストモードの設定
|
|
15
|
+
if (this.testModeValue) {
|
|
16
|
+
Multipayment.init(this.shopIdValue)
|
|
17
|
+
console.log('GMO-PG SDK initialized (TEST MODE) with shop_id:', this.shopIdValue)
|
|
18
|
+
} else {
|
|
19
|
+
Multipayment.init(this.shopIdValue)
|
|
20
|
+
console.log('GMO-PG SDK initialized (PRODUCTION MODE) with shop_id:', this.shopIdValue)
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
console.error('Multipayment SDK not loaded')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// フォーム送信をインターセプト
|
|
27
|
+
// bind した関数を保持しないと removeEventListener が効かないため this に退避する。
|
|
28
|
+
// capture phase(第3引数 true)で登録することで、同じ form の submit を listen する
|
|
29
|
+
// turbo-stream-form コントローラより先に実行され、トークン未取得時に
|
|
30
|
+
// stopImmediatePropagation で turbo-stream-form の送信を確実に抑止できる。
|
|
31
|
+
this.boundHandleSubmit = this.handleSubmit.bind(this)
|
|
32
|
+
const form = this.element.closest('form')
|
|
33
|
+
if (form) {
|
|
34
|
+
form.addEventListener('submit', this.boundHandleSubmit, true)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 初期状態でカード選択状態を処理
|
|
38
|
+
this.handleCardSelection()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
disconnect() {
|
|
42
|
+
const form = this.element.closest('form')
|
|
43
|
+
if (form && this.boundHandleSubmit) {
|
|
44
|
+
form.removeEventListener('submit', this.boundHandleSubmit, true)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
handleCardSelection() {
|
|
49
|
+
// 新規カード選択のラジオボタンが存在しない場合は何もしない
|
|
50
|
+
if (!this.hasNewCardRadioTarget) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const newCardSelected = this.newCardRadioTarget.checked
|
|
55
|
+
|
|
56
|
+
if (newCardSelected) {
|
|
57
|
+
this.showNewCardForm()
|
|
58
|
+
} else {
|
|
59
|
+
this.hideNewCardForm()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
showNewCardForm() {
|
|
64
|
+
if (this.hasNewCardFormTarget) {
|
|
65
|
+
this.newCardFormTarget.style.display = 'block'
|
|
66
|
+
this.enableNewCardInputs()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
hideNewCardForm() {
|
|
71
|
+
if (this.hasNewCardFormTarget) {
|
|
72
|
+
this.newCardFormTarget.style.display = 'none'
|
|
73
|
+
this.disableNewCardInputs()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
enableNewCardInputs() {
|
|
78
|
+
if (this.hasNewCardFormTarget) {
|
|
79
|
+
const inputs = this.newCardFormTarget.querySelectorAll('input')
|
|
80
|
+
inputs.forEach(input => {
|
|
81
|
+
input.disabled = false
|
|
82
|
+
if (input.hasAttribute('data-original-required')) {
|
|
83
|
+
input.required = true
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
disableNewCardInputs() {
|
|
90
|
+
if (this.hasNewCardFormTarget) {
|
|
91
|
+
const inputs = this.newCardFormTarget.querySelectorAll('input')
|
|
92
|
+
inputs.forEach(input => {
|
|
93
|
+
input.disabled = true
|
|
94
|
+
if (input.required) {
|
|
95
|
+
input.setAttribute('data-original-required', 'true')
|
|
96
|
+
input.required = false
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
handleSubmit(event) {
|
|
103
|
+
// このコントローラーの要素が非表示の場合(他の支払い方法が選択されている)は何もしない
|
|
104
|
+
if (this.element.offsetParent === null) {
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 既存カードが選択されている場合はトークン取得をスキップ
|
|
109
|
+
if (this.hasExistingCardRadioTarget) {
|
|
110
|
+
const existingCardSelected = Array.from(this.existingCardRadioTargets || []).some(radio => radio.checked)
|
|
111
|
+
if (existingCardSelected) {
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// カード情報が入力されているか確認
|
|
117
|
+
const hasCardInput = this.hasNumberInputTarget && this.numberInputTarget.value.trim() !== ''
|
|
118
|
+
|
|
119
|
+
// トークンが既にセットされていて、かつカード番号が空(既にクリア済み)なら送信続行
|
|
120
|
+
if (this.hasTokenTarget && this.tokenTarget.value && !hasCardInput) {
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// トークン未取得、またはカード情報が変更された場合はトークンを再取得
|
|
125
|
+
// stopImmediatePropagation で、同じ form の submit を listen する
|
|
126
|
+
// turbo-stream-form コントローラがトークン未取得のまま送信してしまうのを防ぐ。
|
|
127
|
+
// (capture phase で登録しているため turbo-stream-form より先に実行される)
|
|
128
|
+
event.preventDefault()
|
|
129
|
+
event.stopImmediatePropagation()
|
|
130
|
+
|
|
131
|
+
// エラーメッセージを非表示
|
|
132
|
+
this.hideError()
|
|
133
|
+
|
|
134
|
+
this.getToken()
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getToken() {
|
|
139
|
+
// 有効期限をMM/YYYY → YYMMに変換
|
|
140
|
+
const expiry = this.expiryInputTarget.value
|
|
141
|
+
const [month, year] = expiry.split('/')
|
|
142
|
+
const yymm = year ? year.slice(-2) + month : ''
|
|
143
|
+
|
|
144
|
+
const cardData = {
|
|
145
|
+
cardno: this.numberInputTarget.value.replace(/\s/g, ''),
|
|
146
|
+
expire: yymm,
|
|
147
|
+
securitycode: this.cvvInputTarget.value,
|
|
148
|
+
holdername: this.nameInputTarget.value,
|
|
149
|
+
tokennumber: '1'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('=== GMO-PG Token Request Debug ===')
|
|
153
|
+
console.log('Original expiry input:', expiry)
|
|
154
|
+
console.log('Converted expire (YYMM):', yymm)
|
|
155
|
+
console.log('Card data being sent:', {
|
|
156
|
+
cardno: cardData.cardno.replace(/./g, '*'), // マスク表示
|
|
157
|
+
expire: cardData.expire,
|
|
158
|
+
securitycode: '***',
|
|
159
|
+
holdername: cardData.holdername,
|
|
160
|
+
tokennumber: cardData.tokennumber
|
|
161
|
+
})
|
|
162
|
+
console.log('================================')
|
|
163
|
+
|
|
164
|
+
// GMO-PG SDKはbind()を使ったコールバックを受け付けないため、
|
|
165
|
+
// thisを保持するためにアロー関数でラップする
|
|
166
|
+
const self = this
|
|
167
|
+
Multipayment.getToken(cardData, (result) => {
|
|
168
|
+
self.handleTokenResponse(result)
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
handleTokenResponse(result) {
|
|
173
|
+
console.log('Token response:', result)
|
|
174
|
+
|
|
175
|
+
// resultCodeは数値または文字列で返ってくる可能性があるため、文字列に変換して比較
|
|
176
|
+
const resultCodeStr = String(result.resultCode)
|
|
177
|
+
console.log('resultCodeStr:', resultCodeStr, 'type:', typeof resultCodeStr)
|
|
178
|
+
|
|
179
|
+
if (resultCodeStr !== '000') {
|
|
180
|
+
console.log('Error detected! resultCode:', resultCodeStr)
|
|
181
|
+
const errorMessage = this.getErrorMessage(result.resultCode)
|
|
182
|
+
console.log('Error message:', errorMessage)
|
|
183
|
+
this.showError(errorMessage)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// トークンとマスク済みカード番号をセット
|
|
188
|
+
this.tokenTarget.value = result.tokenObject.token
|
|
189
|
+
|
|
190
|
+
// マスク済みカード番号から下4桁を抽出
|
|
191
|
+
const maskedCard = result.tokenObject.maskedCardNo
|
|
192
|
+
const lastDigits = maskedCard.replace(/\*/g, '').slice(-4)
|
|
193
|
+
this.maskedCardTarget.value = lastDigits
|
|
194
|
+
|
|
195
|
+
console.log('Token acquired successfully')
|
|
196
|
+
|
|
197
|
+
// 生カード情報をクリア(セキュリティ対策)
|
|
198
|
+
this.numberInputTarget.value = ''
|
|
199
|
+
this.cvvInputTarget.value = ''
|
|
200
|
+
|
|
201
|
+
// フォーム送信
|
|
202
|
+
// トークン取得が完了したのでフォームを送信する。
|
|
203
|
+
// form.submit() はネイティブ送信で submit イベントを発火しないため、
|
|
204
|
+
// capture phase で登録した handleSubmit が再度呼ばれて二重にトークン取得を
|
|
205
|
+
// 試みることがなく、確実に1回だけ送信できる。
|
|
206
|
+
const form = this.element.closest('form')
|
|
207
|
+
if (form) {
|
|
208
|
+
form.submit()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getErrorMessage(resultCode) {
|
|
213
|
+
// GMO-PGエラーコードに応じたメッセージ(i18n対応)
|
|
214
|
+
const codeStr = String(resultCode)
|
|
215
|
+
const prefix = codeStr.substring(0, 3)
|
|
216
|
+
const messages = this.errorMessagesValue
|
|
217
|
+
return messages[prefix] || `${messages['default']} (${codeStr})`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
showError(message) {
|
|
221
|
+
// エラーメッセージをフォーム上部に表示
|
|
222
|
+
if (this.hasErrorContainerTarget) {
|
|
223
|
+
this.errorContainerTarget.textContent = message
|
|
224
|
+
this.errorContainerTarget.style.display = 'block'
|
|
225
|
+
|
|
226
|
+
// フォーム上部にスムーズスクロール
|
|
227
|
+
this.errorContainerTarget.scrollIntoView({
|
|
228
|
+
behavior: 'smooth',
|
|
229
|
+
block: 'nearest'
|
|
230
|
+
})
|
|
231
|
+
} else {
|
|
232
|
+
// フォールバック: エラーコンテナがない場合はalertを使用
|
|
233
|
+
console.error('Error container not found! Using alert fallback.')
|
|
234
|
+
alert(message)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
hideError() {
|
|
239
|
+
// エラーメッセージを非表示
|
|
240
|
+
if (this.hasErrorContainerTarget) {
|
|
241
|
+
this.errorContainerTarget.style.display = 'none'
|
|
242
|
+
this.errorContainerTarget.textContent = ''
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
deleteCard(event) {
|
|
247
|
+
// カード削除処理
|
|
248
|
+
event.preventDefault()
|
|
249
|
+
|
|
250
|
+
const button = event.currentTarget
|
|
251
|
+
const url = button.dataset.url
|
|
252
|
+
const turboFrame = button.dataset.turboFrame
|
|
253
|
+
const confirmMessage = button.dataset.turboConfirm
|
|
254
|
+
|
|
255
|
+
// 確認ダイアログを表示
|
|
256
|
+
if (!confirm(confirmMessage)) {
|
|
257
|
+
console.log('Card deletion cancelled by user')
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.log('Deleting card:', url)
|
|
262
|
+
|
|
263
|
+
// CSRFトークンを取得
|
|
264
|
+
const csrfToken = document.querySelector('[name="csrf-token"]')?.content
|
|
265
|
+
|
|
266
|
+
// DELETEリクエストを送信
|
|
267
|
+
fetch(url, {
|
|
268
|
+
method: 'DELETE',
|
|
269
|
+
headers: {
|
|
270
|
+
'X-CSRF-Token': csrfToken,
|
|
271
|
+
'Accept': 'text/vnd.turbo-stream.html'
|
|
272
|
+
},
|
|
273
|
+
credentials: 'same-origin'
|
|
274
|
+
})
|
|
275
|
+
.then(response => {
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
throw new Error(`HTTP error! status: ${response.status}`)
|
|
278
|
+
}
|
|
279
|
+
return response.text()
|
|
280
|
+
})
|
|
281
|
+
.then(html => {
|
|
282
|
+
// Turbo Streamレスポンスを処理してDOMを更新
|
|
283
|
+
if (typeof Turbo !== 'undefined' && Turbo.renderStreamMessage) {
|
|
284
|
+
Turbo.renderStreamMessage(html)
|
|
285
|
+
console.log('Card deleted successfully')
|
|
286
|
+
} else {
|
|
287
|
+
console.error('Turbo is not available')
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
.catch(error => {
|
|
291
|
+
console.error('Card deletion failed:', error)
|
|
292
|
+
alert('カードの削除に失敗しました。もう一度お試しください。')
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { lazyLoadControllersFromManifest } from "spree/storefront/helpers/lazy_load_controllers_with_manifest"
|
|
2
|
+
|
|
3
|
+
const controllers = ["gmo-pg-payment"]
|
|
4
|
+
const manifest = {
|
|
5
|
+
"gmo-pg-payment": "spree_gmo_pg/controllers/gmo_pg_payment_controller"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
document.addEventListener('turbo:load', function() {
|
|
9
|
+
const application = window.Stimulus
|
|
10
|
+
lazyLoadControllersFromManifest(controllers, null, application, manifest)
|
|
11
|
+
});
|