Merge pull request #1502 from internetee/1422-record-payment-method-and-failed-payments

Record every payment attempt
This commit is contained in:
Timo Võhmar 2020-03-06 13:52:58 +02:00 committed by GitHub
commit 444a07c62a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 798 additions and 342 deletions

View file

@ -60,7 +60,7 @@ module Admin
end
def bind_invoices
@bank_statement.bind_invoices
@bank_statement.bind_invoices(manual: true)
flash[:notice] = t('invoices_were_fully_binded') if @bank_statement.fully_binded?
flash[:warning] = t('invoices_were_partially_binded') if @bank_statement.partially_binded?

View file

@ -34,7 +34,7 @@ module Admin
end
def bind
if @bank_transaction.bind_invoice(params[:invoice_no])
if @bank_transaction.bind_invoice(params[:invoice_no], manual: true)
flash[:notice] = I18n.t('record_created')
redirect_to [:admin, @bank_transaction]
else

View file

@ -5,50 +5,51 @@ class Registrar
skip_authorization_check # actually anyone can pay, no problems at all
skip_before_action :authenticate_registrar_user!, :check_ip_restriction,
only: [:back, :callback]
before_action :check_supported_payment_method
before_action :check_supported_payment_method, only: [:pay]
def pay
invoice = Invoice.find(params[:invoice_id])
bank = params[:bank]
opts = {
return_url: registrar_return_payment_with_url(
bank, invoice_id: invoice
),
response_url: registrar_response_payment_with_url(
bank, invoice_id: invoice
)
}
@payment = ::PaymentOrders.create_with_type(bank, invoice, opts)
@payment.create_transaction
channel = params[:bank]
@payment_order = PaymentOrder.new_with_type(type: channel, invoice: invoice)
@payment_order.save
@payment_order.reload
@payment_order.return_url = registrar_return_payment_with_url(@payment_order)
@payment_order.response_url = registrar_response_payment_with_url(@payment_order)
@payment_order.save
@payment_order.reload
end
def back
invoice = Invoice.find(params[:invoice_id])
opts = { response: params }
@payment = ::PaymentOrders.create_with_type(params[:bank], invoice, opts)
if @payment.valid_response_from_intermediary? && @payment.settled_payment?
Rails.logger.info("User paid invoice ##{invoice.number} successfully")
@payment_order = PaymentOrder.find_by!(id: params[:payment_order])
@payment_order.update!(response: params.to_unsafe_h)
@payment.complete_transaction
if @payment_order.payment_received?
@payment_order.complete_transaction
if invoice.paid?
flash[:notice] = t(:pending_applied)
if @payment_order.invoice.paid?
flash[:notice] = t('.payment_successful')
else
flash[:alert] = t(:something_wrong)
flash[:alert] = t('.successful_payment_backend_error')
end
else
flash[:alert] = t(:something_wrong)
@payment_order.create_failure_report
flash[:alert] = t('.payment_not_received')
end
redirect_to registrar_invoice_path(invoice)
redirect_to registrar_invoice_path(@payment_order.invoice)
end
def callback
invoice = Invoice.find(params[:invoice_id])
opts = { response: params }
@payment = ::PaymentOrders.create_with_type(params[:bank], invoice, opts)
@payment_order = PaymentOrder.find_by!(id: params[:payment_order])
@payment_order.update!(response: params.to_unsafe_h)
if @payment.valid_response_from_intermediary? && @payment.settled_payment?
@payment.complete_transaction
if @payment_order.payment_received?
@payment_order.complete_transaction
else
@payment_order.create_failure_report
end
render status: 200, json: { status: 'ok' }
@ -57,13 +58,9 @@ class Registrar
private
def check_supported_payment_method
return if supported_payment_method?
raise StandardError.new("Not supported payment method")
end
return if PaymentOrder.supported_method?(params[:bank], shortname: true)
def supported_payment_method?
PaymentOrders::PAYMENT_METHODS.include?(params[:bank])
raise(StandardError, 'Not supported payment method')
end
end
end

View file

@ -86,7 +86,9 @@ class BankStatement < ApplicationRecord
status == FULLY_BINDED
end
def bind_invoices
bank_transactions.unbinded.each(&:autobind_invoice)
def bind_invoices(manual: false)
bank_transactions.unbinded.each do |transaction|
transaction.autobind_invoice(manual: manual)
end
end
end

View file

@ -13,6 +13,7 @@ class BankTransaction < ApplicationRecord
def binded_invoice
return unless binded?
account_activity.invoice
end
@ -30,31 +31,54 @@ class BankTransaction < ApplicationRecord
@registrar ||= Invoice.find_by(reference_no: parsed_ref_number)&.buyer
end
# For successful binding, reference number, invoice id and sum must match with the invoice
def autobind_invoice
def autobind_invoice(manual: false)
return if binded?
return unless registrar
return unless invoice
return unless invoice.payable?
create_activity(registrar, invoice)
channel = if manual
'admin_payment'
else
'system_payment'
end
create_internal_payment_record(channel: channel, invoice: invoice,
registrar: registrar)
end
def bind_invoice(invoice_no)
def create_internal_payment_record(channel: nil, invoice:, registrar:)
if channel.nil?
create_activity(invoice.buyer, invoice)
return
end
payment_order = PaymentOrder.new_with_type(type: channel, invoice: invoice)
payment_order.save!
if create_activity(registrar, invoice)
payment_order.paid!
else
payment_order.update(notes: 'Failed to create activity', status: 'failed')
end
end
def bind_invoice(invoice_no, manual: false)
if binded?
errors.add(:base, I18n.t('transaction_is_already_binded'))
return
end
invoice = Invoice.find_by(number: invoice_no)
@registrar = invoice.buyer
errors.add(:base, I18n.t('invoice_was_not_found')) unless invoice
validate_invoice_data(invoice)
return if errors.any?
unless invoice
errors.add(:base, I18n.t('invoice_was_not_found'))
return
end
create_internal_payment_record(channel: (manual ? 'admin_payment' : nil), invoice: invoice,
registrar: invoice.buyer)
end
def validate_invoice_data(invoice)
if invoice.paid?
errors.add(:base, I18n.t('invoice_is_already_binded'))
return
@ -65,23 +89,21 @@ class BankTransaction < ApplicationRecord
return
end
if invoice.total != sum
errors.add(:base, I18n.t('invoice_and_transaction_sums_do_not_match'))
return
end
create_activity(invoice.buyer, invoice)
errors.add(:base, I18n.t('invoice_and_transaction_sums_do_not_match')) if invoice.total != sum
end
def create_activity(registrar, invoice)
ActiveRecord::Base.transaction do
create_account_activity!(account: registrar.cash_account,
invoice: invoice,
sum: invoice.subtotal,
currency: currency,
description: description,
activity_type: AccountActivity::ADD_CREDIT)
activity = AccountActivity.new(
account: registrar.cash_account, bank_transaction: self,
invoice: invoice, sum: invoice.subtotal,
currency: currency, description: description,
activity_type: AccountActivity::ADD_CREDIT
)
if activity.save
reset_pending_registrar_balance_reload
true
else
false
end
end

View file

@ -7,6 +7,7 @@ class Invoice < ApplicationRecord
has_one :account_activity
has_many :items, class_name: 'InvoiceItem', dependent: :destroy
has_many :directo_records, as: :item, class_name: 'Directo'
has_many :payment_orders
accepts_nested_attributes_for :items

102
app/models/payment_order.rb Normal file
View file

@ -0,0 +1,102 @@
class PaymentOrder < ApplicationRecord
include Versions
include ActionView::Helpers::NumberHelper
PAYMENT_INTERMEDIARIES = ENV['payments_intermediaries'].to_s.strip.split(', ').freeze
PAYMENT_BANKLINK_BANKS = ENV['payments_banks'].to_s.strip.split(', ').freeze
INTERNAL_PAYMENT_METHODS = %w[admin_payment system_payment].freeze
PAYMENT_METHODS = [PAYMENT_INTERMEDIARIES, PAYMENT_BANKLINK_BANKS,
INTERNAL_PAYMENT_METHODS].flatten.freeze
CUSTOMER_PAYMENT_METHODS = [PAYMENT_INTERMEDIARIES, PAYMENT_BANKLINK_BANKS].flatten.freeze
belongs_to :invoice, optional: false
validate :invoice_cannot_be_already_paid, on: :create
validate :supported_payment_method
enum status: { issued: 'issued', paid: 'paid', cancelled: 'cancelled',
failed: 'failed' }
attr_accessor :return_url, :response_url
def self.supported_methods
supported = []
PAYMENT_METHODS.each do |method|
class_name = ('PaymentOrders::' + method.camelize).constantize
raise(NoMethodError, class_name) unless class_name < PaymentOrder
supported << class_name
end
supported
end
def self.new_with_type(type:, invoice:)
channel = ('PaymentOrders::' + type.camelize).constantize
PaymentOrder.new(type: channel, invoice: invoice)
end
# Name of configuration namespace
def self.config_namespace_name; end
def supported_payment_method
return if PaymentOrder.supported_method?(type)
errors.add(:type, 'is not supported')
end
def invoice_cannot_be_already_paid
return unless invoice&.paid?
errors.add(:invoice, 'is already paid')
end
def self.supported_method?(name, shortname: false)
some_class = if shortname
('PaymentOrders::' + name.camelize).constantize
else
name.constantize
end
supported_methods.include? some_class
rescue NameError
false
end
def base_transaction(sum:, paid_at:, buyer_name:)
BankTransaction.new(
description: invoice.order,
reference_no: invoice.reference_no,
currency: invoice.currency,
iban: invoice.seller_iban,
sum: sum,
paid_at: paid_at,
buyer_name: buyer_name
)
end
def complete_transaction
return NoMethodError unless payment_received?
paid!
transaction = composed_transaction
transaction.save! && transaction.bind_invoice(invoice.number)
return unless transaction.errors.any?
worded_errors = 'Failed to bind. '
transaction.errors.full_messages.each do |err|
worded_errors << "#{err}, "
end
update!(notes: worded_errors)
end
def channel
type.gsub('PaymentOrders::', '')
end
def form_url
ENV["payments_#{self.class.config_namespace_name}_url"]
end
end

View file

@ -1,15 +0,0 @@
module PaymentOrders
PAYMENT_INTERMEDIARIES = ENV['payments_intermediaries'].to_s.strip.split(', ').freeze
PAYMENT_BANKLINK_BANKS = ENV['payments_banks'].to_s.strip.split(', ').freeze
PAYMENT_METHODS = [PAYMENT_INTERMEDIARIES, PAYMENT_BANKLINK_BANKS].flatten.freeze
def self.create_with_type(type, invoice, opts = {})
raise ArgumentError unless PAYMENT_METHODS.include?(type)
if PAYMENT_BANKLINK_BANKS.include?(type)
BankLink.new(type, invoice, opts)
elsif type == 'every_pay'
EveryPay.new(type, invoice, opts)
end
end
end

View file

@ -0,0 +1,9 @@
module PaymentOrders
class AdminPayment < PaymentOrder
CONFIG_NAMESPACE = 'admin_payment'.freeze
def self.config_namespace_name
CONFIG_NAMESPACE
end
end
end

View file

@ -1,44 +1,44 @@
module PaymentOrders
class BankLink < Base
BANK_LINK_VERSION = '008'
class BankLink < PaymentOrder
BANK_LINK_VERSION = '008'.freeze
NEW_TRANSACTION_SERVICE_NUMBER = '1012'
SUCCESSFUL_PAYMENT_SERVICE_NUMBER = '1111'
CANCELLED_PAYMENT_SERVICE_NUMBER = '1911'
NEW_TRANSACTION_SERVICE_NUMBER = '1012'.freeze
SUCCESSFUL_PAYMENT_SERVICE_NUMBER = '1111'.freeze
CANCELLED_PAYMENT_SERVICE_NUMBER = '1911'.freeze
NEW_MESSAGE_KEYS = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_STAMP VK_AMOUNT
NEW_MESSAGE_KEYS = %w[VK_SERVICE VK_VERSION VK_SND_ID VK_STAMP VK_AMOUNT
VK_CURR VK_REF VK_MSG VK_RETURN VK_CANCEL
VK_DATETIME).freeze
SUCCESS_MESSAGE_KEYS = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP
VK_DATETIME].freeze
SUCCESS_MESSAGE_KEYS = %w[VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP
VK_T_NO VK_AMOUNT VK_CURR VK_REC_ACC VK_REC_NAME
VK_SND_ACC VK_SND_NAME VK_REF VK_MSG
VK_T_DATETIME).freeze
CANCEL_MESSAGE_KEYS = %w(VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP
VK_REF VK_MSG).freeze
VK_T_DATETIME].freeze
CANCEL_MESSAGE_KEYS = %w[VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP
VK_REF VK_MSG].freeze
def form_fields
hash = {}
hash["VK_SERVICE"] = NEW_TRANSACTION_SERVICE_NUMBER
hash["VK_VERSION"] = BANK_LINK_VERSION
hash["VK_SND_ID"] = seller_account
hash["VK_STAMP"] = invoice.number
hash["VK_AMOUNT"] = number_with_precision(invoice.total, precision: 2, separator: ".")
hash["VK_CURR"] = invoice.currency
hash["VK_REF"] = ""
hash["VK_MSG"] = invoice.order
hash["VK_RETURN"] = return_url
hash["VK_CANCEL"] = return_url
hash["VK_DATETIME"] = Time.zone.now.strftime("%Y-%m-%dT%H:%M:%S%z")
hash["VK_MAC"] = calc_mac(hash)
hash["VK_ENCODING"] = "UTF-8"
hash["VK_LANG"] = "ENG"
hash['VK_SERVICE'] = NEW_TRANSACTION_SERVICE_NUMBER
hash['VK_VERSION'] = BANK_LINK_VERSION
hash['VK_SND_ID'] = seller_account
hash['VK_STAMP'] = invoice.number
hash['VK_AMOUNT'] = number_with_precision(invoice.total, precision: 2, separator: ".")
hash['VK_CURR'] = invoice.currency
hash['VK_REF'] = ''
hash['VK_MSG'] = invoice.order
hash['VK_RETURN'] = return_url
hash['VK_CANCEL'] = return_url
hash['VK_DATETIME'] = Time.zone.now.strftime('%Y-%m-%dT%H:%M:%S%z')
hash['VK_MAC'] = calc_mac(hash)
hash['VK_ENCODING'] = 'UTF-8'
hash['VK_LANG'] = 'ENG'
hash
end
def valid_response_from_intermediary?
return false unless response
case response["VK_SERVICE"]
case response['VK_SERVICE']
when SUCCESSFUL_PAYMENT_SERVICE_NUMBER
valid_successful_transaction?
when CANCELLED_PAYMENT_SERVICE_NUMBER
@ -48,29 +48,31 @@ module PaymentOrders
end
end
def complete_transaction
return unless valid_successful_transaction?
def payment_received?
valid_response_from_intermediary? && settled_payment?
end
transaction = compose_or_find_transaction
def create_failure_report
notes = "User failed to make payment. Bank responded with code #{response['VK_SERVICE']}"
status = 'cancelled'
update!(notes: notes, status: status)
end
transaction.sum = response['VK_AMOUNT']
transaction.bank_reference = response['VK_T_NO']
transaction.buyer_bank_code = response["VK_SND_ID"]
transaction.buyer_iban = response["VK_SND_ACC"]
transaction.buyer_name = response["VK_SND_NAME"]
transaction.paid_at = Time.parse(response["VK_T_DATETIME"])
def composed_transaction
paid_at = Time.parse(response['VK_T_DATETIME'])
transaction = base_transaction(sum: response['VK_AMOUNT'],
paid_at: paid_at,
buyer_name: response['VK_SND_NAME'])
transaction.save!
transaction.bind_invoice(invoice.number)
if transaction.errors.empty?
Rails.logger.info("Invoice ##{invoice.number} was marked as paid")
else
Rails.logger.error("Failed to bind invoice ##{invoice.number}")
end
transaction.bank_reference = response['VK_T_NO']
transaction.buyer_bank_code = response['VK_SND_ID']
transaction.buyer_iban = response['VK_SND_ACC']
transaction
end
def settled_payment?
response["VK_SERVICE"] == SUCCESSFUL_PAYMENT_SERVICE_NUMBER
response['VK_SERVICE'] == SUCCESSFUL_PAYMENT_SERVICE_NUMBER
end
private
@ -89,17 +91,15 @@ module PaymentOrders
def valid_amount?
source = number_with_precision(
BigDecimal(response["VK_AMOUNT"]), precision: 2, separator: "."
)
target = number_with_precision(
invoice.total, precision: 2, separator: "."
BigDecimal(response['VK_AMOUNT']), precision: 2, separator: '.'
)
target = number_with_precision(invoice.total, precision: 2, separator: '.')
source == target
end
def valid_currency?
invoice.currency == response["VK_CURR"]
invoice.currency == response['VK_CURR']
end
def sign(data)
@ -117,7 +117,7 @@ module PaymentOrders
def valid_mac?(hash, keys)
data = keys.map { |element| prepend_size(hash[element]) }.join
verify_mac(data, hash["VK_MAC"])
verify_mac(data, hash['VK_MAC'])
end
def verify_mac(data, mac)
@ -126,22 +126,22 @@ module PaymentOrders
end
def prepend_size(value)
value = (value || "").to_s.strip
string = ""
value = (value || '').to_s.strip
string = ''
string << format("%03i", value.size)
string << value
end
def seller_account
ENV["payments_#{type}_seller_account"]
ENV["payments_#{self.class.config_namespace_name}_seller_account"]
end
def seller_certificate
ENV["payments_#{type}_seller_private"]
ENV["payments_#{self.class.config_namespace_name}_seller_private"]
end
def bank_certificate
ENV["payments_#{type}_bank_certificate"]
ENV["payments_#{self.class.config_namespace_name}_bank_certificate"]
end
end
end

View file

@ -1,59 +0,0 @@
module PaymentOrders
class Base
include ActionView::Helpers::NumberHelper
attr_reader :type,
:invoice,
:return_url,
:response_url,
:response
def initialize(type, invoice, opts = {})
@type = type
@invoice = invoice
@return_url = opts[:return_url]
@response_url = opts[:response_url]
@response = opts[:response]
end
def create_transaction
transaction = BankTransaction.where(description: invoice.order).first_or_initialize(
reference_no: invoice.reference_no,
currency: invoice.currency,
iban: invoice.seller_iban
)
transaction.save!
end
def compose_or_find_transaction
transaction = BankTransaction.find_by(base_transaction_params)
# Transaction already autobinded (possibly) invalid invoice
if transaction.binded?
Rails.logger.info("Transaction #{transaction.id} is already binded")
Rails.logger.info('Creating new BankTransaction record.')
transaction = new_base_transaction
end
transaction
end
def new_base_transaction
BankTransaction.new(base_transaction_params)
end
def base_transaction_params
{
description: invoice.order,
currency: invoice.currency,
iban: invoice.seller_iban,
}
end
def form_url
ENV["payments_#{type}_url"]
end
end
end

View file

@ -1,9 +1,15 @@
module PaymentOrders
class EveryPay < Base
USER = ENV['payments_every_pay_api_user'].freeze
KEY = ENV['payments_every_pay_api_key'].freeze
ACCOUNT_ID = ENV['payments_every_pay_seller_account'].freeze
SUCCESSFUL_PAYMENT = %w(settled authorized).freeze
class EveryPay < PaymentOrder
USER = ENV['payments_every_pay_api_user']
KEY = ENV['payments_every_pay_api_key']
ACCOUNT_ID = ENV['payments_every_pay_seller_account']
SUCCESSFUL_PAYMENT = %w[settled authorized].freeze
CONFIG_NAMESPACE = 'every_pay'.freeze
def self.config_namespace_name
CONFIG_NAMESPACE
end
def form_fields
base_json = base_params
@ -25,25 +31,23 @@ module PaymentOrders
end
def settled_payment?
SUCCESSFUL_PAYMENT.include?(response[:payment_state])
SUCCESSFUL_PAYMENT.include?(response['payment_state'])
end
def complete_transaction
return unless valid_response_from_intermediary? && settled_payment?
def payment_received?
valid_response_from_intermediary? && settled_payment?
end
transaction = compose_or_find_transaction
def composed_transaction
base_transaction(sum: response['amount'],
paid_at: Date.strptime(response['timestamp'], '%s'),
buyer_name: response['cc_holder_name'])
end
transaction.sum = response[:amount]
transaction.paid_at = Date.strptime(response[:timestamp], '%s')
transaction.buyer_name = response[:cc_holder_name]
transaction.save!
transaction.bind_invoice(invoice.number)
if transaction.errors.empty?
Rails.logger.info("Invoice ##{invoice.number} marked as paid")
else
Rails.logger.error("Failed to bind invoice ##{invoice.number}")
end
def create_failure_report
notes = "User failed to make valid payment. Payment state: #{response['payment_state']}"
status = 'cancelled'
update!(notes: notes, status: status)
end
private
@ -63,24 +67,27 @@ module PaymentOrders
end
def valid_hmac?
hmac_fields = response[:hmac_fields].split(',')
hmac_fields = response['hmac_fields'].split(',')
hmac_hash = {}
hmac_fields.map do |field|
symbol = field.to_sym
hmac_hash[symbol] = response[symbol]
hmac_hash[field] = response[field]
end
hmac_string = hmac_hash.map { |key, _v| "#{key}=#{hmac_hash[key]}" }.join('&')
expected_hmac = OpenSSL::HMAC.hexdigest('sha1', KEY, hmac_string)
expected_hmac == response[:hmac]
expected_hmac == response['hmac']
rescue NoMethodError
false
end
def valid_amount?
invoice.total == BigDecimal(response[:amount])
return false unless response.key? 'amount'
invoice.total == BigDecimal(response['amount'])
end
def valid_account?
response[:account_id] == ACCOUNT_ID
response['account_id'] == ACCOUNT_ID
end
end
end

View file

@ -0,0 +1,7 @@
module PaymentOrders
class Lhv < BankLink
def self.config_namespace_name
'lhv'
end
end
end

View file

@ -0,0 +1,7 @@
module PaymentOrders
class Seb < BankLink
def self.config_namespace_name
'seb'
end
end
end

View file

@ -0,0 +1,7 @@
module PaymentOrders
class Swed < BankLink
def self.config_namespace_name
'swed'
end
end
end

View file

@ -0,0 +1,9 @@
module PaymentOrders
class SystemPayment < PaymentOrder
CONFIG_NAMESPACE = 'system_payment'.freeze
def self.config_namespace_name
CONFIG_NAMESPACE
end
end
end

View file

@ -0,0 +1,4 @@
class PaymentOrderVersion < PaperTrail::Version
self.table_name = :log_payment_orders
self.sequence_name = :log_payment_orders_id_seq
end

View file

@ -21,3 +21,5 @@
.col-md-6= render 'registrar/invoices/partials/buyer'
.row
.col-md-12= render 'registrar/invoices/partials/items'
.row
.col-md-12= render 'registrar/invoices/partials/payment_orders'

View file

@ -0,0 +1,19 @@
%h4= "Payment Orders"
%hr
.table-responsive
%table.table.table-hover.table-condensed
%thead
%tr
%th{class: 'col-xs-1'}= "#"
%th{class: 'col-xs-1'}= "Channel"
%th{class: 'col-xs-2'}= "Status"
%th{class: 'col-xs-3'}= "Initiated"
%th{class: 'col-xs-4'}= "Notes"
%tbody
- @invoice.payment_orders.each do |payment_order|
%tr
%td= payment_order.id
%td= payment_order.channel
%td= payment_order.status
%td= payment_order.created_at
%td= payment_order.notes

View file

@ -17,4 +17,4 @@
- if @invoice.payable?
.row.semifooter
.col-md-6-offset-6.text-right= render 'registrar/invoices/partials/banklinks', locals: { payment_channels: PaymentOrders::PAYMENT_METHODS }
.col-md-6-offset-6.text-right= render 'registrar/invoices/partials/banklinks', locals: { payment_channels: PaymentOrder::CUSTOMER_PAYMENT_METHODS }

View file

@ -2,8 +2,8 @@
= t('registrar.invoices.redirected_to_intermediary')
.payment-form
= form_tag @payment.form_url, method: :post do
- @payment.form_fields.each do |k, v|
= form_tag @payment_order.form_url, method: :post do
- @payment_order.form_fields.each do |k, v|
= hidden_field_tag k, v
= submit_tag t('registrar.invoices.go_to_intermediary')

View file

@ -0,0 +1,7 @@
en:
registrar:
payments:
back:
payment_successful: 'Thank you! Payment received successfully.'
successful_payment_backend_error: 'We received your payment, but something went wrong on our side. Please contact us via email or phone.'
payment_not_received: 'Payment was unsuccessful. Please make sure you have enough funds on your account and try again.'

View file

@ -128,11 +128,11 @@ Rails.application.routes.draw do
end
end
get 'pay/return/:bank' => 'payments#back', as: 'return_payment_with'
post 'pay/return/:bank' => 'payments#back'
put 'pay/return/:bank' => 'payments#back'
post 'pay/callback/:bank' => 'payments#callback', as: 'response_payment_with'
get 'pay/go/:bank' => 'payments#pay', as: 'payment_with'
get 'pay/return/:payment_order' => 'payments#back', as: 'return_payment_with'
post 'pay/return/:payment_order' => 'payments#back'
put 'pay/return/:payment_order' => 'payments#back'
post 'pay/callback/:payment_order' => 'payments#callback', as: 'response_payment_with'
get 'pay/go/:bank' => 'payments#pay', as: 'payment_with'
namespace :settings do
resource :balance_auto_reload, controller: :balance_auto_reload, only: %i[edit update destroy]

View file

@ -0,0 +1,15 @@
class CreatePaymentOrders < ActiveRecord::Migration[5.0]
def change
create_table :payment_orders do |t|
t.string :type, null: false
t.string :status, default: 'issued', null: false
t.belongs_to :invoice, foreign_key: true
t.jsonb :response, null: true
t.string :notes, null: true
t.string :creator_str
t.string :updator_str
t.timestamps
end
end
end

View file

@ -0,0 +1,16 @@
class CreatePaymentOrderVersions < ActiveRecord::Migration[5.0]
def change
create_table :log_payment_orders do |t|
t.string :item_type, null: false
t.integer :item_id, null: false
t.string :event, null: false
t.string :whodunnit
t.jsonb :object
t.jsonb :object_changes
t.datetime :created_at
t.string :session
t.jsonb :children
t.string :uuid
end
end
end

View file

@ -746,7 +746,6 @@ CREATE TABLE public.domains (
locked_by_registrant_at timestamp without time zone,
force_delete_start timestamp without time zone,
force_delete_data public.hstore
);
@ -1516,6 +1515,44 @@ CREATE SEQUENCE public.log_notifications_id_seq
ALTER SEQUENCE public.log_notifications_id_seq OWNED BY public.log_notifications.id;
--
-- Name: log_payment_orders; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE public.log_payment_orders (
id integer NOT NULL,
item_type character varying NOT NULL,
item_id integer NOT NULL,
event character varying NOT NULL,
whodunnit character varying,
object jsonb,
object_changes jsonb,
created_at timestamp without time zone,
session character varying,
children jsonb,
uuid character varying
);
--
-- Name: log_payment_orders_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.log_payment_orders_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: log_payment_orders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.log_payment_orders_id_seq OWNED BY public.log_payment_orders.id;
--
-- Name: log_registrant_verifications; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
@ -1819,6 +1856,43 @@ CREATE SEQUENCE public.notifications_id_seq
ALTER SEQUENCE public.notifications_id_seq OWNED BY public.notifications.id;
--
-- Name: payment_orders; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
CREATE TABLE public.payment_orders (
id integer NOT NULL,
type character varying NOT NULL,
status character varying DEFAULT 'issued'::character varying NOT NULL,
invoice_id integer,
response jsonb,
notes character varying,
creator_str character varying,
updator_str character varying,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);
--
-- Name: payment_orders_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.payment_orders_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: payment_orders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.payment_orders_id_seq OWNED BY public.payment_orders.id;
--
-- Name: prices; Type: TABLE; Schema: public; Owner: -; Tablespace:
--
@ -2500,6 +2574,13 @@ ALTER TABLE ONLY public.log_nameservers ALTER COLUMN id SET DEFAULT nextval('pub
ALTER TABLE ONLY public.log_notifications ALTER COLUMN id SET DEFAULT nextval('public.log_notifications_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.log_payment_orders ALTER COLUMN id SET DEFAULT nextval('public.log_payment_orders_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
@ -2556,6 +2637,13 @@ ALTER TABLE ONLY public.nameservers ALTER COLUMN id SET DEFAULT nextval('public.
ALTER TABLE ONLY public.notifications ALTER COLUMN id SET DEFAULT nextval('public.notifications_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.payment_orders ALTER COLUMN id SET DEFAULT nextval('public.payment_orders_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
@ -2905,6 +2993,14 @@ ALTER TABLE ONLY public.log_notifications
ADD CONSTRAINT log_notifications_pkey PRIMARY KEY (id);
--
-- Name: log_payment_orders_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
ALTER TABLE ONLY public.log_payment_orders
ADD CONSTRAINT log_payment_orders_pkey PRIMARY KEY (id);
--
-- Name: log_registrant_verifications_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
@ -2969,6 +3065,14 @@ ALTER TABLE ONLY public.notifications
ADD CONSTRAINT notifications_pkey PRIMARY KEY (id);
--
-- Name: payment_orders_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
ALTER TABLE ONLY public.payment_orders
ADD CONSTRAINT payment_orders_pkey PRIMARY KEY (id);
--
-- Name: prices_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
--
@ -3630,6 +3734,13 @@ CREATE INDEX index_nameservers_on_domain_id ON public.nameservers USING btree (d
CREATE INDEX index_notifications_on_registrar_id ON public.notifications USING btree (registrar_id);
--
-- Name: index_payment_orders_on_invoice_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
CREATE INDEX index_payment_orders_on_invoice_id ON public.payment_orders USING btree (invoice_id);
--
-- Name: index_prices_on_zone_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
--
@ -3896,6 +4007,14 @@ ALTER TABLE ONLY public.registrant_verifications
ADD CONSTRAINT fk_rails_f41617a0e9 FOREIGN KEY (domain_id) REFERENCES public.domains(id);
--
-- Name: fk_rails_f9dc5857c3; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.payment_orders
ADD CONSTRAINT fk_rails_f9dc5857c3 FOREIGN KEY (invoice_id) REFERENCES public.invoices(id);
--
-- Name: invoice_items_invoice_id_fk; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@ -4341,6 +4460,8 @@ INSERT INTO "schema_migrations" (version) VALUES
('20191227110904'),
('20200113091254'),
('20200115102202'),
('20200130092113'),
('20200203143458'),
('20200204103125');

View file

@ -4,3 +4,10 @@ one:
quantity: 1
unit: pc
invoice: one
two:
description: Acme services
price: 5
quantity: 1
unit: pc
invoice: unpaid

View file

@ -24,3 +24,30 @@ one:
reference_no: 13
number: 1
description: Order nr 1 from registrar 1234567 second number 2345678
unpaid:
issue_date: <%= Date.parse '2010-07-05' %>
due_date: <%= Date.parse '2010-07-06' %>
currency: EUR
seller_name: Seller Ltd
seller_reg_no: 1234
seller_iban: US75512108001245126199
seller_bank: Main Bank
seller_swift: swift
seller_email: info@seller.test
seller_country_code: US
seller_street: Main Street 1
seller_city: New York
seller_contact_name: John Doe
buyer: bestnames
buyer_name: Buyer Ltd
buyer_reg_no: 12345
buyer_email: info@buyer.test
buyer_country_code: GB
buyer_street: Main Street 2
buyer_city: London
vat_rate: 0.1
total: 16.50
reference_no: 13
number: 2
description: Order nr 2 from registrar 1234567 second number 2345678

27
test/fixtures/payment_orders.yml vendored Normal file
View file

@ -0,0 +1,27 @@
everypay_issued:
type: PaymentOrders::EveryPay
status: issued
invoice: one
response:
notes:
banklink_issued:
type: PaymentOrders::Seb
status: issued
invoice: one
response:
notes:
paid:
type: PaymentOrders::EveryPay
status: paid
invoice: unpaid
response: "{}"
notes:
cancelled:
type: PaymentOrders::Seb
status: cancelled
invoice: unpaid
response: "{}"
notes: User failed to make payment. Bank responded with code 1911

View file

@ -6,34 +6,40 @@ class PaymentCallbackTest < ApplicationIntegrationTest
@user = users(:api_bestnames)
sign_in @user
@payment_order = payment_orders(:everypay_issued)
@invoice = invoices(:one)
@invoice.update!(account_activity: nil, total: 12)
end
def test_every_pay_callback_returns_status_200
invoice = payable_invoice
assert_matching_bank_transaction_exists(invoice)
request_params = every_pay_request_params.merge(invoice_id: invoice.id)
post "/registrar/pay/callback/every_pay", params: request_params
request_params = every_pay_request_params
post "/registrar/pay/callback/#{@payment_order.id}", params: request_params
assert_response :ok
end
def test_invoice_is_marked_as_paid
request_params = every_pay_request_params
post "/registrar/pay/callback/#{@payment_order.id}", params: request_params
assert @payment_order.invoice.paid?
end
def failure_log_is_created_if_unsuccessful_payment
request_params = every_pay_request_params.dup
request_params['payment_state'] = 'cancelled'
request_params['transaction_result'] = 'failed'
post "/registrar/pay/callback/#{@payment_order.id}", params: request_params
@payment_order.reload
assert @payment_order.cancelled?
assert_includes @payment_order.notes, 'Payment state: cancelled'
end
private
def payable_invoice
invoice = invoices(:one)
invoice.update!(account_activity: nil)
invoice
end
def assert_matching_bank_transaction_exists(invoice)
assert BankTransaction.find_by(
description: invoice.description,
currency: invoice.currency,
iban: invoice.seller_iban
), 'Matching bank transaction should exist'
end
def every_pay_request_params
{
nonce: "392f2d7748bc8cb0d14f263ebb7b8932",

View file

@ -8,6 +8,9 @@ class PaymentReturnTest < ApplicationIntegrationTest
sign_in @user
@invoice = invoices(:one)
@invoice.update!(account_activity: nil, total: 12)
@everypay_order = payment_orders(:everypay_issued)
@banklink_order = payment_orders(:banklink_issued)
end
def every_pay_request_params
@ -57,33 +60,78 @@ class PaymentReturnTest < ApplicationIntegrationTest
}
end
def test_every_pay_return_creates_activity_redirects_to_invoice_path
request_params = every_pay_request_params.merge(invoice_id: @invoice.id)
def test_successful_bank_payment_marks_invoice_as_paid
@invoice.update!(account_activity: nil)
request_params = bank_link_request_params
post "/registrar/pay/return/every_pay", params: request_params
post "/registrar/pay/return/#{@banklink_order.id}", params: request_params
@banklink_order.reload
assert @banklink_order.invoice.paid?
end
def test_every_pay_return_creates_activity_redirects_to_invoice_path
request_params = every_pay_request_params
post "/registrar/pay/return/#{@everypay_order.id}", params: request_params
assert_equal(302, response.status)
assert_redirected_to(registrar_invoice_path(@invoice))
end
def test_Every_Pay_return_raises_RecordNotFound
request_params = every_pay_request_params.merge(invoice_id: "178907")
def test_every_pay_return_raises_record_not_found
request_params = every_pay_request_params
assert_raises(ActiveRecord::RecordNotFound) do
post "/registrar/pay/return/every_pay", params: request_params
post '/registrar/pay/return/123456', params: request_params
end
end
def test_bank_link_return_redirects_to_invoice_paths
request_params = bank_link_request_params.merge(invoice_id: @invoice.id)
request_params = bank_link_request_params
post "/registrar/pay/return/seb", params: request_params
post "/registrar/pay/return/#{@banklink_order.id}", params: request_params
assert_equal(302, response.status)
assert_redirected_to(registrar_invoice_path(@invoice))
end
def test_bank_link_return
request_params = bank_link_request_params.merge(invoice_id: "178907")
request_params = bank_link_request_params
assert_raises(ActiveRecord::RecordNotFound) do
post "/registrar/pay/return/seb", params: request_params
post '/registrar/pay/return/123456', params: request_params
end
end
def test_marks_as_paid_and_adds_notes_if_failed_to_bind
request_params = bank_link_request_params
post "/registrar/pay/return/#{@banklink_order.id}", params: request_params
post "/registrar/pay/return/#{@banklink_order.id}", params: request_params
@banklink_order.reload
assert @banklink_order.notes.present?
assert @banklink_order.paid?
assert_includes @banklink_order.notes, 'Failed to bind'
end
def test_failed_bank_link_payment_creates_brief_error_explanation
request_params = bank_link_request_params.dup
request_params['VK_SERVICE'] = '1911'
post "/registrar/pay/return/#{@banklink_order.id}", params: request_params
@banklink_order.reload
assert_includes @banklink_order.notes, 'Bank responded with code 1911'
end
def test_failed_every_pay_payment_creates_brief_error_explanation
request_params = every_pay_request_params.dup
request_params['payment_state'] = 'cancelled'
request_params['transaction_result'] = 'failed'
post "/registrar/pay/return/#{@everypay_order.id}", params: request_params
@everypay_order.reload
assert_includes @everypay_order.notes, 'Payment state: cancelled'
end
end

View file

@ -8,7 +8,7 @@ class BankLinkTest < ActiveSupport::TestCase
super
@invoice = invoices(:one)
@invoice.update!(total: 12)
@invoice.update!(account_activity: nil, total: 12)
travel_to '2018-04-01 00:30 +0300'
create_new_bank_link
@ -36,11 +36,11 @@ class BankLinkTest < ActiveSupport::TestCase
'VK_MAC': 'CZZvcptkxfuOxRR88JmT4N+Lw6Hs4xiQfhBWzVYldAcRTQbcB/lPf9MbJzBE4e1/HuslQgkdCFt5g1xW2lJwrVDBQTtP6DAHfvxU3kkw7dbk0IcwhI4whUl68/QCwlXEQTAVDv1AFnGVxXZ40vbm/aLKafBYgrirB5SUe8+g9FE=',
'VK_ENCODING': 'UTF-8',
'VK_LANG': 'ENG'
}.with_indifferent_access
}.as_json
@completed_bank_link = PaymentOrders::BankLink.new(
'seb', @invoice, { response: params }
)
@completed_bank_link = PaymentOrder.new(type: 'PaymentOrders::Seb',
invoice: @invoice,
response: params)
end
def create_cancelled_bank_link
@ -55,16 +55,17 @@ class BankLinkTest < ActiveSupport::TestCase
'VK_MAC': 'PElE2mYXXN50q2UBvTuYU1rN0BmOQcbafPummDnWfNdm9qbaGQkGyOn0XaaFGlrdEcldXaHBbZKUS0HegIgjdDfl2NOk+wkLNNH0Iu38KzZaxHoW9ga7vqiyKHC8dcxkHiO9HsOnz77Sy/KpWCq6cz48bi3fcMgo+MUzBMauWoQ=',
'VK_ENCODING': 'UTF-8',
'VK_LANG': 'ENG'
}.with_indifferent_access
}.as_json
@cancelled_bank_link = PaymentOrders::BankLink.new(
'seb', @invoice, { response: params }
)
@cancelled_bank_link = PaymentOrder.new(type: 'PaymentOrders::Seb',
invoice: @invoice,
response: params)
end
def create_new_bank_link
params = { return_url: 'return.url', response_url: 'response.url' }
@new_bank_link = PaymentOrders::BankLink.new('seb', @invoice, params)
@new_bank_link = PaymentOrder.new(type: 'PaymentOrders::Seb', invoice: @invoice)
@new_bank_link.return_url = 'return.url'
@new_bank_link.response_url = 'response.url'
end
def test_response_is_not_valid_when_it_is_missing
@ -105,23 +106,14 @@ class BankLinkTest < ActiveSupport::TestCase
refute(@cancelled_bank_link.settled_payment?)
end
def test_complete_transaction_calls_methods_on_transaction
mock_transaction = MiniTest::Mock.new
mock_transaction.expect(:sum= , '12.00', ['12.00'])
mock_transaction.expect(:bank_reference= , '1', ['1'])
mock_transaction.expect(:buyer_bank_code= , 'testvpos', ['testvpos'])
mock_transaction.expect(:buyer_iban= , '1234', ['1234'])
mock_transaction.expect(:paid_at= , Date.parse('2018-04-01 00:30:00 +0300'), [Time.parse('2018-04-01T00:30:00+0300')])
mock_transaction.expect(:buyer_name=, 'John Doe', ['John Doe'])
mock_transaction.expect(:save!, true)
mock_transaction.expect(:binded?, false)
mock_transaction.expect(:bind_invoice, AccountActivity.new, [1])
mock_transaction.expect(:errors, [])
def test_successful_payment_creates_bank_transaction
@completed_bank_link.complete_transaction
BankTransaction.stub(:find_by, mock_transaction) do
@completed_bank_link.complete_transaction
end
transaction = BankTransaction.find_by(
sum: @completed_bank_link.response['VK_AMOUNT'],
buyer_name: @completed_bank_link.response['VK_SND_NAME']
)
mock_transaction.verify
assert transaction.present?
end
end

View file

@ -4,36 +4,38 @@ class EveryPayTest < ActiveSupport::TestCase
def setup
super
@invoice = invoices(:one)
@invoice = invoices(:unpaid)
@invoice.update!(total: 12)
params = {
response:
{
utf8: '✓',
_method: 'put',
authenticity_token: 'OnA69vbccQtMt3C9wxEWigs5Gpf/7z+NoxRCMkFPlTvaATs8+OgMKF1I4B2f+vuK37zCgpWZaWWtyuslRRSwkw==',
nonce: '392f2d7748bc8cb0d14f263ebb7b8932',
timestamp: '1524136727',
api_username: 'ca8d6336dd750ddb',
transaction_result: 'completed',
payment_reference: 'fd5d27b59a1eb597393cd5ff77386d6cab81ae05067e18d530b10f3802e30b56',
payment_state: 'settled',
amount: '12.00',
order_reference: 'e468a2d59a731ccc546f2165c3b1a6',
account_id: 'EUR3D1',
cc_type: 'master_card',
cc_last_four_digits: '0487',
cc_month: '10',
cc_year: '2018',
cc_holder_name: 'John Doe',
hmac_fields: 'account_id,amount,api_username,cc_holder_name,cc_last_four_digits,cc_month,cc_type,cc_year,hmac_fields,nonce,order_reference,payment_reference,payment_state,timestamp,transaction_result',
hmac: 'efac1c732835668cd86023a7abc140506c692f0d',
invoice_id: '1',
},
}
@every_pay = PaymentOrders::EveryPay.new('every_pay', @invoice, params)
@other_pay = PaymentOrders::EveryPay.new('every_pay', @invoice, {})
response = {
"utf8": '✓',
"_method": 'put',
"authenticity_token": 'OnA69vbccQtMt3C9wxEWigs5Gpf/7z+NoxRCMkFPlTvaATs8+OgMKF1I4B2f+vuK37zCgpWZaWWtyuslRRSwkw=="',
"nonce": '392f2d7748bc8cb0d14f263ebb7b8932',
"timestamp": '1524136727',
"api_username": 'ca8d6336dd750ddb',
"transaction_result": 'completed',
"payment_reference": 'fd5d27b59a1eb597393cd5ff77386d6cab81ae05067e18d530b10f3802e30b56',
"payment_state": 'settled',
"amount": '12.00',
"order_reference": 'e468a2d59a731ccc546f2165c3b1a6',
"account_id": 'EUR3D1',
"cc_type": 'master_card',
"cc_last_four_digits": '0487',
"cc_month": '10',
"cc_year": '2018',
"cc_holder_name": 'John Doe',
"hmac_fields": 'account_id,amount,api_username,cc_holder_name,cc_last_four_digits,cc_month,cc_type,cc_year,hmac_fields,nonce,order_reference,payment_reference,payment_state,timestamp,transaction_result',
"hmac": 'efac1c732835668cd86023a7abc140506c692f0d',
"invoice_id": '2'
}.as_json
@successful_payment = PaymentOrder.new(type: 'PaymentOrders::EveryPay',
invoice: @invoice,
response: response)
@failed_payment = @successful_payment.dup
@failed_payment.response['payment_state'] = 'cancelled'
travel_to Time.zone.parse('2018-04-01 00:30:00 +0000')
end
@ -47,39 +49,37 @@ class EveryPayTest < ActiveSupport::TestCase
transaction_type: 'charge',
hmac_fields: 'account_id,amount,api_username,callback_url,customer_url,hmac_fields,nonce,order_reference,timestamp,transaction_type'
}
form_fields = @every_pay.form_fields
form_fields = @successful_payment.form_fields
expected_fields.each do |k, v|
assert_equal(v, form_fields[k])
end
end
def test_valid_response_from_intermediary?
assert(@every_pay.valid_response_from_intermediary?)
refute(@other_pay.valid_response_from_intermediary?)
assert(@successful_payment.valid_response_from_intermediary?)
@failed_payment.response = { 'what': 'definitely not valid everypay response' }
refute(@failed_payment.valid_response_from_intermediary?)
end
def test_valid_and_successful_payment_is_determined
assert(@successful_payment.payment_received?)
refute(@failed_payment.payment_received?)
end
def test_settled_payment?
assert(@every_pay.settled_payment?)
other_pay = PaymentOrders::EveryPay.new(
'every_pay', @invoice, {response: {payment_state: 'CANCELLED'}}
)
refute(other_pay.settled_payment?)
assert(@successful_payment.settled_payment?)
refute(@failed_payment.settled_payment?)
end
def test_complete_transaction_calls_methods_on_transaction
mock_transaction = MiniTest::Mock.new
mock_transaction.expect(:sum= , '12.00', ['12.00'])
mock_transaction.expect(:paid_at= , Date.strptime('1524136727', '%s'), [Date.strptime('1524136727', '%s')])
mock_transaction.expect(:buyer_name=, 'John Doe', ['John Doe'])
mock_transaction.expect(:save!, true)
mock_transaction.expect(:binded?, false)
mock_transaction.expect(:bind_invoice, AccountActivity.new, [1])
mock_transaction.expect(:errors, [])
def test_successful_payment_creates_bank_transaction
@successful_payment.complete_transaction
BankTransaction.stub(:find_by, mock_transaction) do
@every_pay.complete_transaction
end
transaction = BankTransaction.find_by(
sum: @successful_payment.response['amount'],
buyer_name: @successful_payment.response['cc_holder_name']
)
mock_transaction.verify
assert transaction.present?
end
end

View file

@ -5,23 +5,21 @@ class PaymentOrdersTest < ActiveSupport::TestCase
super
@original_methods = ENV['payment_methods']
@original_seb_URL = ENV['seb_payment_url']
ENV['payment_methods'] = 'seb, swed, credit_card'
@original_seb_url = ENV['seb_payment_url']
ENV['payment_methods'] = 'seb, swed, every_pay'
ENV['seb_payment_url'] = nil
@not_implemented_payment = PaymentOrders::Base.new(
'not_implemented', Invoice.new
)
@not_implemented_payment = PaymentOrder.new(invoice: Invoice.new)
end
def teardown
super
ENV['payment_methods'] = @original_methods
ENV['seb_payment_url'] = @original_seb_URL
ENV['seb_payment_url'] = @original_seb_url
end
def test_variable_assignment
assert_equal 'not_implemented', @not_implemented_payment.type
assert_nil @not_implemented_payment.type
assert_nil @not_implemented_payment.response_url
assert_nil @not_implemented_payment.return_url
assert_nil @not_implemented_payment.form_url
@ -45,14 +43,61 @@ class PaymentOrdersTest < ActiveSupport::TestCase
end
end
def test_that_create_with_type_raises_argument_error
assert_raise ArgumentError do
PaymentOrders.create_with_type("not_implemented", Invoice.new)
def test_correct_channel_is_assigned
everypay_channel = PaymentOrder.new_with_type(type: 'every_pay', invoice: @invoice)
assert_equal everypay_channel.channel, 'EveryPay'
assert_equal everypay_channel.class.config_namespace_name, 'every_pay'
swed_channel = PaymentOrder.new_with_type(type: 'swed', invoice: @invoice)
assert_equal swed_channel.channel, 'Swed'
assert_equal swed_channel.class.config_namespace_name, 'swed'
seb_channel = PaymentOrder.new_with_type(type: 'seb', invoice: @invoice)
assert_equal seb_channel.channel, 'Seb'
assert_equal seb_channel.class.config_namespace_name, 'seb'
lhv_channel = PaymentOrder.new_with_type(type: 'lhv', invoice: @invoice)
assert_equal lhv_channel.channel, 'Lhv'
assert_equal lhv_channel.class.config_namespace_name, 'lhv'
admin_channel = PaymentOrder.new_with_type(type: 'admin_payment', invoice: @invoice)
assert_equal admin_channel.channel, 'AdminPayment'
assert_equal admin_channel.class.config_namespace_name, 'admin_payment'
system_channel = PaymentOrder.new_with_type(type: 'system_payment', invoice: @invoice)
assert_equal system_channel.channel, 'SystemPayment'
assert_equal system_channel.class.config_namespace_name, 'system_payment'
end
def test_can_not_create_order_for_paid_invoice
invoice = invoices(:one)
payment_order = PaymentOrder.new_with_type(type: 'every_pay', invoice: invoice)
assert payment_order.invalid?
assert_includes payment_order.errors[:invoice], 'is already paid'
end
def test_order_without_channel_is_invalid
payment_order = PaymentOrder.new
assert payment_order.invalid?
assert_includes payment_order.errors[:type], 'is not supported'
end
def test_can_not_create_order_with_invalid_type
assert_raise NameError do
PaymentOrder.new_with_type(type: 'not_implemented', invoice: Invoice.new)
end
end
def test_create_with_correct_subclass
payment = PaymentOrders.create_with_type('seb', Invoice.new)
assert_equal PaymentOrders::BankLink, payment.class
def test_supported_method_bool_does_not_fail
assert_not PaymentOrder.supported_method?('not_implemented', shortname: true)
assert PaymentOrder.supported_method?('every_pay', shortname: true)
assert_not PaymentOrder.supported_method?('PaymentOrders::NonExistant')
assert PaymentOrder.supported_method?('PaymentOrders::EveryPay')
end
def test_can_create_with_correct_subclass
payment = PaymentOrder.new_with_type(type: 'seb', invoice: Invoice.new)
assert_equal PaymentOrders::Seb, payment.class
end
end

View file

@ -58,6 +58,30 @@ class ProcessPaymentsTaskTest < ActiveSupport::TestCase
assert @invoice.paid?
end
def test_attaches_paid_payment_order_to_invoice
assert @invoice.unpaid?
capture_io { run_task }
@invoice.reload
payment_order = @invoice.payment_orders.last
assert_equal 'PaymentOrders::SystemPayment', payment_order.type
assert payment_order.paid?
end
def test_attaches_failed_payment_order_to_invoice
assert @invoice.unpaid?
account = accounts(:cash)
account.update!(registrar: registrars(:goodnames))
capture_io { run_task }
@invoice.reload
payment_order = @invoice.payment_orders.last
assert_equal 'PaymentOrders::SystemPayment', payment_order.type
assert payment_order.failed?
end
def test_output
assert_output "Transactions processed: 1\n" do
run_task
@ -75,4 +99,4 @@ class ProcessPaymentsTaskTest < ActiveSupport::TestCase
invoice.update!({ account_activity: nil, cancelled_at: nil }.merge(attributes))
invoice
end
end
end