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

one to one relationship for gateway customer #212

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug with the ClientTokensController. The controller generates the token for the current user. However if that endpoint is called from admin then it will attach the payment source to the administrating user rather than the user on the order

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debating if the token should be generated when the view is rendered. Maybe as a hidden input

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, what's the difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See implementation 2 here: solidusio/solidus_braintree#79

The main difference is that the view no longer makes an api request to obtain the client token but rather just generates it when the view renders. The api request was only made if the new braintree payment method was selected (javascript events). This change generates a token even if the user may not be adding a new payment method. Therefore it could mean a slight increase in api requests sent to braintree. I think this should be negligible though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about who is using this gem without a Rails frontend, eg on React or Vue.js? I think we still need this controller to work for them, right?

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 @@ -196,18 +198,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 = source.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 @@ -216,12 +233,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 @@ -235,9 +256,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 @@ -378,5 +402,16 @@ def customer_profile_params(payment)

params
end

def braintree_payment_method_params_for(payment)
source = payment.source
user = source.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