2021-04-30 16:07:19 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'payment/service'
|
|
|
|
require 'pay_zen/charge'
|
|
|
|
require 'pay_zen/order'
|
2021-06-03 12:22:37 +02:00
|
|
|
require 'pay_zen/item'
|
2021-04-30 16:07:19 +02:00
|
|
|
|
|
|
|
# PayZen payement gateway
|
|
|
|
module PayZen; end
|
|
|
|
|
|
|
|
## create remote objects on PayZen
|
|
|
|
class PayZen::Service < Payment::Service
|
2023-02-14 13:10:58 +01:00
|
|
|
def create_subscription(payment_schedule, order_id, *_args)
|
2021-04-30 16:07:19 +02:00
|
|
|
first_item = payment_schedule.ordered_items.first
|
|
|
|
|
2021-06-01 12:20:02 +02:00
|
|
|
order = PayZen::Order.new.get(order_id, operation_type: 'VERIFICATION')
|
2021-04-30 16:07:19 +02:00
|
|
|
client = PayZen::Charge.new
|
2021-06-02 20:07:53 +02:00
|
|
|
token_id = order['answer']['transactions'].first['paymentMethodToken']
|
2021-04-30 16:07:19 +02:00
|
|
|
|
|
|
|
params = {
|
2021-10-18 12:14:51 +02:00
|
|
|
amount: payzen_amount(first_item.details['recurring'].to_i),
|
2021-06-02 20:07:53 +02:00
|
|
|
effect_date: first_item.due_date.iso8601,
|
|
|
|
payment_method_token: token_id,
|
2021-04-30 16:07:19 +02:00
|
|
|
rrule: rrule(payment_schedule),
|
|
|
|
order_id: order_id
|
|
|
|
}
|
2021-06-03 14:28:52 +02:00
|
|
|
unless first_item.details['adjustment']&.zero? && first_item.details['other_items']&.zero?
|
2021-10-18 12:14:51 +02:00
|
|
|
params[:initial_amount] = payzen_amount(first_item.amount)
|
2021-04-30 16:07:19 +02:00
|
|
|
params[:initial_amount_number] = 1
|
|
|
|
end
|
2023-04-05 10:19:07 +02:00
|
|
|
pz_subscription = client.create_subscription(**params)
|
2021-06-02 20:07:53 +02:00
|
|
|
|
|
|
|
# save payment token
|
|
|
|
pgo_tok = PaymentGatewayObject.new(
|
|
|
|
gateway_object_id: token_id,
|
|
|
|
gateway_object_type: 'PayZen::Token',
|
|
|
|
item: payment_schedule
|
|
|
|
)
|
|
|
|
pgo_tok.save!
|
|
|
|
|
|
|
|
# save payzen subscription
|
|
|
|
pgo_sub = PaymentGatewayObject.new(
|
|
|
|
gateway_object_id: pz_subscription['answer']['subscriptionId'],
|
|
|
|
gateway_object_type: 'PayZen::Subscription',
|
|
|
|
item: payment_schedule,
|
|
|
|
payment_gateway_object_id: pgo_tok.id
|
|
|
|
)
|
|
|
|
pgo_sub.save!
|
2021-04-30 16:07:19 +02:00
|
|
|
end
|
|
|
|
|
2022-01-03 17:13:35 +01:00
|
|
|
def cancel_subscription(payment_schedule)
|
|
|
|
pz_subscription = payment_schedule.gateway_subscription.retrieve
|
|
|
|
|
|
|
|
order_client = PayZen::Order.new
|
|
|
|
tr_client = PayZen::Transaction.new
|
|
|
|
|
|
|
|
# first, we cancel all running transactions
|
|
|
|
begin
|
|
|
|
order = order_client.get(pz_subscription['answer']['orderId'])
|
|
|
|
order['answer']['transactions'].select { |t| t['status'] == 'RUNNING' }.each do |t|
|
|
|
|
tr_res = tr_client.cancel_or_refund(t['uuid'], amount: t['amount'], currency: t['currency'], resolution_mode: 'CANCELLATION_ONLY')
|
|
|
|
raise "Cannot cancel transaction #{t['uuid']}" unless tr_res['answer']['detailedStatus'] == 'CANCELLED'
|
|
|
|
end
|
|
|
|
rescue PayzenError => e
|
|
|
|
raise e unless e.details['errorCode'] == 'PSP_010' # ignore if no order
|
|
|
|
end
|
|
|
|
|
|
|
|
# then, we cancel the subscription
|
|
|
|
begin
|
|
|
|
sub_client = PayZen::Subscription.new
|
|
|
|
res = sub_client.cancel(pz_subscription['answer']['subscriptionId'], pz_subscription['answer']['paymentMethodToken'])
|
|
|
|
rescue PayzenError => e
|
|
|
|
return true if e.details['errorCode'] == 'PSP_033' # recurring payment already canceled
|
|
|
|
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
res['answer']['responseCode'].zero?
|
|
|
|
end
|
|
|
|
|
2021-06-03 12:22:37 +02:00
|
|
|
def process_payment_schedule_item(payment_schedule_item)
|
2022-01-11 14:20:27 +01:00
|
|
|
pz_subscription = payment_schedule_item.payment_schedule.gateway_subscription.retrieve
|
2023-02-14 13:10:58 +01:00
|
|
|
if pz_subscription['answer']['cancelDate'] && Time.zone.parse(pz_subscription['answer']['cancelDate']) <= Time.current
|
2022-01-11 12:37:06 +01:00
|
|
|
# the subscription was canceled by the gateway => notify & update the status
|
|
|
|
notify_payment_schedule_gateway_canceled(payment_schedule_item)
|
2023-02-14 13:10:58 +01:00
|
|
|
payment_schedule_item.update(state: 'gateway_canceled')
|
2022-01-05 17:16:25 +01:00
|
|
|
return
|
|
|
|
end
|
2021-06-04 18:26:20 +02:00
|
|
|
pz_order = payment_schedule_item.payment_schedule.gateway_order.retrieve
|
2021-06-03 12:22:37 +02:00
|
|
|
transaction = pz_order['answer']['transactions'].last
|
2021-06-04 18:26:20 +02:00
|
|
|
return unless transaction_matches?(transaction, payment_schedule_item)
|
|
|
|
|
2023-02-14 13:10:58 +01:00
|
|
|
case transaction['status']
|
|
|
|
when 'PAID'
|
2021-06-04 18:26:20 +02:00
|
|
|
PaymentScheduleService.new.generate_invoice(payment_schedule_item,
|
|
|
|
payment_method: 'card',
|
|
|
|
payment_id: transaction['uuid'],
|
|
|
|
payment_type: 'PayZen::Transaction')
|
2023-02-14 13:10:58 +01:00
|
|
|
payment_schedule_item.update(state: 'paid', payment_method: 'card')
|
2021-06-04 18:26:20 +02:00
|
|
|
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
|
|
|
|
pgo.gateway_object = PayZen::Item.new('PayZen::Transaction', transaction['uuid'])
|
|
|
|
pgo.save!
|
2023-02-14 13:10:58 +01:00
|
|
|
when 'RUNNING'
|
2022-01-11 12:37:06 +01:00
|
|
|
notify_payment_schedule_item_failed(payment_schedule_item)
|
2023-02-14 13:10:58 +01:00
|
|
|
payment_schedule_item.update(state: transaction['detailedStatus'])
|
2021-06-04 18:26:20 +02:00
|
|
|
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
|
|
|
|
pgo.gateway_object = PayZen::Item.new('PayZen::Transaction', transaction['uuid'])
|
|
|
|
pgo.save!
|
|
|
|
else
|
2022-01-11 12:37:06 +01:00
|
|
|
notify_payment_schedule_item_error(payment_schedule_item)
|
2023-02-14 13:10:58 +01:00
|
|
|
payment_schedule_item.update(state: 'error')
|
2021-06-03 12:22:37 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-18 12:14:51 +02:00
|
|
|
def payzen_amount(amount)
|
|
|
|
currency = Setting.get('payzen_currency')
|
2023-04-05 09:46:00 +02:00
|
|
|
raise ConfigurationError, 'PayZen currency is not configured. Unable to process online payments.' if currency.nil?
|
|
|
|
|
2021-10-18 12:14:51 +02:00
|
|
|
return amount / 100 if zero_decimal_currencies.any? { |s| s.casecmp(currency).zero? }
|
|
|
|
return amount * 10 if three_decimal_currencies.any? { |s| s.casecmp(currency).zero? }
|
|
|
|
|
|
|
|
amount
|
|
|
|
end
|
|
|
|
|
2021-04-30 16:07:19 +02:00
|
|
|
private
|
|
|
|
|
|
|
|
def rrule(payment_schedule)
|
|
|
|
count = payment_schedule.payment_schedule_items.count
|
2021-06-02 20:07:53 +02:00
|
|
|
"RRULE:FREQ=MONTHLY;COUNT=#{count}"
|
2021-04-30 16:07:19 +02:00
|
|
|
end
|
2021-06-04 18:26:20 +02:00
|
|
|
|
|
|
|
# check if the given transaction matches the given PaymentScheduleItem
|
|
|
|
def transaction_matches?(transaction, payment_schedule_item)
|
2023-02-14 13:10:58 +01:00
|
|
|
transaction_date = Time.zone.parse(transaction['creationDate']).to_date
|
2021-06-04 18:26:20 +02:00
|
|
|
|
|
|
|
transaction['amount'] == payment_schedule_item.amount &&
|
|
|
|
transaction_date >= payment_schedule_item.due_date.to_date &&
|
|
|
|
transaction_date <= payment_schedule_item.due_date.to_date + 7.days
|
|
|
|
end
|
2021-10-18 12:14:51 +02:00
|
|
|
|
|
|
|
# @see https://payzen.io/en-EN/payment-file/ips/list-of-supported-currencies.html
|
|
|
|
def zero_decimal_currencies
|
|
|
|
%w[KHR JPY KRW XOF XPF]
|
|
|
|
end
|
|
|
|
|
|
|
|
def three_decimal_currencies
|
|
|
|
%w[KWD TND]
|
|
|
|
end
|
2021-04-30 16:07:19 +02:00
|
|
|
end
|