1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-13 23:48:55 +01:00
fab-manager/lib/pay_zen/service.rb

172 lines
6.4 KiB
Ruby

# frozen_string_literal: true
require 'payment/service'
require 'pay_zen/charge'
require 'pay_zen/order'
require 'pay_zen/item'
# PayZen payement gateway
module PayZen; end
## create remote objects on PayZen
class PayZen::Service < Payment::Service
def create_subscription(payment_schedule, order_id, *_args)
first_item = payment_schedule.ordered_items.first
order = PayZen::Order.new.get(order_id, operation_type: 'VERIFICATION')
client = PayZen::Charge.new
token_id = order['answer']['transactions'].first['paymentMethodToken']
coupon = payment_schedule.coupon
amount = coupon && coupon.validity_per_user == 'forever' ? first_item.amount : first_item.details['recurring'].to_i
params = {
amount: payzen_amount(amount),
effect_date: first_item.due_date.iso8601,
payment_method_token: token_id,
rrule: rrule(payment_schedule, first_item.due_date),
order_id: order_id
}
initial_amount = first_item.amount
initial_amount -= payment_schedule.wallet_amount if payment_schedule.wallet_amount
if initial_amount.zero?
effect_date = first_item.due_date + 1.month
params[:effect_date] = effect_date.iso8601
params[:rrule] = rrule(payment_schedule, effect_date, -1)
else
params[:initial_amount] = payzen_amount(initial_amount)
params[:initial_amount_number] = 1
end
pz_subscription = client.create_subscription(**params)
# 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!
end
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
def process_payment_schedule_item(payment_schedule_item)
pz_subscription = payment_schedule_item.payment_schedule.gateway_subscription.retrieve
if pz_subscription['answer']['cancelDate'] && Time.zone.parse(pz_subscription['answer']['cancelDate']) <= Time.current &&
pz_subscription['answer']['pastPaymentsNumber'] != pz_subscription['answer']['totalPaymentsNumber']
# the subscription was canceled by the gateway => notify & update the status
notify_payment_schedule_gateway_canceled(payment_schedule_item)
payment_schedule_item.update(state: 'gateway_canceled')
return
end
pz_order = payment_schedule_item.payment_schedule.gateway_order.retrieve
transaction = find_transaction_by_payment_schedule_item(pz_order['answer']['transactions'], payment_schedule_item)
return unless transaction
case transaction['status']
when 'PAID'
PaymentScheduleService.new.generate_invoice(payment_schedule_item,
payment_method: 'card',
payment_id: transaction['uuid'],
payment_type: 'PayZen::Transaction')
payment_schedule_item.update(state: 'paid', payment_method: 'card')
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
pgo.gateway_object = PayZen::Item.new('PayZen::Transaction', transaction['uuid'])
pgo.save!
when 'RUNNING'
notify_payment_schedule_item_failed(payment_schedule_item)
payment_schedule_item.update(state: transaction['detailedStatus'])
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
pgo.gateway_object = PayZen::Item.new('PayZen::Transaction', transaction['uuid'])
pgo.save!
else
notify_payment_schedule_item_error(payment_schedule_item)
payment_schedule_item.update(state: 'error')
end
end
def payzen_amount(amount)
currency = Setting.get('payzen_currency')
raise ConfigurationError, 'PayZen currency is not configured. Unable to process online payments.' if currency.nil?
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
def rrule(payment_schedule, first_date, offset = 0)
count = payment_schedule.payment_schedule_items.count + offset
by_month_day_part = case first_date.day
when 31
"BYMONTHDAY=28,29,30,31;BYSETPOS=-1"
when 30
"BYMONTHDAY=28,29,30;BYSETPOS=-1"
when 29
"BYMONTHDAY=28,29;BYSETPOS=-1"
else
"BYMONTHDAY=#{first_date.day}"
end
"RRULE:FREQ=MONTHLY;#{by_month_day_part};COUNT=#{count}"
end
def find_transaction_by_payment_schedule_item(transactions, payment_schedule_item)
due_date = payment_schedule_item.due_date.to_date
transactions.find do |tr|
next unless tr["operationType"] == "DEBIT"
expected_capture_date = Time.zone.parse(tr["transactionDetails"]["paymentMethodDetails"]["expectedCaptureDate"]).to_date
expected_capture_date.between?(due_date - 1.day, due_date + 1.day)
end
end
private
# @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
end