Skip to content

Commit

Permalink
THREESCALE-11011: Authorize.net is a living dead 🧟‍♀️ (#3771)
Browse files Browse the repository at this point in the history
* Authorize.net is a living dead

Reverts 65a284e, afa1b52 and a31cd40
  • Loading branch information
jlledom committed Apr 30, 2024
1 parent 5397809 commit eea43d5
Show file tree
Hide file tree
Showing 45 changed files with 889 additions and 55 deletions.
1 change: 1 addition & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ Style/UnneededPercentQ:
# SupportedStyles: snake_case, camelCase
Style/VariableName:
Exclude:
- 'lib/payment_gateways/authorize_net_cim_crypt.rb'
- 'app/lib/three_scale/swagger/specification.rb'
- 'test/unit/logic/provider_upgrade_test.rb'

Expand Down
29 changes: 29 additions & 0 deletions app/assets/javascripts/payment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

// I'm comming back to this.


// var Payment = {
//
// init: function(card_type) {
// // if(card_type)
// this.showCards(card_type);
// },
//
// showCards: function() {
//
// // $$('#supported_cardtypes ul').invoke('hide');
// $('supported_cardtypes').show();
// // $(card).show();
// },
//
// listen: function(){
// $('account_payment_gateway_type').observe()
// }
//
// }

// Payment.SupportedCards = {
// 'authorize_net' : ['visa', 'master', 'american_express', 'discover'],
// 'braintree' : ['visa', 'master', 'american_express', 'discover']
// }

8 changes: 8 additions & 0 deletions app/controllers/admin/api/credit_cards_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ def update
:billing_address_city, :billing_address_country, :credit_card_expiration_year,
:credit_card_expiration_month

if @buyer.provider_account.payment_gateway_type == :authorize_net
forced_parameters << :credit_card_authorize_net_payment_profile_token
end

failed = nil
unless failed = required_params(forced_parameters)

@buyer.credit_card_auth_code = params[:credit_card_token]

@buyer.credit_card_authorize_net_payment_profile_token =
params[:credit_card_authorize_net_payment_profile_token]

@buyer.billing_address_name = params[:billing_address_name]
@buyer.billing_address_address1 = params[:billing_address_address]

Expand Down
6 changes: 5 additions & 1 deletion app/helpers/admin/payment_details_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ def credit_card_stored_status(account, link_to_payment_gateway = false)
txt = if account.credit_card_stored?
ccexp = l account.credit_card_expires_on_with_default, format: :month

"Credit Card details are on file. Card expires in: #{ccexp}"
if current_account.payment_gateway_type != :authorize_net
"Credit Card details are on file. Card expires in: #{ccexp}"
else
"Credit Card details are on file"
end
else
'Credit Card details are not stored'
end
Expand Down
132 changes: 132 additions & 0 deletions app/lib/payment_gateways/authorize_net_cim_crypt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

module PaymentGateways
class AuthorizeNetCimCrypt < PaymentGatewayCrypt

def authorize_api_url
::ActiveMerchant::Billing::AuthorizeNetCimGateway.public_send(test? ? :test_url : :live_url)
end

def form_url
@form_url ||= "https://#{test? ? 'test' : 'secure'}.authorize.net/profile"
end

def create_profile
log_gateway_action("Creating Profile")

response = create_remote_profile
account.credit_card_auth_code = response.params['customer_profile_id']
account.save!
log_gateway_action("Creating Profile ok")
end

def action_form_url
action = account.credit_card_stored? ? "/editPayment" : "/addPayment"
form_url + action
end

def payment_profile
auth_response = provider.payment_gateway.cim_gateway
.get_customer_profile(:customer_profile_id => account.credit_card_auth_code)
return unless has_credit_card?(auth_response)
auth_response.params['profile']['payment_profiles']['customer_payment_profile_id']
end

def has_credit_card?(auth_response)
auth_response.success? &&
auth_response.params['profile'].key?('payment_profiles') &&
auth_response.params['profile']['payment_profiles'].key?('payment') &&
auth_response.params['profile']['payment_profiles']['payment'].key?('credit_card')
end

def get_token(args)
getHostedProfilePageRequest = <<~EOR
<?xml version="1.0" encoding="utf-8"?>
<getHostedProfilePageRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
<merchantAuthentication>
<name>#{args[:login]}</name>
<transactionKey>#{args[:trans_key]}</transactionKey>
</merchantAuthentication>
<customerProfileId>#{args[:profile_id]}</customerProfileId>
<hostedProfileSettings>
<setting>
<settingName>hostedProfileReturnUrl</settingName>
<settingValue>#{args[:ok_url]}</settingValue>
</setting>
<setting>
<settingName>hostedProfileReturnUrlText</settingName>
<settingValue>Continue to settings</settingValue>
</setting>
<setting>
<settingName>hostedProfilePageBorderVisible</settingName>
<settingValue>true</settingValue>
</setting>
</hostedProfileSettings>
</getHostedProfilePageRequest>
EOR

begin
log_gateway_action("Getting token for user #{user.id}")
crypted_request = RestClient.post authorize_api_url, getHostedProfilePageRequest,
:content_type => "text/xml"

xml_reply = Nokogiri::XML::Document.parse(crypted_request)
if xml_reply.xpath("//api:resultCode",
'api' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd').text == 'Ok'
return xml_reply.xpath("//api:token", 'api' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd').text
else
notify_exception(IncorrectKeys.new(xml_reply))
code=xml_reply.xpath("//api:code", 'api' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd').text
raise IncorrectKeys, "code:#{code}."
end
log_gateway_action("user #{user.id} token aquired")
rescue SocketError => e
notify_exception(e)
raise PaymentGatewayDown, "Payment gateway offline. Try again in few minutes"
end

end

def update_user(auth_response)
return :error unless auth_response.params['messages']['result_code'] == 'Ok'

log_gateway_action("updating user #{user}")
payment_profiles = auth_response.params['profile']['payment_profiles']
if payment_profiles && payment_profiles['bill_to']
bill_info = payment_profiles['bill_to']
account.billing_address_name = bill_info['company']
account.billing_address_address1 = bill_info['address']
account.billing_address_city = bill_info['city']
account.billing_address_country = bill_info['country']
account.billing_address_state = bill_info['state']
account.billing_address_zip = bill_info['zip']
account.billing_address_phone = bill_info['phone_number']
end

account.credit_card_partial_number =
auth_response.params['profile']['payment_profiles']['payment']['credit_card']['card_number'][-4..-1]
account.credit_card_authorize_net_payment_profile_token =
auth_response.params['profile']['payment_profiles']['customer_payment_profile_id']
account.credit_card_expires_on = nil #We can't store expiration date as Authorize.net doesn't retrieve it
account.save!
end

# FIXME: delete_user_profile has nothing to do in this class
# Reusing Account#delete_cc_details
def delete_user_profile
account.delete_cc_details
account.save!
end

private

def create_remote_profile
response = provider.payment_gateway.cim_gateway
.create_customer_profile(profile: { email: user.email, description: buyer_reference })

# this is a nice trick, where we raise IncorectKeys whatever the response was
raise IncorrectKeys, response unless response.params['messages']['result_code'] == 'Ok'
response
end
end
end
1 change: 0 additions & 1 deletion app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ class Account < ApplicationRecord
attribute :credit_card_expires_on, :date
self.ignored_columns = %i[proxy_configs_file_name proxy_configs_content_type proxy_configs_file_size
proxy_configs_updated_at proxy_configs_conf_file_name proxy_configs_conf_content_type
credit_card_authorize_net_payment_profile_token
proxy_configs_conf_file_size proxy_configs_conf_updated_at]

# it has to be THE FIRST callback after create, so associations get the tenant id
Expand Down
13 changes: 13 additions & 0 deletions app/models/account/credit_card.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,31 @@ def credit_card
end
end


def credit_card_authorize_net_profile_stored?
if provider_account && provider_account.payment_gateway_type == :authorize_net
credit_card_auth_code.present?
else
credit_card_stored?
end
end

def credit_card_stored?
public_send(credit_card_stored_attribute).present?
end

def credit_card_stored_attribute
case provider_account.try(:payment_gateway_type)
when :authorize_net
:credit_card_authorize_net_payment_profile_token
when :stripe
:credit_card_partial_number
else
:credit_card_auth_code
end
end

# FIXME : Authorize.net does not provide expiration dates
def credit_card_expired?
if credit_card_expires_on
credit_card_expires_on_with_default.end_of_month < Time.zone.today
Expand Down Expand Up @@ -88,6 +100,7 @@ def delete_cc_details
self.credit_card_auth_code = nil
self.credit_card_expires_on = nil
self.credit_card_partial_number = nil
self.credit_card_authorize_net_payment_profile_token = nil
end

def unstore_credit_card!
Expand Down
2 changes: 2 additions & 0 deletions app/models/account/payment_details.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Account::PaymentDetails
:credit_card_auth_code,
:credit_card_partial_number,
:credit_card_expires_on,
:credit_card_authorize_net_payment_profile_token
].freeze

delegate(
Expand Down Expand Up @@ -48,5 +49,6 @@ def clear_cc_attributes
self[:credit_card_auth_code] = nil
self[:credit_card_partial_number] = nil
self[:credit_card_expires_on] = nil
self[:credit_card_authorize_net_payment_profile_token] = nil
end
end
3 changes: 2 additions & 1 deletion app/models/payment_detail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ class PaymentDetail < ApplicationRecord
:allow_blank => true,
:message => "must be the final 4 digits only" }
validates :buyer_reference, :payment_service_reference, :credit_card_partial_number, :credit_card_auth_code,
:payment_method_id, length: {maximum: 255}
:credit_card_authorize_net_payment_profile_token, :payment_method_id, length: {maximum: 255}

alias_attribute :credit_card_auth_code, :buyer_reference
alias_attribute :credit_card_authorize_net_payment_profile_token, :payment_service_reference

after_commit :notify_credit_card_changes, if: :notify_changes?

Expand Down
3 changes: 2 additions & 1 deletion app/models/payment_gateway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def non_boolean_fields

# So far hardcoded list of gateways, later maybe this will be loaded from a config file or db.
GATEWAYS = [
PaymentGateway.new(:authorize_net, deprecated: true, login: 'LoginID', password: 'Transaction Key'),
PaymentGateway.new(:braintree_blue, public_key: 'Public Key', merchant_id: 'Merchant ID', private_key: 'Private Key', three_ds_enabled: '3D Secure enabled', boolean: %i[three_ds_enabled]),
PaymentGateway.new(:stripe, login: 'Secret Key', publishable_key: 'Publishable Key', endpoint_secret: 'Webhook Signing Secret')
].freeze
Expand Down Expand Up @@ -49,7 +50,7 @@ def self.find(type)
#
# Example:
#
# PaymentGateway.implementation(:stripe) # ActiveMerchant::Billing::StripeGateway
# PaymentGateway.implementation(:authorize_net) # ActiveMerchant::Billing::AuthorizeNetGateway
#
# Note:
#
Expand Down
31 changes: 31 additions & 0 deletions app/services/finance/charging_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def initialize(gateway, buyer_reference:, amount:, options: {})

def call
case gateway
when ActiveMerchant::Billing::AuthorizeNetGateway
charge_with_authorize_net
when ActiveMerchant::Billing::StripePaymentIntentsGateway
charge_with_stripe_payment_intents
when ActiveMerchant::Billing::StripeGateway
Expand All @@ -26,6 +28,28 @@ def call

protected

def charge_with_authorize_net
profile_response = authorize_net_customer_profile
return profile_response unless profile_response.success?

payment_profiles = profile_response.params['profile']['payment_profiles']
payment_profile = payment_profiles.is_a?(Array) ? payment_profiles[-1] : payment_profiles # payment_profiles can be a Hash or an Array of hashes
payment_profile_id = payment_profile['customer_payment_profile_id']

gateway.cim_gateway.create_customer_profile_transaction(
transaction: {
customer_profile_id: buyer_reference,
customer_payment_profile_id: payment_profile_id,
type: :auth_capture,
amount: amount.to_f
}
)
end

def authorize_net_customer_profile
gateway.cim_gateway.get_customer_profile(customer_profile_id: buyer_reference)
end

def charge_with_stripe(opts = options)
options = opts.merge(customer: buyer_reference)
payment_method_id = options.delete(:payment_method_id)
Expand All @@ -43,3 +67,10 @@ def charge_with_braintree_blue
end
end
end

# Monkey patch to turn all AuthorizeNet gateway clients into AuthorizeNetCim ones
class ::ActiveMerchant::Billing::AuthorizeNetGateway
def cim_gateway
@cim_gateway ||= ::ActiveMerchant::Billing::AuthorizeNetCimGateway.new(options)
end
end
12 changes: 12 additions & 0 deletions app/views/payment_gateways/_authorize_net.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<form class="form-horizontal" method="post" action="<%= @form_url %>" id="formAuthorizeNetPage">
<input type="hidden" name="Token" value="<%= @token %>" />
<input type="hidden" name="PaymentProfileId" value="<%= @payment_profile_id %>" />

<fieldset>
<div class="form-group">
<div class="col-md-10 operations">
<input class="btn btn-primary pull-right" type="submit" onclick="document.getElementById('formAuthorizeNetPage').submit();" value="<%= text -%>" />
</div>
</div>
</fieldset>
</form>
2 changes: 1 addition & 1 deletion config/initializers/filter_parameter_logging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += %i[activation_code cms_token credit_card credit_card_auth_code
credit_card_expires_on
credit_card_authorize_net_payment_profile_token credit_card_expires_on
credit_card_partial_number crypted_password janrain_api_key lost_password_token
password password_digest payment_gateway_options payment_service_reference salt
site_access_code sso_key user_key access_token service_token provider_key app_key]
1 change: 1 addition & 0 deletions config/initializers/inflections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable << 'trash'
inflect.uncountable << 'braintree_blue'
inflect.uncountable << 'authorize_net'
inflect.uncountable << 'stripe'
inflect.uncountable << 'sudo'
inflect.uncountable << 'github'
Expand Down

This file was deleted.

1 change: 1 addition & 0 deletions db/oracle_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
t.text "vat_zero_text"
t.integer "default_account_plan_id", precision: 38
t.integer "default_service_id", precision: 38
t.string "credit_card_authorize_net_payment_profile_token"
t.integer "tenant_id", precision: 38
t.string "self_domain"
t.string "s3_prefix"
Expand Down
1 change: 1 addition & 0 deletions db/postgres_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
t.text "vat_zero_text"
t.bigint "default_account_plan_id"
t.bigint "default_service_id"
t.string "credit_card_authorize_net_payment_profile_token", limit: 255
t.bigint "tenant_id"
t.string "self_domain", limit: 255
t.string "s3_prefix", limit: 255
Expand Down

0 comments on commit eea43d5

Please sign in to comment.