active_merchant_gmo_pg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,386 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe ActiveMerchant::Billing::GmoPgGateway do
6
+ let(:gateway) do
7
+ described_class.new(
8
+ shop_id: 'test_shop_id',
9
+ shop_pass: 'test_shop_pass',
10
+ site_id: 'test_site_id',
11
+ site_pass: 'test_site_pass',
12
+ test: true
13
+ )
14
+ end
15
+
16
+ describe '#initialize' do
17
+ context '必須パラメータが全て提供された場合' do
18
+ it 'ゲートウェイが正常に初期化される' do
19
+ expect(gateway).to be_a(described_class)
20
+ end
21
+ end
22
+
23
+ context '必須パラメータが不足している場合' do
24
+ it 'ArgumentErrorが発生する' do
25
+ expect do
26
+ described_class.new(shop_id: 'test_shop_id')
27
+ end.to raise_error(ArgumentError)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe 'クラス定数' do
33
+ it '正しいテストURLが設定されている' do
34
+ expect(described_class.test_url).to eq('https://pt01.mul-pay.jp')
35
+ end
36
+
37
+ it '正しい本番URLが設定されている' do
38
+ expect(described_class.live_url).to eq('https://p01.mul-pay.jp')
39
+ end
40
+
41
+ it 'サポート国が日本のみである' do
42
+ expect(described_class.supported_countries).to eq([ 'JP' ])
43
+ end
44
+
45
+ it 'デフォルト通貨が日本円である' do
46
+ expect(described_class.default_currency).to eq('JPY')
47
+ end
48
+
49
+ it 'サポートされるカードタイプが正しく設定されている' do
50
+ expect(described_class.supported_cardtypes).to include(:visa, :master, :american_express, :jcb, :diners_club)
51
+ end
52
+ end
53
+
54
+ describe '#store' do
55
+ it '新規会員とカードの登録をシミュレートする' do
56
+ expect(gateway).to respond_to(:store)
57
+ end
58
+ end
59
+
60
+ describe '#unstore' do
61
+ it 'カードの削除をシミュレートする' do
62
+ expect(gateway).to respond_to(:unstore)
63
+ end
64
+ end
65
+
66
+ describe '#authorize' do
67
+ it 'オーソリ処理をシミュレートする' do
68
+ expect(gateway).to respond_to(:authorize)
69
+ end
70
+ end
71
+
72
+ describe '#purchase' do
73
+ it '即時売上処理をシミュレートする' do
74
+ expect(gateway).to respond_to(:purchase)
75
+ end
76
+ end
77
+
78
+ describe '#capture' do
79
+ it '売上計上処理をシミュレートする' do
80
+ expect(gateway).to respond_to(:capture)
81
+ end
82
+ end
83
+
84
+ describe '#void' do
85
+ it '取消処理をシミュレートする' do
86
+ expect(gateway).to respond_to(:void)
87
+ end
88
+ end
89
+
90
+ describe '#amount' do
91
+ context '金額を整数に変換する' do
92
+ it '2000円をそのまま2000として返す' do
93
+ expect(gateway.send(:amount, 2000)).to eq(2000)
94
+ end
95
+
96
+ it '100円をそのまま100として返す' do
97
+ expect(gateway.send(:amount, 100)).to eq(100)
98
+ end
99
+
100
+ it '1円をそのまま1として返す' do
101
+ expect(gateway.send(:amount, 1)).to eq(1)
102
+ end
103
+
104
+ it '0円をそのまま0として返す' do
105
+ expect(gateway.send(:amount, 0)).to eq(0)
106
+ end
107
+
108
+ it '小数を整数に変換する(150.5 -> 150)' do
109
+ expect(gateway.send(:amount, 150.5)).to eq(150)
110
+ end
111
+
112
+ it '小数を整数に変換する(149.9 -> 149)' do
113
+ expect(gateway.send(:amount, 149.9)).to eq(149)
114
+ end
115
+ end
116
+ end
117
+
118
+ describe '#extract_error_code' do
119
+ context 'C系エラーコードの場合' do
120
+ it '42C010000からC01を抽出する' do
121
+ expect(gateway.send(:extract_error_code, '42C010000')).to eq('C01')
122
+ end
123
+
124
+ it '42C030000からC03を抽出する' do
125
+ expect(gateway.send(:extract_error_code, '42C030000')).to eq('C03')
126
+ end
127
+ end
128
+
129
+ context 'G系エラーコードの場合' do
130
+ it '42G020000からG02を抽出する' do
131
+ expect(gateway.send(:extract_error_code, '42G020000')).to eq('G02')
132
+ end
133
+
134
+ it '42G440000からG44を抽出する' do
135
+ expect(gateway.send(:extract_error_code, '42G440000')).to eq('G44')
136
+ end
137
+
138
+ it '42G650000からG65を抽出する' do
139
+ expect(gateway.send(:extract_error_code, '42G650000')).to eq('G65')
140
+ end
141
+ end
142
+
143
+ context 'E系エラーコードの場合' do
144
+ it 'E01010001からE01を抽出する' do
145
+ expect(gateway.send(:extract_error_code, 'E01010001')).to eq('E01')
146
+ end
147
+
148
+ it 'E01390002からE01を抽出する' do
149
+ expect(gateway.send(:extract_error_code, 'E01390002')).to eq('E01')
150
+ end
151
+ end
152
+
153
+ context 'M系エラーコードの場合' do
154
+ it 'M01002001からM01を抽出する' do
155
+ expect(gateway.send(:extract_error_code, 'M01002001')).to eq('M01')
156
+ end
157
+ end
158
+
159
+ context '未対応のフォーマットの場合' do
160
+ it '元のコードをそのまま返す' do
161
+ expect(gateway.send(:extract_error_code, 'UNKNOWN123')).to eq('UNKNOWN123')
162
+ end
163
+ end
164
+
165
+ context 'nilの場合' do
166
+ it 'nilを返す' do
167
+ expect(gateway.send(:extract_error_code, nil)).to be_nil
168
+ end
169
+ end
170
+ end
171
+
172
+ describe '#parse_error_message' do
173
+ let(:error_with_42g020000) do
174
+ error = double('GMO::Payment::APIError')
175
+ allow(error).to receive(:respond_to?).with(:error_info).and_return(true)
176
+ allow(error).to receive_messages(message: 'エラーが発生しました', error_info: { 'ErrInfo' => '42G020000' })
177
+ error
178
+ end
179
+
180
+ let(:error_with_42c010000) do
181
+ error = double('GMO::Payment::APIError')
182
+ allow(error).to receive(:respond_to?).with(:error_info).and_return(true)
183
+ allow(error).to receive_messages(message: 'エラーが発生しました', error_info: { 'ErrInfo' => '42C010000' })
184
+ error
185
+ end
186
+
187
+ let(:error_with_e01) do
188
+ error = double('GMO::Payment::APIError')
189
+ allow(error).to receive(:respond_to?).with(:error_info).and_return(true)
190
+ allow(error).to receive_messages(message: 'エラーが発生しました', error_info: { 'ErrInfo' => 'E01010001' })
191
+ error
192
+ end
193
+
194
+ let(:error_without_error_info) do
195
+ error = double('GMO::Payment::APIError')
196
+ allow(error).to receive(:message).and_return('元のエラーメッセージ')
197
+ allow(error).to receive(:respond_to?).with(:error_info).and_return(false)
198
+ error
199
+ end
200
+
201
+ context 'G系エラーコードの場合' do
202
+ it '42G020000から日本語エラーメッセージを抽出する' do
203
+ result = gateway.send(:parse_error_message, error_with_42g020000)
204
+ # gmo gemのエラーメッセージが返されることを確認
205
+ expect(result).not_to eq('エラーが発生しました')
206
+ expect(result).to be_a(String)
207
+ expect(result.length).to be > 10
208
+ end
209
+ end
210
+
211
+ context 'C系エラーコードの場合' do
212
+ it '42C010000から日本語エラーメッセージを抽出する' do
213
+ result = gateway.send(:parse_error_message, error_with_42c010000)
214
+ # gmo gemのエラーメッセージが返されることを確認
215
+ expect(result).not_to eq('エラーが発生しました')
216
+ expect(result).to be_a(String)
217
+ expect(result.length).to be > 10
218
+ end
219
+ end
220
+
221
+ context 'E系エラーコードの場合' do
222
+ it 'E01010001から日本語エラーメッセージを抽出する' do
223
+ result = gateway.send(:parse_error_message, error_with_e01)
224
+ # gmo gemのエラーメッセージが返されることを確認
225
+ expect(result).not_to eq('エラーが発生しました')
226
+ expect(result).to be_a(String)
227
+ expect(result.length).to be > 5
228
+ end
229
+ end
230
+
231
+ context 'error_infoがない場合' do
232
+ it '元のエラーメッセージを返す' do
233
+ result = gateway.send(:parse_error_message, error_without_error_info)
234
+ expect(result).to eq('元のエラーメッセージ')
235
+ end
236
+ end
237
+ end
238
+
239
+ describe '#secure_tran2' do
240
+ let(:access_id) { 'test_access_id_123' }
241
+ let(:access_pass) { 'test_access_pass_456' }
242
+ let(:order_id) { 'R824189000-POKG7GYM' }
243
+ let(:shop_client) { gateway.instance_variable_get(:@shop_client) }
244
+
245
+ context '3DS2.0認証後の決済が成功した場合' do
246
+ it '成功レスポンスとauthorizationを返す' do
247
+ api_response = {
248
+ 'OrderID' => order_id,
249
+ 'Forward' => '2a99662',
250
+ 'Method' => '1',
251
+ 'PayTimes' => '',
252
+ 'Approve' => '0123456',
253
+ 'TranID' => 'test_tran_id',
254
+ 'TranDate' => '20251203120000',
255
+ 'CheckString' => 'abc123def456'
256
+ }
257
+
258
+ allow(shop_client).to receive(:secure_tran_2)
259
+ .with(access_id: access_id, access_pass: access_pass)
260
+ .and_return(api_response)
261
+
262
+ response = gateway.secure_tran2(access_id, access_pass)
263
+
264
+ expect(response).to be_success
265
+ expect(response.message).to eq('3DS2.0認証後の決済に成功しました')
266
+ # authorizationが"access_id|access_pass|order_id"の形式で返されることを確認
267
+ expect(response.authorization).to eq("#{access_id}|#{access_pass}|#{order_id}")
268
+ expect(response.params['OrderID']).to eq(order_id)
269
+ expect(response.params['TranID']).to eq('test_tran_id')
270
+ end
271
+ end
272
+
273
+ context 'GMO-PG APIエラーが発生した場合' do
274
+ it 'エラーレスポンスを返す' do
275
+ error = GMO::Payment::APIError.new({})
276
+ allow(error).to receive_messages(respond_to?: true, message: 'エラーが発生しました', error_info: { 'ErrInfo' => 'E01010001' })
277
+
278
+ allow(shop_client).to receive(:secure_tran_2)
279
+ .with(access_id: access_id, access_pass: access_pass)
280
+ .and_raise(error)
281
+
282
+ response = gateway.secure_tran2(access_id, access_pass)
283
+
284
+ expect(response).not_to be_success
285
+ expect(response.authorization).to be_nil
286
+ end
287
+ end
288
+ end
289
+
290
+ describe '#update_amount' do
291
+ let(:authorization) { 'access_id_123|access_pass_456|order_789' }
292
+ let(:shop_client) { gateway.instance_variable_get(:@shop_client) }
293
+
294
+ context '金額変更が成功した場合' do
295
+ it '成功レスポンスを返す' do
296
+ api_response = {
297
+ 'AccessID' => 'access_id_123',
298
+ 'Forward' => '2a99662',
299
+ 'Approve' => '0123456',
300
+ 'TranID' => '789',
301
+ 'TranDate' => '20251203015959'
302
+ }
303
+
304
+ allow(shop_client).to receive(:post_request)
305
+ .with('ChangeTran.idPass', hash_including(
306
+ shop_id: 'test_shop_id',
307
+ shop_pass: 'test_shop_pass',
308
+ access_id: 'access_id_123',
309
+ access_pass: 'access_pass_456',
310
+ job_cd: 'CAPTURE',
311
+ amount: 500
312
+ ))
313
+ .and_return(api_response)
314
+
315
+ response = gateway.update_amount(500, authorization)
316
+
317
+ expect(response).to be_success
318
+ expect(response.message).to eq('金額変更に成功しました')
319
+ end
320
+ end
321
+
322
+ context 'job_cdがAUTHの場合' do
323
+ it '正しいjob_cdでAPIを呼び出す' do
324
+ api_response = {
325
+ 'AccessID' => 'access_id_123',
326
+ 'Forward' => '2a99662',
327
+ 'Approve' => '0123456',
328
+ 'TranID' => '789',
329
+ 'TranDate' => '20251203015959'
330
+ }
331
+
332
+ allow(shop_client).to receive(:post_request)
333
+ .with('ChangeTran.idPass', hash_including(job_cd: 'AUTH'))
334
+ .and_return(api_response)
335
+
336
+ response = gateway.update_amount(500, authorization, job_cd: 'AUTH')
337
+
338
+ expect(response).to be_success
339
+ end
340
+ end
341
+
342
+ context 'taxオプションが指定された場合' do
343
+ it 'taxパラメータを含めてAPIを呼び出す' do
344
+ api_response = {
345
+ 'AccessID' => 'access_id_123',
346
+ 'Forward' => '2a99662',
347
+ 'Approve' => '0123456',
348
+ 'TranID' => '789',
349
+ 'TranDate' => '20251203015959'
350
+ }
351
+
352
+ allow(shop_client).to receive(:post_request)
353
+ .with('ChangeTran.idPass', hash_including(tax: 50))
354
+ .and_return(api_response)
355
+
356
+ response = gateway.update_amount(500, authorization, tax: 50)
357
+
358
+ expect(response).to be_success
359
+ end
360
+ end
361
+
362
+ context '無効な認証情報の場合' do
363
+ it 'エラーレスポンスを返す' do
364
+ response = gateway.update_amount(500, nil)
365
+
366
+ expect(response).not_to be_success
367
+ expect(response.message).to eq('無効な認証情報です')
368
+ end
369
+ end
370
+
371
+ context 'GMO-PG APIエラーが発生した場合' do
372
+ it 'エラーレスポンスを返す' do
373
+ error = GMO::Payment::APIError.new({})
374
+ allow(error).to receive(:message).and_return('エラーが発生しました')
375
+
376
+ allow(shop_client).to receive(:post_request)
377
+ .with('ChangeTran.idPass', anything)
378
+ .and_raise(error)
379
+
380
+ response = gateway.update_amount(500, authorization)
381
+
382
+ expect(response).not_to be_success
383
+ end
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'active_merchant_gmo_pg'
5
+
6
+ RSpec.configure do |config|
7
+ config.expect_with :rspec do |expectations|
8
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
9
+ end
10
+
11
+ config.mock_with :rspec do |mocks|
12
+ mocks.verify_partial_doubles = true
13
+ end
14
+
15
+ config.shared_context_metadata_behavior = :apply_to_host_groups
16
+ config.filter_run_when_matching :focus
17
+ config.disable_monkey_patching!
18
+ config.warnings = true
19
+
20
+ config.default_formatter = 'doc' if config.files_to_run.one?
21
+
22
+ config.order = :random
23
+ Kernel.srand config.seed
24
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_merchant_gmo_pg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Be Agile
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemerchant
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.100.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.100.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: gmo
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ActiveMerchant payment gateway implementation for GMO-PG (GMO Payment
84
+ Gateway) supporting credit card processing with 3DS2.0
85
+ email:
86
+ - dev@be-agile.jp
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - MIT-LICENSE
92
+ - README.md
93
+ - Rakefile
94
+ - lib/active_merchant/billing/gateways/gmo_pg.rb
95
+ - lib/active_merchant/billing/gateways/gmo_pg_error_messages.rb
96
+ - lib/active_merchant_gmo_pg.rb
97
+ - lib/active_merchant_gmo_pg/engine.rb
98
+ - lib/active_merchant_gmo_pg/version.rb
99
+ - spec/active_merchant/billing/gmo_pg_gateway_spec.rb
100
+ - spec/spec_helper.rb
101
+ homepage: https://github.com/be-agile/active_merchant_gmo_pg
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '3.1'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubygems_version: 3.4.9
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: ActiveMerchant gateway for GMO Payment Gateway
124
+ test_files: []