Skip to content

Instantly share code, notes, and snippets.

@ryenski
Last active December 13, 2022 00:05
Show Gist options
  • Save ryenski/0a9cee0509187989f938c713103ff750 to your computer and use it in GitHub Desktop.
Save ryenski/0a9cee0509187989f938c713103ff750 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
module MxIsoagent
class Client
BOARDING_URL = Rails.configuration.mx_iso_agent['boarding_url']
CHECKOUT_URL = Rails.configuration.mx_iso_agent['checkout_url']
INVITE_CODE = Rails.configuration.mx_iso_agent['invite_code']
BOARDING_KEYS = Rails.configuration.mx_iso_agent['api_key']
BUSINESS_TYPES = [
%w[Retail Retail],
%w[Internet Internet],
%w[Other Other],
%w[Healthcare Healthcare],
%w[Education Education],
%w[Government Government],
['Charity/Non-Profit', 'Charity_NonProfit'],
%w[B2B B2B]
].freeze
CORPORATION_TYPES = [
['Sole Proprietor', 'SoleProprietor'],
%w[Partnership Partnership],
['LLC/LLP', 'LLC_LLP'],
['C Corporation', 'CCorp'],
['S Corporation', 'SCorp'],
%w[Government Government],
['501(c) Tax Exempt', 'TaxExempt501']
].freeze
def initialize(gateway)
@gateway = gateway
@payload = Payload.new(gateway)
end
attr_reader :gateway, :payload
def post_invite
HTTParty.post("#{BOARDING_URL}/invite",
body: MxIsoagent::Payload.new(gateway).to_json,
headers: master_headers)
end
def fetch_merchant
HTTParty.get("#{BOARDING_URL}/merchant",
query: { referenceId: gateway.reference_token },
headers: master_headers)
end
def post_keys
HTTParty.post("#{CHECKOUT_URL}/application",
body: {
roleId: 2,
merchantId: gateway.merchant_id,
name: 'Dime Payments Keys',
locked: false
}.to_json,
headers: merchant_headers)
end
def fetch_keys
HTTParty.get("#{CHECKOUT_URL}/application",
query: { merchantId: gateway.merchant_id },
headers: merchant_headers)
end
private
def master_headers
{
'Content-Type' => 'application/json',
'Authorization' => "Basic #{BOARDING_KEYS}"
}
end
def merchant_headers
{
'Content-Type' => 'application/json',
'Authorization' => "Basic #{merchant_credentials}"
}
end
def email
gateway.merchant_username
end
def password
gateway.merchant_password
end
def merchant_credentials
Base64.strict_encode64("#{email}:#{password}")
end
end
end
# frozen_string_literal: true
module MxIsoagent
class BoardingError < StandardError; end
class MerchantAccountCreator
def initialize(gateway)
@gateway = gateway
@error_messages = []
end
attr_accessor :gateway, :error_messages
def save
create_merchant_with_invite_code
fetch_and_save_merchant_id
create_api_keys
fetch_and_save_api_keys
result
end
def create_merchant_with_invite_code
# Create the merchant (no echo)
# Returns only response code
# 201 Created successfully
# Any other code is failure
with_response_handling { client.post_invite }
end
# Retrieve the newly created merchant_id based on the reference Id
# rubocop:disable Metrics/AbcSize
def fetch_and_save_merchant_id
merchant_response = (with_response_handling { client.fetch_merchant })
# ubocop:disable Style/GuardClause
return unless merchant_response.success?
# response will be empty if no keys were found.
raise BoardingError, 'Merchant keys could not be found' unless merchant_response.parsed_response.any?
gateway.update_attributes(
merchant_id: merchant_response.parsed_response.first['id'],
status: merchant_response.parsed_response.first['status']
)
end
# rubocop:enable Metrics/AbcSize
# Create the API keys (no echo)
def create_api_keys
with_response_handling { client.post_keys }
end
# Retrieve and save the API keys
# We do this without response handling because the production API sometimes returns a 403 response if we do this too quickly.
def fetch_and_save_api_keys
keys_response = client.fetch_keys
return unless keys_response.success?
gateway.update_attributes(
merchant_api_key: keys_response['records'][0]['apiKey'],
merchant_api_secret: keys_response['records'][0]['apiSecret']
)
end
def update_account_status
merchant_response = with_response_handling { client.fetch_merchant }
gateway.update_attributes(status: merchant_response.parsed_response.first['status']) if merchant_response.success?
end
private
def with_response_handling
yield.tap do |response|
gateway.responses << response
raise BoardingError, response.parsed_response['details']&.join(' ') unless response.success?
end
end
def result
OpenStruct.new(
success?: true,
gateway: gateway
)
end
def payload
Payload.new(gateway).to_json
end
def client
@client ||= MxIsoagent::Client.new(gateway)
end
end
end
# frozen_string_literal: true
module MxIsoagent
# rubocop:disable Metrics/ClassLength
class Payload
def initialize(gateway)
@gateway = gateway
@payments_form_data = ActiveSupport::HashWithIndifferentAccess.new(@gateway.payments_form_data)
end
attr_reader :gateway, :payments_form_data
def to_json
to_h.to_json
end
def to_h
%i[base_params hard_coded_params auth_params bank_params
terms_and_conditions_params location_params legal_params owner_params].inject({}) do |k, v|
k.merge!(send(v))
end
end
private
# include ActionView::Helpers::TextHelper
include ActiveSupport::Inflector
# Statement Name. string(50)
# https://docs.mxisoagent.com/docs/modelInfo/MXA.API.Services.Models.Merchant
def statement_name
return unless payments_form_data['organization_name']
underscore(parameterize(payments_form_data['organization_name'])).upcase[0, 49]
end
def email
gateway.merchant_username
end
def password
gateway.merchant_password
end
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def base_params
{
inviteCode: Rails.configuration.mx_iso_agent['invite_code'],
referenceId: gateway.reference_token,
customerServicePhone: payments_form_data['organization_phone'],
statementName: statement_name,
statementPhone: payments_form_data['organization_phone']
}
end
def hard_coded_params
{
delivery: '0',
hasPhysicalLocation: false,
developerPackageOn: true,
website: 'https://example.com',
returnPolicy: 'Not provided',
averageTicket: '100',
averageMonthlyVolume: '25000',
descriptionOfProducts: 'donations',
sponsorBankName: 'Synovus Bank, Columbus, GA',
businessType: '8661',
legalTaxIdType: 'EIN'
}
end
def auth_params
{
mxmUserEmail: email,
mxmUserPassword: password
}
end
def bank_params
{
depositRoutingNumber: payments_form_data['routing_number'],
depositAccountNumber: payments_form_data['account_number'],
depositAccountType: payments_form_data['account_type']
}
end
def terms_and_conditions_params
{
agreeTermsAndConditions: payments_form_data['accepts_merchant_services_agreement'],
agreeMerchantServices: payments_form_data['accepts_merchant_services_agreement'],
agreeInfoAccurate: payments_form_data['accepts_pricing_and_fees'],
agreePricingSchedule: payments_form_data['certifies_information_is_correct']
}
end
def location_params
{
locationName: payments_form_data['organization_name'],
locationAddress: payments_form_data['organization_street_address'],
locationCity: payments_form_data['organization_city'],
locationState: payments_form_data['organization_state'],
locationZip: payments_form_data['organization_zip'],
locationPhone: payments_form_data['organization_phone']
}
end
def legal_params
{
businessCategory: payments_form_data['business_type'],
legalAddressIsBusinessAddress: true,
legalName: payments_form_data['legal_entity_name'],
legalEntity: 1,
legalAddress: payments_form_data['legal_entity_street_address'],
legalCity: payments_form_data['legal_entity_city'],
legalState: payments_form_data['legal_entity_state'],
legalZip: payments_form_data['legal_entity_zip'],
legalPhone: payments_form_data['organization_phone'],
legalCorporationType: payments_form_data['corporate_structure'],
legalCorporationDate: payments_form_data['legal_entity_incorporation_date'],
legalTaxFilingName: payments_form_data['legal_entity_name'],
legalTaxId: payments_form_data['legal_entity_tax_id'],
legalEntityState: payments_form_data['legal_entity_state'],
businessName: payments_form_data['legal_entity_name']
}
end
def owner_params
{
ownerPercentage: '100',
ownerAddressIsBusinessAddress: true,
ownerFirstName: payments_form_data['legal_representative_first_name'],
ownerLastName: payments_form_data['legal_representative_last_name'],
ownerSsn: payments_form_data['legal_representative_ssn'],
ownerMobile: payments_form_data['legal_representative_phone'],
ownerEmail: payments_form_data['legal_representative_email'],
ownerAddress: payments_form_data['legal_representative_street_address'],
ownerCity: payments_form_data['legal_representative_city'],
ownerState: payments_form_data['legal_representative_state'],
ownerZip: payments_form_data['legal_representative_zip'],
ownerDob: payments_form_data['legal_representative_dob']
}
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
end
# rubocop:enable Metrics/ClassLength
end
# frozen_string_literal: true
class PaymentsForm
include ActiveModel::Model
include ActiveModel::Validations
# Don't rename or remove these attributes. It will result in
# ActiveModel::UnknownAttributeError being raised.
REQUIRED_ATTRIBUTES = %i[
organization_name
organization_street_address
organization_city
organization_state
organization_zip
organization_phone
organization_email
legal_entity_name
legal_entity_street_address
legal_entity_city
legal_entity_state
legal_entity_zip
legal_entity_incorporation_date
legal_entity_tax_id
corporate_structure
business_type
routing_number
account_number
account_type
legal_representative_first_name
legal_representative_last_name
legal_representative_email
legal_representative_ssn
legal_representative_dob
legal_representative_phone
legal_representative_email
legal_representative_street_address
legal_representative_city
legal_representative_state
legal_representative_zip
].freeze
ACCEPTANCE_ATTRIBUTES = %i[
accepts_merchant_services_agreement
accepts_pricing_and_fees
certifies_information_is_correct
].freeze
attr_accessor(*REQUIRED_ATTRIBUTES)
attr_accessor(*ACCEPTANCE_ATTRIBUTES)
def initialize(args)
args = args.select { |k, _v| self.class.required_attributes.include?(k.intern) }
super(args)
end
validates(*REQUIRED_ATTRIBUTES, presence: true)
validates(*ACCEPTANCE_ATTRIBUTES, acceptance: { message: '' }, allow_nil: false)
def self.required_attributes
REQUIRED_ATTRIBUTES | ACCEPTANCE_ATTRIBUTES
end
end
# frozen_string_literal: true
class Admin::Settings::PaymentsController < Admin::BaseController
before_action :load_gateway
def show; end
def new
@payments_form = PaymentsForm.new(@gateway.payments_form_data)
end
def create
@payments_form = PaymentsForm.new(payments_form_params)
# Update the saved merchant data even if it's not valid...
@gateway.update_attributes(payments_form_data: @payments_form.as_json)
if @payments_form.valid?
# Invoke the MerchantAccountCreator
begin
MxIsoagent::MerchantAccountCreator.new(@gateway).save
# rescue MxIsoagent::BoardingError => e
# @payments_form.errors.add(:base, e.message)
end
end
respond_with @payments_form, location: admin_settings_payments_path
end
def update
MxIsoagent::MerchantAccountCreator.new(@gateway).fetch_and_save_api_keys
redirect_to action: :show
end
private
def load_gateway
@organization = Current.organization
@gateway = @organization.gateway || @organization.build_gateway
end
def payments_form_params
params.require(:payments_form).permit(*PaymentsForm.required_attributes)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment