Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace banklinks with linkpay #2245

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ gem 'jquery-ui-rails', '6.0.1'
gem 'pdfkit'
gem 'que'
gem 'que-web'
gem 'sidekiq', '>= 6.4.1'
gem 'rqrcode', '~> 2.0'
gem 'sidekiq'

gem 'company_register', github: 'internetee/company_register',
branch: 'master'
Expand Down
14 changes: 7 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ GEM
xpath (~> 3.2)
childprocess (3.0.0)
chronic (0.10.2)
chunky_png (1.4.0)
coderay (1.1.3)
coffee-rails (5.0.0)
coffee-script (>= 2.2.0)
Expand Down Expand Up @@ -234,15 +235,11 @@ GEM
globalid (0.5.2)
activesupport (>= 5.0)
google-protobuf (3.19.4)
google-protobuf (3.19.4-x86_64-linux)
googleapis-common-protos-types (1.3.0)
google-protobuf (~> 3.14)
grpc (1.41.1)
google-protobuf (~> 3.17)
googleapis-common-protos-types (~> 1.0)
grpc (1.41.1-x86_64-linux)
google-protobuf (~> 3.17)
googleapis-common-protos-types (~> 1.0)
gyoku (1.3.1)
builder (>= 2.1.2)
haml (5.2.2)
Expand Down Expand Up @@ -327,8 +324,6 @@ GEM
nokogiri (1.13.4)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.4-x86_64-linux)
racc (~> 1.4)
nori (2.6.0)
omniauth (1.9.1)
hashie (>= 3.4.6)
Expand Down Expand Up @@ -424,6 +419,10 @@ GEM
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rexml (3.2.5)
rqrcode (2.1.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
ruby2_keywords (0.0.4)
rubyzip (2.3.2)
sass-rails (6.0.0)
Expand Down Expand Up @@ -585,10 +584,11 @@ DEPENDENCIES
ransack (~> 2.6.0)
rest-client
rexml
rqrcode (~> 2.0)
sass-rails
select2-rails (= 4.0.13)
selectize-rails (= 0.12.6)
sidekiq (>= 6.4.1)
sidekiq
simplecov (= 0.17.1)
simpleidn (= 0.2.1)
spy
Expand Down
63 changes: 25 additions & 38 deletions app/controllers/registrar/payments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,62 +1,49 @@
class Registrar
class PaymentsController < BaseController
protect_from_forgery except: [:back, :callback]
protect_from_forgery except: [:callback]

skip_authorization_check # actually anyone can pay, no problems at all
skip_before_action :authenticate_registrar_user!, :check_ip_restriction,
only: [:back, :callback]
only: [:callback]

before_action :check_supported_payment_method, only: [:pay]

def pay
invoice = Invoice.find(params[:invoice_id])
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
@payment_order = PaymentOrder.find_by!(id: params[:payment_order])
@payment_order.update!(response: params.to_unsafe_h)

if @payment_order.payment_received?
@payment_order.complete_transaction

if @payment_order.invoice.paid?
flash[:notice] = t('.payment_successful')
else
flash[:alert] = t('.successful_payment_backend_error')
end
else
@payment_order.create_failure_report
flash[:alert] = t('.payment_not_received')
respond_to do |format|
format.html { redirect_to invoice.linkpay_url_builder } if invoice
end
redirect_to registrar_invoice_path(@payment_order.invoice)
end

def callback
@payment_order = PaymentOrder.find_by!(id: params[:payment_order])
@payment_order.update!(response: params.to_unsafe_h)
invoice = Invoice.find_by(number: linkpay_params[:order_reference])
payment_order = find_payment_order(invoice: invoice, ref: linkpay_params[:order_reference])

if @payment_order.payment_received?
@payment_order.complete_transaction
else
@payment_order.create_failure_report
end
payment_order.response = {
order_reference: linkpay_params[:order_reference],
payment_reference: linkpay_params[:payment_reference],
}
payment_order.save

render status: 200, json: { status: 'ok' }
payment_order.check_linkpay_status

render status: :ok, json: { status: 'ok' }
end

private

def linkpay_params
params.permit(:order_reference, :payment_reference)
end

def find_payment_order(invoice:, ref:)
order = invoice.payment_orders.every_pay.for_payment_reference(ref).first
return order if order

PaymentOrder.new_with_type(type: 'every_pay', invoice: invoice)
end

def check_supported_payment_method
return if PaymentOrder.supported_method?(params[:bank], shortname: true)

Expand Down
1 change: 1 addition & 0 deletions app/mailers/invoice_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class InvoiceMailer < ApplicationMailer
def invoice_email(invoice:, recipient:, paid: false)
@invoice = invoice

@linkpay_url = invoice.linkpay_url unless paid
subject = default_i18n_subject(invoice_number: invoice.number)
subject << I18n.t('invoice.already_paid') if paid
attachments["invoice-#{invoice.number}.pdf"] = invoice.as_pdf
Expand Down
77 changes: 77 additions & 0 deletions app/models/concerns/http_requester.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module HttpRequester
extend ActiveSupport::Concern

HTTP_METHODS = {
get: Net::HTTP::Get,
post: Net::HTTP::Post,
}.freeze

HTTP_ERRORS = [
EOFError,
Errno::ECONNRESET,
Errno::EINVAL,
Errno::ECONNREFUSED,
Net::HTTPBadResponse,
Net::HTTPHeaderSyntaxError,
Net::ProtocolError,
Timeout::Error,
].freeze

def default_request_response(url:, body:, headers:, type: :post)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (url.scheme == 'https')

generate_request(body: body, headers: headers, http: http, type: type, uri: uri)
rescue *HTTP_ERRORS => e
failed_result(e)
end

def generate_request(body:, headers:, http:, type:, uri:)
Timeout.timeout(10) do
request = HTTP_METHODS[type].new(uri.request_uri)
headers&.each { |key, val| request[key] = val }
request.body = body.to_json if body
success_result(response: http.request(request))
end
end

def success_result(response:)
{
body: JSON.parse(response.read_body),
status: response.code.to_i,
}
end

def failed_result(exception)
error_code = Rack::Utils::SYMBOL_TO_STATUS_CODE[:service_unavailable]
{
body: "Error occured - #{exception.message}",
status: error_code,
}
end

def default_post_request_response(url:, body: nil, headers: nil)
default_request_response(url: url, body: body, headers: headers, type: :post)
end

def default_get_request_response(url:, body: nil, headers: nil)
default_request_response(url: url, body: body, headers: headers, type: :get)
end

# :nocov:
def basic_auth_get(url:, username:, password:)
uri = URI(url)

Net::HTTP.start(uri.host, uri.port,
use_ssl: uri.scheme == 'https',
verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Get.new uri.request_uri
request.basic_auth username, password
response = http.request request

JSON.parse(response.body)
end
end
# :nocov:
end
39 changes: 39 additions & 0 deletions app/models/concerns/invoice/linkpayable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module Invoice::Linkpayable
extend ActiveSupport::Concern

KEY = ENV['payments_every_pay_api_key']
LINKPAY_PREFIX = ENV['payments_every_pay_linkpay_prefix']
LINKPAY_CHECK_PREFIX = ENV['payments_every_pay_linkpay_check_prefix']
LINKPAY_TOKEN = ENV['payments_every_pay_linkpay_token']
LINKPAY_QR = ENV['payments_every_pay_linkpay_qr']

def linkpay_url
return if paid?

linkpay_url_builder
end

def linkpay_url_builder
price = Money.from_amount(total, 'EUR')
data = CGI.unescape(linkpay_params(price).to_query)

hmac = OpenSSL::HMAC.hexdigest('sha256', KEY, data)
"#{LINKPAY_PREFIX}?#{CGI.unescape(data)}&hmac=#{hmac}"
end

def linkpay_params(price)
{ 'transaction_amount' => price.to_s,
'order_reference' => number,
'customer_name' => buyer_name.parameterize(separator: '_', preserve_case: true),
'customer_email' => buyer.email,
'custom_field_1' => description.parameterize(separator: '_', preserve_case: true),
'linkpay_token' => LINKPAY_TOKEN,
'invoice_number' => number }
end

def qr_enabled?
!!LINKPAY_QR
end
end
1 change: 1 addition & 0 deletions app/models/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Invoice < ApplicationRecord
include Invoice::Cancellable
include Invoice::Payable
include Invoice::BookKeeping
include Invoice::Linkpayable

belongs_to :buyer, class_name: 'Registrar'
has_one :account_activity
Expand Down
20 changes: 20 additions & 0 deletions app/models/invoice/pdf_generator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# frozen_string_literal: true

require 'rqrcode'

class Invoice
class PdfGenerator
attr_reader :invoice
Expand All @@ -8,6 +12,7 @@ def initialize(invoice)

def as_pdf
generator = PDFKit.new(invoice_html)
generate_qr unless @invoice.paid?
generator.to_pdf
end

Expand All @@ -16,5 +21,20 @@ def as_pdf
def invoice_html
ApplicationController.render(template: 'invoice/pdf', assigns: { invoice: invoice })
end

def generate_qr
return unless @invoice.qr_enabled?

qrcode = RQRCode::QRCode.new(@invoice.linkpay_url)
png = qrcode.as_png(
color_mode: ChunkyPNG::COLOR_GRAYSCALE,
color: 'black',
fill: 'white',
size: 240
)

path = Rails.root.join("public/#{@invoice.number}.png")
IO.binwrite(path, png.to_s)
end
end
end
9 changes: 5 additions & 4 deletions app/models/payment_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ class PaymentOrder < ApplicationRecord
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
PAYMENT_METHODS = [PAYMENT_INTERMEDIARIES, INTERNAL_PAYMENT_METHODS].flatten.freeze
CUSTOMER_PAYMENT_METHODS = [PAYMENT_INTERMEDIARIES].flatten.freeze

belongs_to :invoice, optional: false

Expand All @@ -19,6 +17,9 @@ class PaymentOrder < ApplicationRecord

attr_accessor :return_url, :response_url

scope :every_pay, -> { where('type = ?', 'PaymentOrders::EveryPay') }
scope :for_payment_reference, ->(ref) { where("response->>'payment_reference'=?", ref) }

def self.supported_methods
supported = []

Expand Down
Loading