Class: PaymentMethod::GmoPg

Inherits:
Gateway
  • Object
show all
Includes:
Spree::PaymentMethod::AutoCaptureDigitalConcern
Defined in:
app/models/spree/payment_method/gmo_pg.rb

Constant Summary collapse

GMO_PG_CARD_NOT_FOUND_ERRORS =

GMO-PGでカードが既に削除済みの場合のエラーパターン

/not found|404|E01390007/i

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.persist_3ds_redirect_data(payment, gmo_pg_data) ⇒ Object

3DS認証情報を payment に保存し、コールバック後に unprocessed_payments で 再処理できるよう checkout 状態に戻す。 process_payment_with_3ds は process_payments! の with_lock トランザクション内で実行され、 そこで保存しても PaymentRedirectRequired の伝播でロールバックされる。そのため保存は トランザクション外(CheckoutController の rescue)からこのメソッドを呼んで行う。



21
22
23
24
25
26
27
28
29
30
# File 'app/models/spree/payment_method/gmo_pg.rb', line 21

def self.persist_3ds_redirect_data(payment, gmo_pg_data)
  return if payment.blank? || gmo_pg_data.blank?

  # ロールバック後の DB の値に合わせてから保存する
  payment.reload
  payment.[:gmo_pg] = gmo_pg_data
  payment.save!
  # update_column で state_machine の検証をバイパスして checkout に戻す
  payment.update_column(:state, 'checkout') unless payment.checkout?
end

Instance Method Details

#authorize(money, source, gateway_options) ⇒ Object

オーソリ(仮売上)を実行 登録済みカードの場合は "member_id|card_seq" 形式に変換してから実行 3DS2.0対応: 1回目はEntryTran+ExecTran、2回目はSecureTran2を実行



73
74
75
# File 'app/models/spree/payment_method/gmo_pg.rb', line 73

def authorize(money, source, gateway_options)
  process_payment_with_3ds(:authorize, money, source, gateway_options)
end

#can_capture?(payment) ⇒ Boolean

Returns:

  • (Boolean)


52
53
54
# File 'app/models/spree/payment_method/gmo_pg.rb', line 52

def can_capture?(payment)
  payment.pending? || payment.checkout?
end

#cancel(response_code, payment = nil) ⇒ ActiveMerchant::Billing::Response

支払いをキャンセルします 注文キャンセル時に Order#after_cancel から自動的に呼び出されます

Parameters:

  • response_code (String)

    認証コード ("access_id|access_pass|order_id"形式)

  • payment (Spree::Payment) (defaults to: nil)

    支払いオブジェクト (gateway_optionsの取得に使用)

Returns:

  • (ActiveMerchant::Billing::Response)

    void実行結果



211
212
213
214
# File 'app/models/spree/payment_method/gmo_pg.rb', line 211

def cancel(response_code, payment = nil)
  # ActiveMerchantのvoidメソッドを呼び出してGMO-PGのオーソリをキャンセル
  provider.void(response_code, payment&.gateway_options || {})
end

#create_profile(payment) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'app/models/spree/payment_method/gmo_pg.rb', line 84

def create_profile(payment)
  return unless payment.source.gateway_payment_profile_id.present?

  source = payment.source
  token = source.gateway_payment_profile_id

  # すでに登録済みカード(member_id + CardSeq両方ある)の場合はスキップ
  if source.gateway_customer_profile_id.present? && source.gateway_payment_profile_id.present?
    Rails.logger.info "Using existing registered card (member_id: #{source.gateway_customer_profile_id}, CardSeq: #{source.gateway_payment_profile_id})"
    return
  end

  # ゲスト購入の場合は、トークンをそのまま保存して終了
  # SaveMember/SaveCard APIは呼ばない
  if payment.order.user.blank?
    Rails.logger.info "Guest checkout: Token saved without card registration (token: #{token[0..10]}...)"
    return  # トークンは既にgateway_payment_profile_idに入っているので何もしない
  end

  # ログイン済みユーザーの場合は既存の処理(#692の実装)
  # トークンから会員登録とカード登録を実行
  member_id = payment.order.user_id.to_s
  Rails.logger.info "Logged-in user checkout: Registering card with GMO-PG (member_id: #{member_id})"

  # ActiveMerchantのstore メソッドを呼び出し
  # store(payment, options = {})
  # paymentにはトークンを渡す
  response = provider.store(
    token,
    {
      member_id: member_id,
      order_id: payment.order.number
    }
  )

  if response.success?
    # response.authorizationから member_id と CardSeq を取得
    # authorization は "member_id|card_seq" の形式
    member_id_from_response, card_seq = response.authorization.split('|')

    # GatewayCustomerを作成または取得
    self.gateway_customers.find_or_create_by(
      user: payment.order.user
    ) do |gc|
      gc.profile_id = member_id
    end

    # GMOから返却されたcard_seqが既に他のレコードに存在するかチェック
    existing_card = payment.order.user.credit_cards.where(
      payment_method_id: self.id,
      gateway_customer_profile_id: member_id,
      gateway_payment_profile_id: card_seq
    ).where.not(id: source.id).first

    if existing_card
      # 既存カードがある場合 = GMOで更新が行われた = 情報を統合
      Rails.logger.info "GMO updated existing card (CardSeq: #{card_seq}), merging records..."

      # 既存カードの情報を更新(有効期限、名義など)
      # defaultフラグは変更しない(ユーザーが既に選択している可能性があるため)
      existing_card.update!(
        month: source.month,
        year: source.year,
        name: source.name
        # last_digits, cc_type は変わらないはず(同じカード番号なので)
        # default は変更しない
      )

      # 新規作成したsourceレコードは不要なので削除
      source.destroy

      # paymentのsource_idを既存カードに変更
      # update_columnsを使ってafter_saveコールバックをスキップ(無限ループ防止)
      payment.update_columns(source_id: existing_card.id, source_type: existing_card.class.name)

      Rails.logger.info "Updated existing card record (id: #{existing_card.id})"
    else
      # 既存カードがない場合 = 新規登録
      source.update!(
        gateway_payment_profile_id: card_seq,
        gateway_customer_profile_id: member_id
      )

      Rails.logger.info "Card registered successfully (CardSeq: #{card_seq})"
    end
  else
    # エラーの場合は例外を発生させる
    raise Spree::Core::GatewayError.new(response.message)
  end
rescue ActiveMerchant::ConnectionError => e
  raise Spree::Core::GatewayError.new(e.message)
end

#credit(money, source, authorization, options = {}) ⇒ ActiveMerchant::Billing::Response

返金を実行します payment_profiles_supported? が true のため、Spreeは4つの引数で呼び出します

Parameters:

  • money (Integer)

    返金額(セント単位)

  • source (Spree::CreditCard)

    カード情報(使用しない)

  • authorization (String)

    認証コード ("access_id|access_pass|order_id"形式)

  • options (Hash) (defaults to: {})

    ゲートウェイオプション(originator: Spree::Refundオブジェクトを含む)

Returns:

  • (ActiveMerchant::Billing::Response)

    返金実行結果



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'app/models/spree/payment_method/gmo_pg.rb', line 245

def credit(money, source, authorization, options = {})
  # 返金元の情報を取得
  originator = options[:originator]

  unless originator&.respond_to?(:payment)
    raise Spree::Core::GatewayError, 'Refund originator is required'
  end

  payment = originator.payment

  # 現在の残額を計算
  # money, payment.captured_amount, refunds.sum(:amount)はすべてamount_in_centsの結果で同じ単位
  # JPYの場合でも、修正後のamount_in_centsは正しい値を返すので×100は不要
  captured_cents = payment.captured_amount.to_i
  # transaction_idが存在する返金のみをカウント(完了済みの返金)
  refunded_cents = payment.refunds.where.not(transaction_id: nil).sum(:amount).to_i
  remaining_cents = captured_cents - refunded_cents

  # 今回の返金後の残額
  new_remaining_cents = remaining_cents - money

  # 全額返金かどうか
  if new_remaining_cents <= 0
    # 全額返金 → void(キャンセル)
    Rails.logger.info "[GMO-PG Refund] Full refund: money=#{money}, remaining=#{remaining_cents}, authorization=#{authorization}"
    provider.void(authorization, options)
  else
    # 部分返金 → update_amount(減額)
    job_cd = payment.completed? ? 'CAPTURE' : 'AUTH'
    Rails.logger.info "[GMO-PG Refund] Partial refund: money=#{money}, new_remaining=#{new_remaining_cents}, job_cd=#{job_cd}, authorization=#{authorization}"
    provider.update_amount(new_remaining_cents, authorization, options.merge(job_cd: job_cd))
  end
end

#custom_form_fields_partial_nameObject



56
57
58
# File 'app/models/spree/payment_method/gmo_pg.rb', line 56

def custom_form_fields_partial_name
  'gmo_pg_form_fields'
end

#disable_customer_profile(source) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/spree/payment_method/gmo_pg.rb', line 177

def disable_customer_profile(source)
  return unless source.is_a?(Spree::CreditCard)

  # gateway_payment_profile_idが無い場合(登録未完了)はDB側のみ削除
  if source.gateway_payment_profile_id.blank?
    source.destroy
    Rails.logger.info "Local credit card deleted (not registered with GMO-PG): #{source.id}"
    return
  end

  # GMO-PG側から削除
  # unstore の第1引数は "member_id|card_seq" の形式
  identification = "#{source.gateway_customer_profile_id}|#{source.gateway_payment_profile_id}"
  result = provider.unstore(identification)

  # GMO-PG側で既に削除済み(404)や成功の場合、DB側を削除
  if result.success? || result.message.to_s.match?(GMO_PG_CARD_NOT_FOUND_ERRORS)
    source.destroy
    Rails.logger.info "GMO-PG card deleted: #{source.gateway_payment_profile_id}"
  else
    # 削除失敗時はエラーを投げる
    Rails.logger.error "GMO-PG card deletion failed: #{result.message}"
    raise Spree::Core::GatewayError, result.message
  end
rescue ActiveMerchant::ConnectionError => e
  raise Spree::Core::GatewayError.new(e.message)
end

#method_typeObject



40
41
42
# File 'app/models/spree/payment_method/gmo_pg.rb', line 40

def method_type
  'gmo_pg'
end

#optionsObject



60
61
62
63
64
65
66
67
68
# File 'app/models/spree/payment_method/gmo_pg.rb', line 60

def options
  {
    shop_id: preferred_shop_id,
    shop_pass: preferred_shop_pass,
    site_id: preferred_site_id,
    site_pass: preferred_site_pass,
    test: preferred_test_mode
  }
end

#payment_profiles_supported?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'app/models/spree/payment_method/gmo_pg.rb', line 44

def payment_profiles_supported?
  true
end

#payment_source_classObject



36
37
38
# File 'app/models/spree/payment_method/gmo_pg.rb', line 36

def payment_source_class
  Spree::CreditCard
end

#provider_classObject



32
33
34
# File 'app/models/spree/payment_method/gmo_pg.rb', line 32

def provider_class
  ActiveMerchant::Billing::GmoPgGateway
end

#purchase(money, source, gateway_options) ⇒ Object

即時売上を実行 登録済みカードの場合は "member_id|card_seq" 形式に変換してから実行 3DS2.0対応: 1回目はEntryTran+ExecTran、2回目はSecureTran2を実行



80
81
82
# File 'app/models/spree/payment_method/gmo_pg.rb', line 80

def purchase(money, source, gateway_options)
  process_payment_with_3ds(:purchase, money, source, gateway_options)
end

#supports?(source) ⇒ Boolean

Returns:

  • (Boolean)


48
49
50
# File 'app/models/spree/payment_method/gmo_pg.rb', line 48

def supports?(source)
  source.is_a?(Spree::CreditCard)
end

#void(response_code, source = nil, options = {}) ⇒ ActiveMerchant::Billing::Response

支払いを無効化します(手動void用) 管理画面から手動でvoidする際に Payment#void_transaction! から呼び出されます payment_profiles_supported? が true のため、Spreeは3つの引数で呼び出します

Parameters:

  • response_code (String)

    認証コード ("access_id|access_pass|order_id"形式)

  • source (Spree::CreditCard, Hash) (defaults to: nil)

    カード情報、または2引数呼び出し時のoptions

  • options (Hash) (defaults to: {})

    ゲートウェイオプション(3引数呼び出し時のみ)

Returns:

  • (ActiveMerchant::Billing::Response)

    void実行結果



224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/models/spree/payment_method/gmo_pg.rb', line 224

def void(response_code, source = nil, options = {})
  # 2引数呼び出し(古いSpreeバージョンや直接呼び出し)の互換性対応
  # source が Hash の場合、それが実際の options
  if source.is_a?(Hash)
    options = source
    source = nil
  end

  # GMO-PG の void は authorization と options のみを使用
  # source (CreditCard) は不要なので無視
  provider.void(response_code, options)
end