Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Bring braintree module nesting in line with go_cardless
Browse files Browse the repository at this point in the history
  • Loading branch information
osahyoun committed Jul 26, 2016
1 parent 679f1b4 commit dfeacba
Show file tree
Hide file tree
Showing 52 changed files with 1,702 additions and 1,733 deletions.
2 changes: 1 addition & 1 deletion app/controllers/api/payment/braintree_controller.rb
Expand Up @@ -24,7 +24,7 @@ def payment_options
end

def client
PaymentProcessor::Clients::Braintree
PaymentProcessor::Braintree
end

def page
Expand Down
2 changes: 1 addition & 1 deletion app/models/member.rb
@@ -1,5 +1,5 @@
class Member < ActiveRecord::Base
has_one :customer, class_name: "Payment::BraintreeCustomer"
has_one :customer, class_name: "Payment::Braintree::Customer"
has_many :go_cardless_customers, class_name: "Payment::GoCardless::Customer"
has_paper_trail on: [:update, :destroy]

Expand Down
23 changes: 12 additions & 11 deletions app/models/payment.rb → app/models/payment/braintree.rb
@@ -1,7 +1,7 @@
module Payment
module Payment::Braintree
class << self
def table_name_prefix
'payment_'
'payment_braintree_'
end

def write_transaction(bt_result, page_id, member_id, existing_customer, save_customer=true)
Expand All @@ -10,7 +10,7 @@ def write_transaction(bt_result, page_id, member_id, existing_customer, save_cus

def write_subscription(subscription_result, page_id, action_id, currency)
if subscription_result.success?
Payment::BraintreeSubscription.create({
Payment::Braintree::Subscription.create({
subscription_id: subscription_result.subscription.id,
amount: subscription_result.subscription.price,
merchant_account_id: subscription_result.subscription.merchant_account_id,
Expand All @@ -26,7 +26,7 @@ def write_customer(bt_customer, bt_payment_method, member_id, existing_customer)
end

def customer(email)
customer = Payment::BraintreeCustomer.find_by(email: email)
customer = Payment::Braintree::Customer.find_by(email: email)
return customer if customer.present?
member = Member.find_by(email: email)
member.try(:customer)
Expand All @@ -49,9 +49,9 @@ def build
if @customer.present?
@customer.update(customer_attrs)
else
@customer = Payment::BraintreeCustomer.create(customer_attrs)
@customer = Payment::Braintree::Customer.create(customer_attrs)
end
Payment::BraintreePaymentMethod.find_or_create_by!(
Payment::Braintree::PaymentMethod.find_or_create_by!(
customer: @customer,
token: @bt_payment_method.token
)
Expand Down Expand Up @@ -119,17 +119,17 @@ def build
# a Payment::BraintreeCustomer gets created for both successful and failed transactions. The customer_id will be nil,
# though, because transaction.customer_details.id is nil for failed transaction.
# For webhooks, @existing_customer will be present.
@customer = @existing_customer || Payment::BraintreeCustomer.find_or_create_by!(
@customer = @existing_customer || Payment::Braintree::Customer.find_or_create_by!(
member_id: @member_id,
customer_id: transaction.customer_details.id)
# If the transaction was a failure, there is no payment method - don't persist a nil payment method locally.
# Make the foreign key to the payment method token nil for the locally persisted failed transaction.
@local_payment_method_id = payment_method_token.blank? ? nil : Payment::BraintreePaymentMethod.find_or_create_by!(
@local_payment_method_id = payment_method_token.blank? ? nil : Payment::Braintree::PaymentMethod.find_or_create_by!(
customer: @customer,
token: payment_method_token).id


record = ::Payment::BraintreeTransaction.create!(transaction_attrs)
record = ::Payment::Braintree::Transaction.create!(transaction_attrs)

return false unless successful?

Expand All @@ -155,7 +155,7 @@ def transaction_attrs
page_id: @page_id
}.tap do |data|
if transaction.try(:subscription_id)
data[:subscription] = Payment::BraintreeSubscription.find_by_subscription_id(transaction.subscription_id)
data[:subscription] = Payment::Braintree::Subscription.find_by_subscription_id(transaction.subscription_id)
end
end
end
Expand Down Expand Up @@ -185,7 +185,7 @@ def card
end

def status
Payment::BraintreeTransaction.statuses[(successful? ? :success : :failure)]
Payment::Braintree::Transaction.statuses[(successful? ? :success : :failure)]
end

def successful?
Expand Down Expand Up @@ -213,3 +213,4 @@ def payment_method_token
end
end


8 changes: 8 additions & 0 deletions app/models/payment/braintree/customer.rb
@@ -0,0 +1,8 @@
class Payment::Braintree::Customer < ActiveRecord::Base
belongs_to :member
has_many :payment_methods, class_name: 'Payment::Braintree::PaymentMethod', foreign_key: 'customer_id'

def default_payment_method
payment_methods.order('created_at desc').first
end
end
4 changes: 4 additions & 0 deletions app/models/payment/braintree/payment_method.rb
@@ -0,0 +1,4 @@
class Payment::Braintree::PaymentMethod < ActiveRecord::Base
belongs_to :customer, class_name: 'Payment::Braintree::Customer'
has_many :transactions, class_name: 'Payment::Braintree::Transaction', foreign_key: 'payment_method_id'
end
5 changes: 5 additions & 0 deletions app/models/payment/braintree/subscription.rb
@@ -0,0 +1,5 @@
class Payment::Braintree::Subscription < ActiveRecord::Base
belongs_to :page
belongs_to :action
has_many :transactions, class_name: 'Payment::Braintree::Transaction', foreign_key: :subscription_id
end
7 changes: 7 additions & 0 deletions app/models/payment/braintree/transaction.rb
@@ -0,0 +1,7 @@
class Payment::Braintree::Transaction < ActiveRecord::Base
belongs_to :page
belongs_to :payment_method, class_name: 'Payment::Braintree::PaymentMethod'
belongs_to :customer, class_name: 'Payment::Braintree::Customer', primary_key: 'customer_id'
belongs_to :subscription, class_name: 'Payment::Braintree::Subscription'
enum status: [:success, :failure]
end
8 changes: 0 additions & 8 deletions app/models/payment/braintree_customer.rb

This file was deleted.

4 changes: 0 additions & 4 deletions app/models/payment/braintree_payment_method.rb

This file was deleted.

5 changes: 0 additions & 5 deletions app/models/payment/braintree_subscription.rb

This file was deleted.

7 changes: 0 additions & 7 deletions app/models/payment/braintree_transaction.rb

This file was deleted.

2 changes: 0 additions & 2 deletions app/services/action_builder.rb
@@ -1,5 +1,3 @@
require_relative 'action_queue'

module ActionBuilder
def build_action(extra_attrs = {})
@extra_attrs = extra_attrs
Expand Down
@@ -0,0 +1,25 @@
module PaymentProcessor
module Braintree

module ActsLikeSelectorWithCurrency
def self.included(klass)
klass.extend ClassMethods
end

module ClassMethods
def for_currency(currency)
new(currency).select_or_raise
end
end

def initialize(currency)
@currency = currency
end

def raise_error(message)
raise PaymentProcessor::Exceptions::InvalidCurrency, message
end
end
end
end

112 changes: 112 additions & 0 deletions lib/payment_processor/braintree/error_processing.rb
@@ -0,0 +1,112 @@
module PaymentProcessor
module Braintree
class ErrorProcessing

# Collection of error codes, generated by braintree for invalid
# customer/credit card transaction data.
#
# See https://developers.braintreepayments.com/reference/general/validation-errors/all/ruby
# for code definitions.
#
USER_ERROR_CODES = [
(81801..81813),
(81501..81503),
(91814..91826),
(81604..81608),
(81613..81616),
(81709..81717),
(91723..91726),
81827,
91803,
81601,
81723,
81703,
81706,
81707,
81736,
81737,
81528,
81509].map{|code| code.is_a?(Range) ? code.to_a : code }
.flatten
.freeze

FILTER = -> (error) { USER_ERROR_CODES.include?( error.code.to_i ) }

# locale is unused, added to match API for GoCardless
def initialize(response, locale: nil)
@response = response
end

def process
return user_errors if user_errors.any?
return processor_errors if processor_errors.any?

raise_system_errors if system_errors.any?
end

private

def raise_system_errors
raise ::Braintree::ValidationsFailed.new(error_messages)
end

def error_messages
system_errors.map{|e| "#{e.message} (#{e.code} on #{e.attribute})"}.join(', ')
end

def user_errors
@user_errors ||= errors.select(&FILTER)
end

def system_errors
@system_errors ||= errors.reject(&FILTER)
end

def errors
@response.errors
end

def processor_errors
if @response.transaction
case @response.transaction.status
when 'processor_declined'
processor_declined_error
when 'gateway_rejected'
gateway_rejected_error
when 'settlement_declined'
settlement_declined_error
else
[]
end
else
[]
end
end

def processor_declined_error
[{
declined: true,
code: @response.transaction.processor_response_code,
message: @response.transaction.processor_response_text
}]
end

def settlement_declined_error
[{
declined: true,
code: @response.transaction.processor_settlement_response_code,
message: @response.transaction.processor_settlement_response_text
}]
end

def gateway_rejected_error
[{
declined: true,
code: '',
message: @response.transaction.gateway_rejection_reason
}]
end
end
end
end

17 changes: 17 additions & 0 deletions lib/payment_processor/braintree/merchant_account_selector.rb
@@ -0,0 +1,17 @@
module PaymentProcessor
module Braintree
class MerchantAccountSelector
include ActsLikeSelectorWithCurrency

MERCHANT_ACCOUNTS = Settings.braintree.merchants.freeze

def select_or_raise
raise_error if @currency.blank?
id = MERCHANT_ACCOUNTS[@currency.upcase.to_sym]
raise_error("No merchant account is associated with this currency: #{@currency}") unless id
id
end
end
end
end

51 changes: 51 additions & 0 deletions lib/payment_processor/braintree/populator.rb
@@ -0,0 +1,51 @@
module PaymentProcessor
module Braintree
class Populator

def customer_options
@customer_options ||= {
first_name: @user[:first_name] || namesplitter.first_name,
last_name: @user[:last_name] || namesplitter.last_name,
email: @user[:email] || ''
}
end

def billing_options
@billing_options ||= {
first_name: customer_options[:first_name],
last_name: customer_options[:last_name]
}.tap do |options|
populate( options, :region, [:province, :state, :region])
populate( options, :company, [:company])
populate( options, :locality, [:city, :locality])
populate( options, :postal_code, [:zip, :zip_code, :postal, :postal_code])
populate( options, :street_address, [:address, :address1, :street_address])
populate( options, :extended_address, [:apartment, :address2, :extended_address])
populate( options, :country_code_alpha2, [:country, :country_code, :country_code_alpha2])
end
end

def populate(options, field, pick_from)
pick_from.each do |key|
options[field] = @user[key] if @user[key].present?
end
end

def namesplitter
@splitter ||= NameSplitter.new(full_name: @user[:full_name] || @user[:name])
end

def existing_customer
@existing_customer ||= Payment::Braintree.customer(@user[:email])
end

def success?
@result.success?
end

def error_container
@result
end
end
end
end

0 comments on commit dfeacba

Please sign in to comment.