M config.ru => config.ru +34 -3
@@ 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
A lib/three_d_secure_repo.rb => lib/three_d_secure_repo.rb +46 -0
@@ 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
M views/credit_cards.slim => views/credit_cards.slim +20 -7
@@ 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();
});
}
});