Detailed changes
@@ -23,6 +23,7 @@ require_relative "lib/customer"
require_relative "lib/three_d_secure_repo"
require_relative "lib/electrum"
require_relative "lib/transaction"
+require_relative "lib/credit_card_customer_gateway"
require "sentry-ruby"
Sentry.init do |config|
@@ -119,86 +120,6 @@ class CreditCardGateway
end
end
-class CreditCardCustomerGateway
- def initialize(jid, customer_id, antifraud)
- @jid = jid
- @customer_id = customer_id
- @gateway = CreditCardGateway.new(
- customer_plan&.dig(:currency),
- antifraud
- )
- end
-
- def merchant_account
- @gateway.merchant_account
- end
-
- def check_customer_id(cid)
- return cid unless ENV["RACK_ENV"] == "production"
-
- raise "customer_id does not match" unless @customer_id == cid
-
- cid
- end
-
- def customer_id
- customer_id = Customer.new(nil, @jid).customer_id
- return customer_id if check_customer_id(customer_id)
-
- result = @gateway.customer.create
- raise "Braintree customer create failed" unless result.success?
-
- @customer_id = result.customer.id
- Customer.new(@customer_id, @jid).save!
- @customer_id
- end
-
- def customer_plan
- name = DB.exec_params(<<~SQL, [@customer_id]).first&.[]("plan_name")
- SELECT plan_name FROM customer_plans WHERE customer_id=$1 LIMIT 1
- SQL
- PLANS.find { |plan| plan[:name].to_s == name }
- end
-
- def client_token
- @gateway.client_token(customer_id: customer_id)
- end
-
- def payment_methods?
- !@gateway.customer.find(customer_id).payment_methods.empty?
- end
-
- def antifraud
- return if REDIS.exists?("jmp_antifraud_bypass-#{customer_id}")
-
- @gateway.antifraud
- end
-
- def with_antifraud(&blk)
- @gateway.with_antifraud(&blk)
- end
-
- def sale(nonce, amount)
- @gateway.sale(nonce, amount, customer_id: customer_id)
- end
-
- def default_method(nonce)
- with_antifraud do
- @gateway.payment_method.create(
- customer_id: customer_id, payment_method_nonce: nonce,
- options: {
- verify_card: true, make_default: true,
- verification_merchant_account_id: merchant_account.to_s
- }
- )
- end
- end
-
- def remove_method(token)
- @gateway.payment_method.delete(token)
- end
-end
-
class UnknownTransactions
def self.from(currency, customer_id, address, tx_hashes)
self.for(
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require_relative "credit_card_gateway"
+
+class CreditCardCustomerGateway
+ def initialize(jid, customer_id, antifraud, currency: nil)
+ @jid = jid
+ @customer_id = customer_id
+ @gateway = CreditCardGateway.new(
+ currency || customer_plan&.dig(:currency),
+ antifraud
+ )
+ end
+
+ def merchant_account
+ @gateway.merchant_account
+ end
+
+ def check_customer_id(cid)
+ return cid unless ENV["RACK_ENV"] == "production"
+
+ raise "customer_id does not match" unless @customer_id == cid
+
+ cid
+ end
+
+ def customer_id
+ customer_id = Customer.new(nil, @jid).customer_id
+ return customer_id if check_customer_id(customer_id)
+
+ result = @gateway.customer.create
+ raise "Braintree customer create failed" unless result.success?
+
+ @customer_id = result.customer.id
+ Customer.new(@customer_id, @jid).save!
+ @customer_id
+ end
+
+ def customer_plan
+ name = DB.exec_params(<<~SQL, [@customer_id]).first&.[]("plan_name")
+ SELECT plan_name FROM customer_plans WHERE customer_id=$1 LIMIT 1
+ SQL
+ PLANS.find { |plan| plan[:name].to_s == name }
+ end
+
+ def client_token
+ @gateway.client_token(customer_id: customer_id)
+ end
+
+ def payment_methods?
+ !@gateway.customer.find(customer_id).payment_methods.empty?
+ end
+
+ def antifraud
+ return if REDIS.exists?("jmp_antifraud_bypass-#{customer_id}")
+
+ @gateway.antifraud
+ end
+
+ def with_antifraud(&blk)
+ @gateway.with_antifraud(&blk)
+ end
+
+ def sale(nonce, amount)
+ customer = Customer.new(@customer_id, @jid)
+ raise "Please contact JMP support." if customer.trust_level == "Tombed"
+
+ @gateway.sale(nonce, amount, customer_id: customer_id)
+ end
+
+ def default_method(nonce)
+ with_antifraud do
+ @gateway.payment_method.create(
+ customer_id: customer_id, payment_method_nonce: nonce,
+ options: {
+ verify_card: true, make_default: true,
+ verification_merchant_account_id: merchant_account.to_s
+ }
+ )
+ end
+ end
+
+ def remove_method(token)
+ @gateway.payment_method.delete(token)
+ end
+end
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require "braintree"
+require "customer"
+
+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(currency, antifraud)
+ @currency = currency
+ @antifraud = antifraud
+
+ @gateway = Braintree::Gateway.new(
+ environment: BRAINTREE_CONFIG[:environment].to_s,
+ merchant_id: BRAINTREE_CONFIG[:merchant_id].to_s,
+ public_key: BRAINTREE_CONFIG[:public_key].to_s,
+ private_key: BRAINTREE_CONFIG[:private_key].to_s
+ )
+ end
+
+ def merchant_account
+ BRAINTREE_CONFIG[:merchant_accounts][@currency]
+ end
+
+ def client_token(**kwargs)
+ kwargs[:merchant_account_id] = merchant_account.to_s if merchant_account
+ @gateway.client_token.generate(**kwargs)
+ end
+
+ def antifraud
+ REDIS.mget(@antifraud.map { |k| "jmp_antifraud-#{k}" }).find do |anti|
+ anti.to_i > 2
+ end &&
+ Braintree::ErrorResult.new(
+ @gateway, errors: {}, message: "Please contact support"
+ )
+ end
+
+ def with_antifraud
+ result = antifraud || yield
+ return result if result.success?
+
+ @antifraud.each do |k|
+ REDIS.incr("jmp_antifraud-#{k}")
+ REDIS.expire("jmp_antifraud-#{k}", 60 * 60 * 24)
+ end
+
+ raise ErrorResult.for(result)
+ end
+
+ def sale(nonce, amount, **kwargs)
+ with_antifraud do
+ @gateway.transaction.sale(
+ payment_method_nonce: nonce,
+ amount: amount, merchant_account_id: merchant_account.to_s,
+ options: {
+ store_in_vault_on_success: true, submit_for_settlement: true
+ }, **kwargs
+ )
+ end
+ end
+
+ def customer
+ @gateway.customer
+ end
+
+ def payment_method
+ @gateway.payment_method
+ end
+end
@@ -20,8 +20,16 @@ class Customer
raise "Saving new customer,jid to redis failed"
end
+ def trust_level
+ REDIS.get(redis_trust_level)
+ end
+
protected
+ def redis_trust_level
+ "jmp_customer_trust_level-#{@customer_id}"
+ end
+
def redis_key_jid
"jmp_customer_id-#{@jid}"
end
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "credit_card_customer_gateway"
+
+Customer::REDIS = Minitest::Mock.new
+CreditCardCustomerGateway::DB = Minitest::Mock.new
+
+class CreditCardCustomerGatewayTest < Minitest::Test
+ def setup
+ @gateway = CreditCardCustomerGateway.new("test@test.net", "0001", true, currency: "CAD")
+ end
+
+ def test_no_cc_for_tombed
+ Customer::REDIS.expect(:get, "Tombed", ["jmp_customer_trust_level-0001"])
+ CreditCardCustomerGateway::DB.expect(:exec_params, [OpenStruct.new(name: "plan_name")])
+ assert_raises RuntimeError do
+ @gateway.sale("nonce", 1000)
+ end
+ end
+end
@@ -21,6 +21,13 @@ rescue LoadError
nil
end
+BRAINTREE_CONFIG = {
+ environment: "sandbox",
+ merchant_id: "some_merchant_id",
+ public_key: "some_public_key",
+ private_key: "some_private_key"
+}.freeze
+
module Minitest
class Test
def self.property(m, &block)