From 31e785106dbf15a3bba43e0690a543feebba7b8c Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sun, 3 Apr 2022 20:59:05 -0500 Subject: [PATCH] 3D Secure Valuting --- config.ru | 37 +++++++++++++++++++++++++++--- lib/three_d_secure_repo.rb | 46 ++++++++++++++++++++++++++++++++++++++ views/credit_cards.slim | 27 ++++++++++++++++------ 3 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 lib/three_d_secure_repo.rb diff --git a/config.ru b/config.ru index 70917c31471a2471202d9282681e73aa5cac1230..b81a99515b98a33da3ad314d4bdcc464f21af0b7 100644 --- a/config.ru +++ b/config.ru @@ -16,6 +16,7 @@ if ENV["RACK_ENV"] == "development" end require_relative "lib/auto_top_up_repo" +require_relative "lib/three_d_secure_repo" require_relative "lib/electrum" require "sentry-ruby" @@ -35,6 +36,17 @@ DB.type_map_for_results = PG::BasicTypeMapForResults.new(DB) DB.type_map_for_queries = PG::BasicTypeMapForQueries.new(DB) class CreditCardGateway + class ErrorResult < StandardError + def self.for(result) + if result.verification&.status == "gateway_rejected" && + result.verification&.gateway_rejection_reason == "cvv" + new("fieldInvalidForCvv") + else + new(result.message) + end + end + end + def initialize(jid, customer_id=nil) @jid = jid @customer_id = customer_id @@ -86,14 +98,22 @@ class CreditCardGateway !@gateway.customer.find(customer_id).payment_methods.empty? end - def default_payment_method=(nonce) - @gateway.payment_method.create( + def default_method(nonce) + result = @gateway.payment_method.create( customer_id: customer_id, payment_method_nonce: nonce, options: { + verify_card: true, make_default: true } ) + raise ErrorResult.for(result) unless result.success? + + result + end + + def remove_method(token) + @gateway.payment_method.delete(token) end protected @@ -210,12 +230,23 @@ class JmpPay < Roda end r.post do - gateway.default_payment_method = params["braintree_nonce"] + result = gateway.default_method(params["braintree_nonce"]) + ThreeDSecureRepo.new.put_from_payment_method( + gateway.customer_id, + result.payment_method + ) topup.put( gateway.customer_id, params["auto_top_up_amount"].to_i ) "OK" + rescue ThreeDSecureRepo::Failed + gateway.remove_method($!.message) + response.status = 400 + "hostedFieldsFieldsInvalidError" + rescue CreditCardGateway::ErrorResult + response.status = 400 + $!.message end end end diff --git a/lib/three_d_secure_repo.rb b/lib/three_d_secure_repo.rb new file mode 100644 index 0000000000000000000000000000000000000000..e66a9dc68f4cea4f9abe40b89f927541248f4c09 --- /dev/null +++ b/lib/three_d_secure_repo.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class ThreeDSecureRepo + class Failed < StandardError; end + + def initialize(redis: REDIS) + @redis = redis + end + + def find(customer_id, token) + redis(:hget, customer_id, token) + end + + def put(customer_id, token, authid) + if !authid || authid.empty? + redis(:hdel, customer_id, token) + else + redis(:hset, customer_id, token, authid) + end + end + + def put_from_payment_method(customer_id, method) + return unless method.verification # Already vaulted + + three_d = method.verification.three_d_secure_info + if !three_d || + (three_d.liability_shift_possible && !three_d.liability_shifted) + raise Failed, method.token + end + + put( + customer_id, method.token, + three_d.three_d_secure_authentication_id + ) + end + +protected + + def redis(action, customer_id, *args) + @redis.public_send( + action, + "jmp_customer_three_d_secure_authentication_id-#{customer_id}", + *args + ) + end +end diff --git a/views/credit_cards.slim b/views/credit_cards.slim index 28a586fc697ad959b5e7aaf25ce9d4f9e5a35e74..876901c426013530ad382ec19bf0f2be3e90d869 100644 --- a/views/credit_cards.slim +++ b/views/credit_cards.slim @@ -34,7 +34,7 @@ form method="post" action="" input type="hidden" name="customer_id" value=customer_id input type="hidden" name="braintree_nonce" -script src="https://js.braintreegateway.com/web/dropin/1.26.0/js/dropin.min.js" +script src="https://js.braintreegateway.com/web/dropin/1.33.0/js/dropin.js" javascript: document.querySelector("#braintree").innerHTML = ""; @@ -44,7 +44,9 @@ javascript: braintree.dropin.create({ authorization: #{{token.to_json}}, container: "#braintree", + card: { vault: { vaultCard: false } }, vaultManager: true, + threeDSecure: true, translations: { payWithCard: "Add a Card", payingWith: "Default payment source", @@ -56,14 +58,17 @@ javascript: document.querySelector("form").addEventListener("submit", function(e) { e.preventDefault(); instance._mainView.hideSheetError(); - instance._mainView.showLoadingIndicator(); - instance.requestPaymentMethod(function(err, payload) { + instance.requestPaymentMethod({ + threeDSecure: { + amount: "0.0", + requireChallenge: true + } + }, function(err, payload) { if(err) { console.log(err); - instance._mainView.hideLoadingIndicator(); - instance._mainView.showSheetError(); } else { + instance._mainView.showLoadingIndicator(); e.target.braintree_nonce.value = payload.nonce; fetch("", { "method": "POST", @@ -76,8 +81,16 @@ javascript: } }).catch(function(err) { console.log(err); - instance._mainView.hideLoadingIndicator(); - instance._mainView.showSheetError(); + err.text().then(function(msg) { + instance._mainView.hideLoadingIndicator(); + instance.clearSelectedPaymentMethod(); + instance._mainView.showSheetError(msg); + }); + }).catch(function(err) { + console.log(err); + instance._mainView.hideLoadingIndicator(); + instance.clearSelectedPaymentMethod(); + instance._mainView.showSheetError(); }); } });