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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<% if @order.state == 'confirm' %>
|
|
2
|
+
<!-- 確認画面: カード情報の確認表示のみ -->
|
|
3
|
+
<% payment = @order.payments.valid.last %>
|
|
4
|
+
<% if payment&.source %>
|
|
5
|
+
<div class="payment-summary p-3 border rounded text-sm">
|
|
6
|
+
<%= payment.source.cc_type.upcase %> **** <%= payment.source.last_digits %>
|
|
7
|
+
(<%= payment.source.month %>/<%= payment.source.year %>)
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% else %>
|
|
11
|
+
<!-- 支払い方法選択画面: 既存のフォーム表示 -->
|
|
12
|
+
<div class="payment-gateway"
|
|
13
|
+
data-controller="card-validation gmo-pg-payment"
|
|
14
|
+
data-gmo-pg-payment-shop-id-value="<%= payment_method.preferred_shop_id %>"
|
|
15
|
+
data-gmo-pg-payment-test-mode-value="<%= payment_method.preferred_test_mode %>"
|
|
16
|
+
data-gmo-pg-payment-error-messages-value="<%= {
|
|
17
|
+
'E01' => Spree.t('gmo_pg.errors.invalid_card_info'),
|
|
18
|
+
'E11' => Spree.t('gmo_pg.errors.card_declined'),
|
|
19
|
+
'E61' => Spree.t('gmo_pg.errors.invalid_security_code'),
|
|
20
|
+
'E91' => Spree.t('gmo_pg.errors.processing_error'),
|
|
21
|
+
'201' => Spree.t('gmo_pg.errors.invalid_card_number'),
|
|
22
|
+
'default' => Spree.t('gmo_pg.errors.token_acquisition_failed')
|
|
23
|
+
}.to_json %>">
|
|
24
|
+
|
|
25
|
+
<% param_prefix = "payment_source[#{payment_method.id}]" %>
|
|
26
|
+
|
|
27
|
+
<!-- エラーメッセージ表示エリア -->
|
|
28
|
+
<div class="alert alert-danger"
|
|
29
|
+
style="display: none;"
|
|
30
|
+
data-gmo-pg-payment-target="errorContainer"
|
|
31
|
+
role="alert">
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<% if @order.user && @order.user.payment_sources.where(payment_method: payment_method).any? %>
|
|
35
|
+
<%= turbo_frame_tag "saved-cards-#{payment_method.id}" do %>
|
|
36
|
+
<%= render 'spree/checkout/payment/saved_cards',
|
|
37
|
+
payment_method: payment_method,
|
|
38
|
+
payment_sources: @order.user.payment_sources.where(payment_method: payment_method) %>
|
|
39
|
+
<% end %>
|
|
40
|
+
|
|
41
|
+
<div class="or-divider mt-3 mb-3">
|
|
42
|
+
<%= radio_button_tag "order[existing_card]", "", true,
|
|
43
|
+
data: {
|
|
44
|
+
gmo_pg_payment_target: "newCardRadio",
|
|
45
|
+
action: "change->gmo-pg-payment#handleCardSelection"
|
|
46
|
+
},
|
|
47
|
+
class: "mr-2"
|
|
48
|
+
%>
|
|
49
|
+
<label class="text-sm"><%= Spree.t('gmo_pg.or_enter_new_card') %></label>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
|
|
53
|
+
<div data-gmo-pg-payment-target="newCardForm" class="payment-gateway-fields">
|
|
54
|
+
<!-- カード名義人 -->
|
|
55
|
+
<div class="form-group mb-3">
|
|
56
|
+
<label for="name_on_card_<%= payment_method.id %>" class="block text-xs text-neutral-600 mb-1">
|
|
57
|
+
<%= Spree.t(:name_on_card) %>
|
|
58
|
+
</label>
|
|
59
|
+
<%= text_field_tag "#{param_prefix}[name]", '',
|
|
60
|
+
id: "name_on_card_#{payment_method.id}",
|
|
61
|
+
data: { gmo_pg_payment_target: "nameInput" },
|
|
62
|
+
placeholder: Spree.t(:name_on_card),
|
|
63
|
+
class: 'text-input w-full',
|
|
64
|
+
required: true
|
|
65
|
+
%>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- カード番号 (card-validationが自動フォーマット) -->
|
|
69
|
+
<div class="form-group mb-3 relative">
|
|
70
|
+
<label for="card_number_<%= payment_method.id %>" class="block text-xs text-neutral-600 mb-1">
|
|
71
|
+
<%= Spree.t(:card_number) %>
|
|
72
|
+
</label>
|
|
73
|
+
<%= text_field_tag "#{param_prefix}[number]", '',
|
|
74
|
+
id: "card_number_#{payment_method.id}",
|
|
75
|
+
data: {
|
|
76
|
+
card_validation_target: "number",
|
|
77
|
+
gmo_pg_payment_target: "numberInput"
|
|
78
|
+
},
|
|
79
|
+
maxlength: 19,
|
|
80
|
+
autocomplete: "off",
|
|
81
|
+
placeholder: Spree.t(:card_number),
|
|
82
|
+
class: 'text-input w-full pr-10',
|
|
83
|
+
required: true
|
|
84
|
+
%>
|
|
85
|
+
<div class="absolute right-2 top-7"
|
|
86
|
+
data-card-validation-target="typeContainer"
|
|
87
|
+
id="credit-card-type-container-<%= payment_method.id %>">
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="flex gap-3">
|
|
92
|
+
<!-- 有効期限 (card-validationがMM/YYYYフォーマット) -->
|
|
93
|
+
<div class="form-group">
|
|
94
|
+
<label for="card_expiry_<%= payment_method.id %>" class="block text-xs text-neutral-600 mb-1">
|
|
95
|
+
<%= Spree.t(:expiration_date) %>
|
|
96
|
+
</label>
|
|
97
|
+
<%= text_field_tag "#{param_prefix}[expiry]", '',
|
|
98
|
+
id: "card_expiry_#{payment_method.id}",
|
|
99
|
+
class: 'text-input',
|
|
100
|
+
data: {
|
|
101
|
+
card_validation_target: "expiry",
|
|
102
|
+
gmo_pg_payment_target: "expiryInput"
|
|
103
|
+
},
|
|
104
|
+
placeholder: Spree.t(:card_expiration_placeholder),
|
|
105
|
+
required: true
|
|
106
|
+
%>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- CVV (card-validationが桁数調整) -->
|
|
110
|
+
<div class="form-group">
|
|
111
|
+
<label for="card_code_<%= payment_method.id %>" class="block text-xs text-neutral-600 mb-1">
|
|
112
|
+
<%= Spree.t(:cvv) %>
|
|
113
|
+
</label>
|
|
114
|
+
<%= text_field_tag "#{param_prefix}[verification_value]", '',
|
|
115
|
+
id: "card_code_#{payment_method.id}",
|
|
116
|
+
class: 'text-input',
|
|
117
|
+
data: {
|
|
118
|
+
card_validation_target: "cvv",
|
|
119
|
+
gmo_pg_payment_target: "cvvInput"
|
|
120
|
+
},
|
|
121
|
+
maxlength: 4,
|
|
122
|
+
pattern: '[0-9]*',
|
|
123
|
+
inputmode: 'numeric',
|
|
124
|
+
placeholder: Spree.t(:cvv),
|
|
125
|
+
required: true
|
|
126
|
+
%>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- トークン格納用 (GMO-PGトークン) -->
|
|
131
|
+
<%= hidden_field_tag "#{param_prefix}[gateway_payment_profile_id]", '',
|
|
132
|
+
data: { gmo_pg_payment_target: "token" }
|
|
133
|
+
%>
|
|
134
|
+
|
|
135
|
+
<!-- マスク済みカード番号格納用 -->
|
|
136
|
+
<%= hidden_field_tag "#{param_prefix}[last_digits]", '',
|
|
137
|
+
data: { gmo_pg_payment_target: "maskedCard" }
|
|
138
|
+
%>
|
|
139
|
+
|
|
140
|
+
<!-- カードタイプ -->
|
|
141
|
+
<%= hidden_field_tag "#{param_prefix}[cc_type]", '',
|
|
142
|
+
id: "cc_type_#{payment_method.id}",
|
|
143
|
+
data: { card_validation_target: "ccType" }
|
|
144
|
+
%>
|
|
145
|
+
|
|
146
|
+
<!-- カードアイコン(card-validationが自動表示) -->
|
|
147
|
+
<% credit_card_icons.each do |card| %>
|
|
148
|
+
<div id="credit-card-icon-<%= card %>" class="hidden">
|
|
149
|
+
<%= payment_method_icon_tag card, height: 32 %>
|
|
150
|
+
</div>
|
|
151
|
+
<% end %>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
<% end %>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div class="saved-cards mb-4">
|
|
2
|
+
<% if payment_sources.any? %>
|
|
3
|
+
<h3 class="text-sm font-semibold mb-2"><%= Spree.t('gmo_pg.saved_cards') %></h3>
|
|
4
|
+
|
|
5
|
+
<% payment_sources.each do |card| %>
|
|
6
|
+
<div class="saved-card-item mb-2 p-3 border rounded flex items-center justify-between">
|
|
7
|
+
<label class="flex items-center flex-1 cursor-pointer">
|
|
8
|
+
<%= radio_button_tag "order[existing_card]", card.id, false,
|
|
9
|
+
data: {
|
|
10
|
+
gmo_pg_payment_target: "existingCardRadio",
|
|
11
|
+
action: "change->gmo-pg-payment#handleCardSelection"
|
|
12
|
+
},
|
|
13
|
+
class: "mr-2"
|
|
14
|
+
%>
|
|
15
|
+
<span class="text-sm">
|
|
16
|
+
<%= card.cc_type.upcase %> **** <%= card.last_digits %>
|
|
17
|
+
(<%= card.month %>/<%= card.year %>)
|
|
18
|
+
</span>
|
|
19
|
+
</label>
|
|
20
|
+
|
|
21
|
+
<%= button_tag Spree.t('gmo_pg.delete_card'),
|
|
22
|
+
type: 'button',
|
|
23
|
+
class: 'btn-sm btn-danger ml-2',
|
|
24
|
+
data: {
|
|
25
|
+
turbo_method: :delete,
|
|
26
|
+
turbo_frame: "saved-cards-#{payment_method.id}",
|
|
27
|
+
turbo_confirm: Spree.t('gmo_pg.delete_card_confirm'),
|
|
28
|
+
action: 'click->gmo-pg-payment#deleteCard',
|
|
29
|
+
url: spree.payment_source_path(card)
|
|
30
|
+
} %>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% else %>
|
|
34
|
+
<p class="text-sm text-gray-500"><%= Spree.t('gmo_pg.no_saved_cards') %></p>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>決済処理中...</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
9
|
+
display: flex;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
align-items: center;
|
|
12
|
+
height: 100vh;
|
|
13
|
+
margin: 0;
|
|
14
|
+
background-color: #f5f5f5;
|
|
15
|
+
}
|
|
16
|
+
.loading {
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
.spinner {
|
|
20
|
+
border: 4px solid #f3f3f3;
|
|
21
|
+
border-top: 4px solid #3498db;
|
|
22
|
+
border-radius: 50%;
|
|
23
|
+
width: 40px;
|
|
24
|
+
height: 40px;
|
|
25
|
+
animation: spin 1s linear infinite;
|
|
26
|
+
margin: 0 auto 20px;
|
|
27
|
+
}
|
|
28
|
+
@keyframes spin {
|
|
29
|
+
0% { transform: rotate(0deg); }
|
|
30
|
+
100% { transform: rotate(360deg); }
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div class="loading">
|
|
36
|
+
<div class="spinner"></div>
|
|
37
|
+
<p>決済処理中です。しばらくお待ちください...</p>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<%= form_tag spree.update_checkout_path(state: @order.state, token: @order.token),
|
|
41
|
+
method: :patch,
|
|
42
|
+
id: 'gmo_pg_callback_form' do %>
|
|
43
|
+
<!-- 必要に応じてパラメータ追加 -->
|
|
44
|
+
<% end %>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
// 自動的にフォームをサブミット
|
|
48
|
+
document.getElementById('gmo_pg_callback_form').submit();
|
|
49
|
+
</script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Stimulusコントローラーエントリーポイント
|
|
2
|
+
pin 'spree_gmo_pg', to: 'spree_gmo_pg/index.js', preload: true
|
|
3
|
+
|
|
4
|
+
# Stimulusコントローラー
|
|
5
|
+
# Engine.rootは初期化時に使えないため、__dir__を使用
|
|
6
|
+
pin_all_from File.expand_path('../app/javascript/spree_gmo_pg/controllers', __dir__),
|
|
7
|
+
under: 'spree_gmo_pg/controllers',
|
|
8
|
+
to: 'spree_gmo_pg/controllers',
|
|
9
|
+
preload: false
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
en:
|
|
2
|
+
spree:
|
|
3
|
+
gmo_pg:
|
|
4
|
+
api_credentials: "API Credentials"
|
|
5
|
+
settings: "Settings"
|
|
6
|
+
shop_id: "Shop ID"
|
|
7
|
+
shop_id_hint: "Enter the Shop ID issued by GMO-PG"
|
|
8
|
+
shop_pass: "Shop Password"
|
|
9
|
+
shop_pass_hint: "Enter the Shop Password issued by GMO-PG"
|
|
10
|
+
site_id: "Site ID"
|
|
11
|
+
site_id_hint: "Enter the Site ID issued by GMO-PG (required for member management)"
|
|
12
|
+
site_pass: "Site Password"
|
|
13
|
+
site_pass_hint: "Enter the Site Password issued by GMO-PG (required for member management)"
|
|
14
|
+
test_mode: "Test Mode"
|
|
15
|
+
test_mode_hint: "Check this to connect to the test environment (pt01.mul-pay.jp). Make sure to turn this off in production"
|
|
16
|
+
saved_cards: "Saved Cards"
|
|
17
|
+
or_enter_new_card: "Or enter a new card"
|
|
18
|
+
delete_card: "Delete"
|
|
19
|
+
delete_card_confirm: "Are you sure you want to delete this card?"
|
|
20
|
+
card_deleted: "Card has been deleted"
|
|
21
|
+
unauthorized: "You are not authorized to delete this card"
|
|
22
|
+
no_saved_cards: "No saved cards"
|
|
23
|
+
errors:
|
|
24
|
+
token_acquisition_failed: "An error occurred while processing card information"
|
|
25
|
+
invalid_card_info: "Card information is invalid"
|
|
26
|
+
card_declined: "This card cannot be used"
|
|
27
|
+
invalid_security_code: "Security code is invalid"
|
|
28
|
+
processing_error: "An error occurred during processing"
|
|
29
|
+
invalid_card_number: "Card number is invalid"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
ja:
|
|
2
|
+
spree:
|
|
3
|
+
gmo_pg:
|
|
4
|
+
api_credentials: "API認証情報"
|
|
5
|
+
settings: "動作設定"
|
|
6
|
+
shop_id: "ショップID"
|
|
7
|
+
shop_id_hint: "GMO-PGから発行されたショップIDを入力してください"
|
|
8
|
+
shop_pass: "ショップパスワード"
|
|
9
|
+
shop_pass_hint: "GMO-PGから発行されたショップパスワードを入力してください"
|
|
10
|
+
site_id: "サイトID"
|
|
11
|
+
site_id_hint: "GMO-PGから発行されたサイトIDを入力してください(会員管理機能利用時)"
|
|
12
|
+
site_pass: "サイトパスワード"
|
|
13
|
+
site_pass_hint: "GMO-PGから発行されたサイトパスワードを入力してください(会員管理機能利用時)"
|
|
14
|
+
test_mode: "テストモード"
|
|
15
|
+
test_mode_hint: "チェックを入れるとテスト環境(pt01.mul-pay.jp)に接続します。本番環境では必ずオフにしてください"
|
|
16
|
+
saved_cards: "登録済みのカード"
|
|
17
|
+
or_enter_new_card: "または新規カードを入力"
|
|
18
|
+
delete_card: "削除"
|
|
19
|
+
delete_card_confirm: "このカードを削除してもよろしいですか?"
|
|
20
|
+
card_deleted: "カードを削除しました"
|
|
21
|
+
unauthorized: "このカードを削除する権限がありません"
|
|
22
|
+
no_saved_cards: "登録済みのカードはありません"
|
|
23
|
+
errors:
|
|
24
|
+
token_acquisition_failed: "カード情報の処理中にエラーが発生しました"
|
|
25
|
+
invalid_card_info: "カード情報が正しくありません"
|
|
26
|
+
card_declined: "このカードはご利用いただけません"
|
|
27
|
+
invalid_security_code: "セキュリティコードが正しくありません"
|
|
28
|
+
processing_error: "処理中にエラーが発生しました"
|
|
29
|
+
invalid_card_number: "カード番号が正しくありません"
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Spree::Core::Engine.routes.draw do
|
|
4
|
+
resources :payment_sources, only: [ :destroy ]
|
|
5
|
+
|
|
6
|
+
# 3DSコールバックエンドポイント
|
|
7
|
+
# GMO-PGのCallbackType=3(GET方式)に対応
|
|
8
|
+
# GET方式はSameSite=Lax cookieが送信されるため、セッション維持が可能
|
|
9
|
+
get '/gmo_pg/3ds_callback', to: 'gmo_pg_callback#callback', as: :gmo_pg_3ds_callback
|
|
10
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
# 3DS認証リダイレクトが必要な場合に投げる例外
|
|
5
|
+
# StandardErrorではなくExceptionから継承することで、
|
|
6
|
+
# state_machineのrescue StandardErrorをバイパスする
|
|
7
|
+
class PaymentRedirectRequired < Exception
|
|
8
|
+
attr_reader :redirect_url, :payment, :gmo_pg_data
|
|
9
|
+
|
|
10
|
+
# @param redirect_url [String] 3DS認証のリダイレクト先URL
|
|
11
|
+
# @param payment [Spree::Payment] 対象の決済
|
|
12
|
+
# @param gmo_pg_data [Hash] コールバックでの再処理に必要な access_id / access_pass 等
|
|
13
|
+
# この例外は process_payments! の with_lock トランザクション内で raise されるため、
|
|
14
|
+
# そこで metadata を save してもロールバックで消えてしまう。トランザクション外
|
|
15
|
+
# (CheckoutController の rescue)で永続化できるよう、必要情報を例外に積んで渡す。
|
|
16
|
+
def initialize(redirect_url, payment: nil, gmo_pg_data: nil)
|
|
17
|
+
@redirect_url = redirect_url
|
|
18
|
+
@payment = payment
|
|
19
|
+
@gmo_pg_data = gmo_pg_data
|
|
20
|
+
super("Payment redirect required: #{redirect_url}")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreeGmoPg
|
|
4
|
+
class Engine < Rails::Engine
|
|
5
|
+
require 'spree/core'
|
|
6
|
+
isolate_namespace Spree
|
|
7
|
+
engine_name 'spree_gmo_pg'
|
|
8
|
+
|
|
9
|
+
# Decoratorの読み込み
|
|
10
|
+
def self.activate
|
|
11
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
|
|
12
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Zeitwerk対応(overridesの除外)
|
|
17
|
+
initializer 'spree_gmo_pg.autoloader' do |app|
|
|
18
|
+
if Rails.autoloaders.zeitwerk_enabled?
|
|
19
|
+
Rails.autoloaders.main.ignore("#{root}/app/overrides")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# アセットパスの追加
|
|
24
|
+
initializer 'spree_gmo_pg.assets' do |app|
|
|
25
|
+
app.config.assets.paths << root.join('app/javascript')
|
|
26
|
+
app.config.assets.precompile += %w[spree_gmo_pg_manifest]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# importmapの読み込み
|
|
30
|
+
initializer 'spree_gmo_pg.importmap', before: 'importmap' do |app|
|
|
31
|
+
if defined?(Importmap)
|
|
32
|
+
app.config.importmap.paths << root.join('config/importmap.rb')
|
|
33
|
+
app.config.importmap.cache_sweepers << root.join('app/javascript')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# 決済方法の登録
|
|
38
|
+
config.after_initialize do |app|
|
|
39
|
+
app.config.spree.payment_methods << Spree::PaymentMethod::GmoPg
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
config.to_prepare(&method(:activate).to_proc)
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/spree_gmo_pg.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spree_core'
|
|
4
|
+
require 'deface'
|
|
5
|
+
|
|
6
|
+
# Spree::PaymentMethod::GmoPg はクラス本体で Spree::PaymentMethod::AutoCaptureDigitalConcern を
|
|
7
|
+
# include し(= クラス定義の評価時点で解決できていなければならない)、provider_class で
|
|
8
|
+
# ActiveMerchant::Billing::GmoPgGateway を返す。後者を定義するのは active_merchant_gmo_pg の
|
|
9
|
+
# lib エントリなので、gemspec の add_dependency だけでは足りずここで require する必要がある。
|
|
10
|
+
# @see https://github.com/be-agile/giga-repeat/issues/1307
|
|
11
|
+
require 'active_merchant_gmo_pg'
|
|
12
|
+
require 'spree_auto_capture_digital'
|
|
13
|
+
|
|
14
|
+
require 'spree_gmo_pg/version'
|
|
15
|
+
require 'spree_gmo_pg/engine'
|
|
16
|
+
require 'spree/payment_redirect_required'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Spree::GmoPgCallbackController, type: :controller do
|
|
6
|
+
routes { Spree::Core::Engine.routes }
|
|
7
|
+
|
|
8
|
+
describe "GET #callback" do
|
|
9
|
+
let(:order) { create(:order, token: 'test_token_123') }
|
|
10
|
+
let(:payment_method) { create(:payment_method_gmo_pg) }
|
|
11
|
+
let(:credit_card) { create(:credit_card, user: nil) }
|
|
12
|
+
let(:payment) { create(:payment, order: order, payment_method: payment_method, source: credit_card) }
|
|
13
|
+
|
|
14
|
+
context "Order が見つかる場合" do
|
|
15
|
+
before do
|
|
16
|
+
payment.update!(private_metadata: { gmo_pg: { access_id: 'stored_access_id', access_pass: 'stored_access_pass' } })
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "ビューをレンダリングする" do
|
|
20
|
+
get :callback, params: { token: order.token, AccessID: 'stored_access_id' }
|
|
21
|
+
|
|
22
|
+
expect(response).to have_http_status(:success)
|
|
23
|
+
expect(response).to render_template(:callback)
|
|
24
|
+
expect(assigns(:order)).to eq(order)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "Order が見つからない場合" do
|
|
29
|
+
it "root_path にリダイレクトする" do
|
|
30
|
+
get :callback, params: { token: 'invalid_token', AccessID: 'test_access_id', AccessPass: 'test_access_pass' }
|
|
31
|
+
|
|
32
|
+
expect(response).to redirect_to('/')
|
|
33
|
+
expect(flash[:alert]).to eq('ご注文が見つかりませんでした')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "Payment が見つからない場合" do
|
|
38
|
+
it "checkout_state_path にリダイレクトする" do
|
|
39
|
+
get :callback, params: { token: order.token, AccessID: 'test_access_id', AccessPass: 'test_access_pass' }
|
|
40
|
+
|
|
41
|
+
# Spree 5.x のルーティング形式: /checkout/:token/:state
|
|
42
|
+
expect(response).to redirect_to("/checkout/#{order.token}/#{order.state}")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Spree::PaymentSourcesController, type: :controller do
|
|
6
|
+
routes { Spree::Core::Engine.routes }
|
|
7
|
+
|
|
8
|
+
let(:user) { create(:user) }
|
|
9
|
+
let(:other_user) { create(:user) }
|
|
10
|
+
let(:payment_method) { create(:payment_method_gmo_pg) }
|
|
11
|
+
let(:credit_card) { create(:credit_card, user: user, payment_method: payment_method, gateway_payment_profile_id: 'card_seq_123') }
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
allow(controller).to receive_messages(spree_current_user: user, authenticate_user!: true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "DELETE #destroy" do
|
|
18
|
+
context "自分のカードを削除する場合" do
|
|
19
|
+
context "削除成功時" do
|
|
20
|
+
before do
|
|
21
|
+
# Stub GMO-PG API call
|
|
22
|
+
stub_request(:post, "https://pt01.mul-pay.jp/payment/DeleteCard.idPass")
|
|
23
|
+
.to_return(status: 200, body: "", headers: {})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "カードを削除し、更新後のカード一覧をturbo_streamで返す" do
|
|
27
|
+
delete :destroy, params: { id: credit_card.id }, format: :turbo_stream
|
|
28
|
+
expect(response).to have_http_status(:success)
|
|
29
|
+
expect(response.body).to include("turbo-stream")
|
|
30
|
+
expect(response.body).to include("saved-cards-#{payment_method.id}")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "削除失敗時" do
|
|
35
|
+
before do
|
|
36
|
+
# Stub GMO-PG API call to return error (GMO-PG returns HTTP 200 with error code in body)
|
|
37
|
+
stub_request(:post, "https://pt01.mul-pay.jp/payment/DeleteCard.idPass")
|
|
38
|
+
.to_return(
|
|
39
|
+
status: 200,
|
|
40
|
+
body: "ErrCode=E01&ErrInfo=E01390008",
|
|
41
|
+
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "エラーメッセージを返す" do
|
|
46
|
+
delete :destroy, params: { id: credit_card.id }, format: :turbo_stream
|
|
47
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
|
48
|
+
expect(response.body).to include("turbo-stream")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "他人のカードを削除しようとした場合" do
|
|
54
|
+
let(:other_card) { create(:credit_card, user: other_user, payment_method: payment_method) }
|
|
55
|
+
|
|
56
|
+
it "権限エラーを返す" do
|
|
57
|
+
delete :destroy, params: { id: other_card.id }, format: :turbo_stream
|
|
58
|
+
expect(response).to have_http_status(:forbidden)
|
|
59
|
+
expect(response.body).to include("turbo-stream")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "ログインしていない場合" do
|
|
64
|
+
before do
|
|
65
|
+
allow(controller).to receive(:spree_current_user).and_return(nil)
|
|
66
|
+
allow(controller).to receive(:authenticate_user!).and_call_original
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "認証エラーを返す" do
|
|
70
|
+
delete :destroy, params: { id: credit_card.id }, format: :turbo_stream
|
|
71
|
+
expect(response).to have_http_status(:redirect)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :payment_method_gmo_pg, class: 'Spree::PaymentMethod::GmoPg' do
|
|
5
|
+
name { 'GMO-PG Credit Card' }
|
|
6
|
+
active { true }
|
|
7
|
+
auto_capture { false }
|
|
8
|
+
|
|
9
|
+
preferred_shop_id { 'test_shop_id' }
|
|
10
|
+
preferred_shop_pass { 'test_shop_pass' }
|
|
11
|
+
preferred_site_id { 'test_site_id' }
|
|
12
|
+
preferred_site_pass { 'test_site_pass' }
|
|
13
|
+
preferred_test_mode { true }
|
|
14
|
+
|
|
15
|
+
after(:build) do |payment_method|
|
|
16
|
+
payment_method.stores << (Spree::Store.first || FactoryBot.create(:store))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|