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

Commit

Permalink
Ensure one braintree customer per spree user
Browse files Browse the repository at this point in the history
Ensure that when creating braintree customers we create one per spree
user. To do this, first generate a token with the given customer id if
available. When a braintree customer already exists, rather than create
a new braintree customer, add the payment method to the existing
braintree customer.
  • Loading branch information
skukx committed Feb 5, 2019
1 parent dde562b commit b683d3c
Show file tree
Hide file tree
Showing 13 changed files with 606 additions and 651 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
module SolidusPaypalBraintree
class ClientTokensController < Spree::Api::BaseController
skip_before_action :authenticate_user

class ClientTokensController < Spree::BaseController
before_action :load_gateway

def create
render json: { client_token: @gateway.generate_token, payment_method_id: @gateway.id }
render json: { client_token: generate_token, payment_method_id: @gateway.id }
end

private
Expand All @@ -18,5 +16,20 @@ def load_gateway
@gateway = ::SolidusPaypalBraintree::Gateway.where(active: true).merge(store_payment_methods_scope).first!
end
end

def generate_token
options = {}
options[:customer_id] = customer_id if customer_id.present?

@gateway.generate_token(options)
rescue ::SolidusPaypalBraintree::Gateway::TokenGenerationDisabledError => error
Rails.logger.error error
nil
end

def customer_id
return unless try_spree_current_user&.braintree_customer
try_spree_current_user.braintree_customer.braintree_customer_id
end
end
end
53 changes: 44 additions & 9 deletions app/models/solidus_paypal_braintree/gateway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module SolidusPaypalBraintree
class Gateway < ::Spree::PaymentMethod
include RequestProtection

class TokenGenerationDisabledError < StandardError; end

# Error message from Braintree that gets returned by a non voidable transaction
NON_VOIDABLE_STATUS_ERROR_REGEXP = /can only be voided if status is authorized/

Expand Down Expand Up @@ -193,18 +195,33 @@ def try_void(payment)
#
# @api public
# @param payment [Spree::Payment]
# @return [SolidusPaypalBraintree::Customer]
# @return [SolidusPaypalBraintree::Customer, NilClass]
def create_profile(payment)
source = payment.source
user = payment.order.user
source.customer = user&.braintree_customer

return if source.token.present? || source.nonce.nil?

if source.customer.present?
# Add new payment method to existing customer
result = braintree.payment_method.create(
braintree_payment_method_params_for(payment)
)
fail Spree::Core::GatewayError, result.message unless result.success?

return if source.token.present? || source.customer.present? || source.nonce.nil?
source.token = result.payment_method.token
source.save!

return source.customer
end

result = braintree.customer.create(customer_profile_params(payment))
fail Spree::Core::GatewayError, result.message unless result.success?

customer = result.customer

source.create_customer!(braintree_customer_id: customer.id).tap do
source.create_customer!(user_id: user&.id, braintree_customer_id: customer.id).tap do
if customer.payment_methods.any?
source.token = customer.payment_methods.last.token
end
Expand All @@ -213,12 +230,16 @@ def create_profile(payment)
end
end

# @raise [TokenGenerationDisabledError]
# If `preferred_token_generation_enabled` is false
#
# @param options [Hash]
# The options for braintree.
# See: https://developers.braintreepayments.com/reference/request/client-token/generate/ruby
#
# @return [String]
# The token that should be used along with the Braintree js-client sdk.
#
# returns an error message if `preferred_token_generation_enabled` is
# set to false.
#
# @example
# <script>
# var token = #{Spree::Braintree::Gateway.first!.generate_token}
Expand All @@ -232,9 +253,12 @@ def create_profile(payment)
# }
# );
# </script>
def generate_token
return TOKEN_GENERATION_DISABLED_MESSAGE unless preferred_token_generation_enabled
braintree.client_token.generate
def generate_token(options = {})
unless preferred_token_generation_enabled
raise TokenGenerationDisabledError, TOKEN_GENERATION_DISABLED_MESSAGE
end

braintree.client_token.generate(options)
end

def payment_profiles_supported?
Expand Down Expand Up @@ -369,5 +393,16 @@ def customer_profile_params(payment)

params
end

def braintree_payment_method_params_for(payment)
source = payment.source
user = payment.order.user

params = {}
params[:payment_method_nonce] = source.nonce
params[:customer_id] = user.braintree_customer.braintree_customer_id

params
end
end
end
4 changes: 4 additions & 0 deletions app/models/spree/user_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Spree.user_class.class_eval do
has_one :braintree_customer, class_name: 'SolidusPaypalBraintree::Customer',
inverse_of: :user
end
64 changes: 16 additions & 48 deletions spec/fixtures/cassettes/admin/invalid_credit_card.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b683d3c

Please sign in to comment.