lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
require 'json'
require 'active_merchant/billing/gateways/quickpay/quickpay_common'
module ActiveMerchant
module Billing
class QuickpayV10Gateway < Gateway
include QuickpayCommon
API_VERSION = 10
self.live_url = self.test_url = 'https://api.quickpay.net'
def initialize(options = {})
requires!(options, :api_key)
super
end
def purchase(money, credit_card_or_reference, options = {})
MultiResponse.run do |r|
if credit_card_or_reference.is_a?(String)
r.process { create_token(credit_card_or_reference, options) }
credit_card_or_reference = r.authorization
end
r.process { create_payment(money, options) }
r.process {
post = authorization_params(money, credit_card_or_reference, options)
add_autocapture(post, false)
commit(synchronized_path("/payments/#{r.responses.last.params['id']}/authorize"), post)
}
r.process {
post = capture_params(money, credit_card_or_reference, options)
commit(synchronized_path("/payments/#{r.responses.last.params['id']}/capture"), post)
}
end
end
def authorize(money, credit_card_or_reference, options = {})
MultiResponse.run do |r|
if credit_card_or_reference.is_a?(String)
r.process { create_token(credit_card_or_reference, options) }
credit_card_or_reference = r.authorization
end
r.process { create_payment(money, options) }
r.process {
post = authorization_params(money, credit_card_or_reference, options)
commit(synchronized_path("/payments/#{r.responses.last.params['id']}/authorize"), post)
}
end
end
def void(identification, _options = {})
commit(synchronized_path "/payments/#{identification}/cancel")
end
def credit(money, identification, options = {})
ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
def capture(money, identification, options = {})
post = capture_params(money, identification, options)
commit(synchronized_path("/payments/#{identification}/capture"), post)
end
def refund(money, identification, options = {})
post = {}
add_amount(post, money, options)
add_additional_params(:refund, post, options)
commit(synchronized_path("/payments/#{identification}/refund"), post)
end
def verify(credit_card, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(100, credit_card, options) }
r.process(:ignore_result) { void(r.authorization, options) }
end
end
def store(credit_card, options = {})
MultiResponse.run do |r|
r.process { create_store(options) }
r.process { authorize_store(r.authorization, credit_card, options) }
end
end
def unstore(identification)
commit(synchronized_path "/cards/#{identification}/cancel")
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
gsub(%r(("card\\?":{\\?"number\\?":\\?")\d+), '\1[FILTERED]').
gsub(%r(("cvd\\?":\\?")\d+), '\1[FILTERED]')
end
private
def authorization_params(money, credit_card_or_reference, options = {})
post = {}
add_amount(post, money, options)
add_credit_card_or_reference(post, credit_card_or_reference, options)
add_additional_params(:authorize, post, options)
post
end
def capture_params(money, credit_card, options = {})
post = {}
add_amount(post, money, options)
add_additional_params(:capture, post, options)
post
end
def create_store(options = {})
post = {}
commit('/cards', post)
end
def authorize_store(identification, credit_card, options = {})
post = {}
add_credit_card_or_reference(post, credit_card, options)
commit(synchronized_path("/cards/#{identification}/authorize"), post)
end
def create_token(identification, options)
post = {}
commit(synchronized_path("/cards/#{identification}/tokens"), post)
end
def create_payment(money, options = {})
post = {}
add_currency(post, money, options)
add_invoice(post, options)
commit('/payments', post)
end
def commit(action, params = {})
success = false
begin
response = parse(ssl_post(self.live_url + action, params.to_json, headers))
success = successful?(response)
rescue ResponseError => e
response = response_error(e.response.body)
rescue JSON::ParserError
response = json_error(response)
end
Response.new(
success,
message_from(success, response),
response,
test: test?,
authorization: authorization_from(response)
)
end
def authorization_from(response)
if response['token']
response['token'].to_s
else
response['id'].to_s
end
end
def add_currency(post, money, options)
post[:currency] = options[:currency] || currency(money)
end
def add_amount(post, money, options)
post[:amount] = options[:amount] || amount(money)
end
def add_autocapture(post, value)
post[:auto_capture] = value
end
def add_order_id(post, options)
requires!(options, :order_id)
post[:order_id] = format_order_id(options[:order_id])
end
def add_invoice(post, options)
add_order_id(post, options)
post[:invoice_address] = map_address(options[:billing_address]) if options[:billing_address]
post[:shipping_address] = map_address(options[:shipping_address]) if options[:shipping_address]
%i[metadata branding_id variables].each do |field|
post[field] = options[field] if options[field]
end
end
def add_additional_params(action, post, options = {})
MD5_CHECK_FIELDS[API_VERSION][action].each do |key|
key = key.to_sym
post[key] = options[key] if options[key]
end
end
def add_credit_card_or_reference(post, credit_card_or_reference, options = {})
post[:card] ||= {}
if credit_card_or_reference.is_a?(String)
post[:card][:token] = credit_card_or_reference
else
post[:card][:number] = credit_card_or_reference.number
post[:card][:cvd] = credit_card_or_reference.verification_value
post[:card][:expiration] = expdate(credit_card_or_reference)
post[:card][:issued_to] = credit_card_or_reference.name
end
if options[:three_d_secure]
post[:card][:cavv] = options.dig(:three_d_secure, :cavv)
post[:card][:eci] = options.dig(:three_d_secure, :eci)
post[:card][:xav] = options.dig(:three_d_secure, :xid)
end
end
def parse(body)
JSON.parse(body)
end
def successful?(response)
has_error = response['errors']
invalid_code = invalid_operation_code?(response)
!(has_error || invalid_code)
end
def message_from(success, response)
success ? 'OK' : (response['message'] || invalid_operation_message(response) || 'Unknown error - please contact QuickPay')
end
def invalid_operation_code?(response)
if response['operations']
operation = response['operations'].last
operation && operation['qp_status_code'] != '20000'
end
end
def invalid_operation_message(response)
response['operations'] && response['operations'].last['qp_status_msg']
end
def map_address(address)
return {} if address.nil?
requires!(address, :name, :address1, :city, :zip, :country)
country = Country.find(address[:country])
{
name: address[:name],
street: address[:address1],
city: address[:city],
region: address[:address2],
zip_code: address[:zip],
country_code: country.code(:alpha3).value
}
end
def format_order_id(order_id)
truncate(order_id.to_s.delete('#'), 20)
end
def headers
auth = Base64.strict_encode64(":#{@options[:api_key]}")
{
'Authorization' => 'Basic ' + auth,
'User-Agent' => "Quickpay-v#{API_VERSION} ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
'Accept' => 'application/json',
'Accept-Version' => "v#{API_VERSION}",
'Content-Type' => 'application/json'
}
end
def response_error(raw_response)
parse(raw_response)
rescue JSON::ParserError
json_error(raw_response)
end
def json_error(raw_response)
msg = 'Invalid response received from the Quickpay API.'
msg += " (The raw response returned by the API was #{raw_response.inspect})"
{ 'message' => msg }
end
def synchronized_path(path)
"#{path}?synchronized"
end
end
end
end