Skip to content

Commit

Permalink
close #238 follow order state machine for payway_v2
Browse files Browse the repository at this point in the history
  • Loading branch information
theachoem committed Jan 23, 2025
1 parent 3c49421 commit 76357dc
Show file tree
Hide file tree
Showing 14 changed files with 273 additions and 303 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ def request_update_payment
render_serialized_payload { serialize_resource(order) }
else
payment = find_payment(order, params[:payment_number])
context = payment.request_update

if context.success? && context.error_message.blank?
processor = Vpago::PaymentProcessor.new(payment: payment)
processor.call

if processor.success?
render_serialized_payload { serialize_resource(order) }
else
render_error_payload(context.error_message)
render_error_payload(processor.error)
end
end
end
Expand Down
29 changes: 6 additions & 23 deletions app/controllers/spree/webhook/payways_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@ class PaywaysController < BaseController
# match via: [:get, :post]
# {"response"=>"{\"tran_id\":\"PE13LXT1\",\"status\":0"}"}
def v2_return
handler_service = v2_request_updater_service

return_callback_handler(handler_service)
return_callback_handler
end

def return
handler_service = request_updater_service

return_callback_handler(handler_service)
return_callback_handler
end

# https://vtenh.herokuapp.com/payways/continue?tran_id=P2W2S1LB
Expand All @@ -28,29 +24,16 @@ def continue

private

def v2_request_updater_service
::Vpago::PaywayV2::PaymentRequestUpdater
end

def request_updater_service
::Vpago::Payway::PaymentRequestUpdater
end

# the callback invoke by PAYWAY in case of success
def return_callback_handler(handler_service)
# pawway send get request with nothing
def return_callback_handler
return render plain: :ok if request.method == 'GET'

builder = Vpago::PaywayReturnOptionsBuilder.new(params: params)
payment = builder.payment

request_updater = handler_service.new(payment)
request_updater.call

order = payment.order
order = order.reload
processor = Vpago::PaymentProcessor.new(payment: payment)
processor.call

if order.paid? || payment.pending?
if processor.success?
render plain: :success
else
render plain: :failed, status: 400
Expand Down
2 changes: 1 addition & 1 deletion app/models/spree/gateway/payway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def process(_money, _source, gateway_options)
ActiveMerchant::Billing::Response.new(true, 'Order created')
end

def cancel(_response_code)
def cancel(_response_code, _payment)
# we can use this to send request to payment gateway api to cancel the payment ( void )
# currently Payway does not support to cancel the gateway

Expand Down
170 changes: 133 additions & 37 deletions app/models/spree/gateway/payway_v2.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
module Spree
class Gateway::PaywayV2 < PaymentMethod
# preference :endpoint, :string
# preference :return_url, :string
# preference :continue_success_url, :string
preference :host, :string
preference :api_key, :string
preference :merchant_id, :string
Expand All @@ -12,25 +9,31 @@ class Gateway::PaywayV2 < PaymentMethod
preference :transaction_fee_percentage, :string
preference :public_key, :text

# Only enable one-click payments if spree_auth_devise is installed
# def self.allow_one_click_payments?
# Gem.loaded_specs.key?('spree_auth_devise')
# end

validates :preferred_public_key, presence: true, if: :require_public_key?

def require_public_key?
enable_pre_auth == true
end
validates :preferred_public_key, presence: true, if: :enable_pre_auth?

def payment_source_class
Spree::VpagoPaymentSource
end

# override
def payment_profiles_supported?
false
end

# override
def support_payout?
return false unless default_payout_profile.present? && default_payout_profile.receivable?

true
end

# override
def support_pre_auth?
return false unless enable_pre_auth?

true
end

def card_type
if Vpago::Payway::CARD_TYPES.index(preferences[:payment_option]).nil?
Vpago::Payway::CARD_TYPE_ABAPAY
Expand All @@ -39,43 +42,136 @@ def card_type
end
end

def payment_option_card?
preferences[:payment_option] == Vpago::Payway::CARD_TYPE_CARDS
# partial to render the gateway.
def method_type
'payway_v2'
end

def payment_option_aba?
preferences[:payment_option] == Vpago::Payway::CARD_TYPE_ABAPAY
# override
# authorize payment if pre-auth is enabled, otherwise purchase / complete immediately.
def auto_capture?
!enable_pre_auth?
end

# partial to render the gateway.
def method_type
'payway_v2'
# override
def capture(_amount, _response_code, gateway_options)
_, payment_number = gateway_options[:order_id].split('-')
payment = Spree::Payment.find_by(number: payment_number)

success = true
params = {}

if payment.support_pre_auth?
success, params[:pre_auth] = confirm_pre_auth(payment)
elsif payment.support_payout?
success, params[:payout] = confirm_payouts(payment)
end

if success
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Success', params)
else
ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Failed', params)
end
end

# Custom PaymentMethod/Gateway can redefine this method to check method
# availability for concrete order.
def available_for_order?(_order)
true
# override
# purchase is used when pre auth disabled
def purchase(_amount, _source, _gateway_options = {})
_, payment_number = gateway_options[:order_id].split('-')
payment = Spree::Payment.find_by(number: payment_number)

checker = check_transaction(payment)
payment.update(transaction_response: checker.json_response)

success = checker.success?
params = {}

success, params[:payout] = confirm_payouts(payment) if success && payment.support_payout?

if success
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Purchased', params, { authorization: checker.transaction_id })
else
ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Purchasing Failed', params, { authorization: checker.transaction_id, error_code: checker.status })
end
end

# force to purchase instead of authorize
def auto_capture?
true
# override
# authorize is used when pre auth enabled
def authorize(_amount, _source, gateway_options = {})
_, payment_number = gateway_options[:order_id].split('-')
payment = Spree::Payment.find_by(number: payment_number)

checker = check_transaction(payment)
payment.update(transaction_response: checker.json_response)

success = checker.success?
params = {}

if success
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Authorized', params, { authorization: checker.transaction_id })
else
ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Authorization Failed', params, { authorization: checker.transaction_id, error_code: checker.status })
end
end

# override
def void(_response_code, gateway_options)
_, payment_number = gateway_options[:order_id].split('-')
payment = Spree::Payment.find_by(number: payment_number)

return ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Payment has been voided.') unless payment.support_pre_auth?

canceler = Vpago::PaywayV2::PreAuthCanceler.new(payment)
canceler.call

if canceler.success?
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Pre-authorization successfully canceled.')
else
ActiveMerchant::Billing::Response.new(false, 'Payway Gateway: Failed to cancel pre-authorization.')
end
end

# override
def cancel(_response_code, _payment)
ActiveMerchant::Billing::Response.new(true, 'Payway Gateway: Payment has been canceled.')
end

private

def check_transaction(payment)
checker = Vpago::PaywayV2::TransactionStatus.new(payment)
checker.call
checker
end

def process(_money, _source, gateway_options)
Rails.logger.debug { "About to create payment for order #{gateway_options[:order_id]}" }
# First of all, invalidate all previous tranx orders to prevent multiple paid orders
# source.save!
ActiveMerchant::Billing::Response.new(true, 'Order created')
def confirm_pre_auth(payment)
completer = Vpago::PaywayV2::PreAuthCompleter.new(payment)
completer.call

pre_auth_params = { merchant_auth: completer.merchant_auth }
[completer.success?, pre_auth_params]
end

def cancel(_response_code)
# we can use this to send request to payment gateway api to cancel the payment ( void )
# currently Payway does not support to cancel the gateway
def confirm_payouts(payment)
expect_payout_total = payment.payouts.sum(:amount)
confirmed = payout_total(payment) == expect_payout_total
success = false

if confirmed
payment.payouts.find_each { |payout| payout.update!(state: :confirmed) }
success = true
end

payout_params = { payout: checker.payout_total, expect_payout: expect_payout_total }
[success, payout_params]
end

def payout_total(payment)
payouts_response = payment.transaction_response['payout']

return nil if payouts_response.nil? || !payouts_response.is_a?(Array) || payouts_response.empty?

# in our case don't do anything
ActiveMerchant::Billing::Response.new(true, 'Payway order has been cancelled.')
payouts_response.map { |payout| payout['amt'].to_f || 0 }.sum
end
end
end
Loading

0 comments on commit 76357dc

Please sign in to comment.