M config.ru => config.ru +73 -9
@@ 1,6 1,7 @@
# frozen_string_literal: true
require "braintree"
+require "bigdecimal/util"
require "date"
require "delegate"
require "dhall"
@@ 19,6 20,7 @@ require_relative "lib/auto_top_up_repo"
require_relative "lib/customer"
require_relative "lib/three_d_secure_repo"
require_relative "lib/electrum"
+require_relative "lib/transaction"
require "sentry-ruby"
Sentry.init do |config|
@@ 129,6 131,17 @@ class CreditCardGateway
raise ErrorResult.for(result)
end
+ def sale(nonce, amount)
+ with_antifraud do
+ @gateway.transaction.sale(
+ customer_id: customer_id, payment_method_nonce: nonce,
+ amount: amount, merchant_account_id: merchant_account.to_s,
+ options: {
+ store_in_vault_on_success: true, submit_for_settlement: true
+ }
+ )
+ end
+ end
def default_method(nonce)
with_antifraud do
@@ 192,6 205,61 @@ class UnknownTransactions
end
end
+class CardVault
+ def self.for(gateway, nonce, amount=nil)
+ if amount&.positive?
+ CardDeposit.new(gateway, nonce, amount)
+ else
+ new(gateway, nonce)
+ end
+ end
+
+ def initialize(gateway, nonce)
+ @gateway = gateway
+ @nonce = nonce
+ end
+
+ def call(auto_top_up_amount)
+ result = vault!
+ ThreeDSecureRepo.new.put_from_result(result)
+ AutoTopUpRepo.new.put(
+ @gateway.customer_id,
+ auto_top_up_amount
+ )
+ result
+ end
+
+ def vault!
+ @gateway.default_method(@nonce)
+ end
+
+ class CardDeposit < self
+ def initialize(gateway, nonce, amount)
+ super(gateway, nonce)
+ @amount = amount
+
+ return unless @amount < 15 || @amount > 35
+
+ raise CreditCardGateway::ErrorResult, "amount too low or too high"
+ end
+
+ def call(*)
+ result = super
+ Transaction.new(
+ @gateway.customer_id,
+ result.transaction.id,
+ @amount,
+ "Credit card payment"
+ ).save
+ result
+ end
+
+ def vault!
+ @gateway.sale(@nonce, @amount)
+ end
+ end
+end
+
class JmpPay < Roda
SENTRY_DSN = ENV["SENTRY_DSN"] && URI(ENV["SENTRY_DSN"])
plugin :render, engine: "slim"
@@ 268,15 336,11 @@ class JmpPay < Roda
end
r.post do
- 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
- )
+ CardVault
+ .for(
+ gateway, params["braintree_nonce"],
+ params["amount"].to_d
+ ).call(params["auto_top_up_amount"].to_i)
"OK"
rescue ThreeDSecureRepo::Failed
gateway.remove_method($!.message)
M lib/three_d_secure_repo.rb => lib/three_d_secure_repo.rb +8 -3
@@ 3,10 3,15 @@
class ThreeDSecureRepo
class Failed < StandardError; end
- def put_from_payment_method(_customer_id, method)
- return unless method.verification # Already vaulted
+ def put_from_result(result)
+ three_d = if result.payment_method
+ return unless result.payment_method.verification # Already vaulted
+
+ result.payment_method.verification.three_d_secure_info
+ else
+ result.transaction.three_d_secure_info
+ end
- three_d = method.verification.three_d_secure_info
if !three_d ||
(three_d.liability_shift_possible && !three_d.liability_shifted)
raise Failed, method.token
M views/credit_cards.slim => views/credit_cards.slim +6 -2
@@ 24,11 24,15 @@ form method="post" action=""
#braintree
| Unfortunately, our credit card processor requires JavaScript.
+ label#amount style="#{'display:none;' unless params['amount']}"
+ div Amount of initial deposit (minimum $15)
+ input type="number" name="amount" min="15" value="#{params.fetch('amount', '')}"
+
fieldset
legend Auto top-up when account balance is low?
label
| When balance drops below $5, add $
- input type="number" name="auto_top_up_amount" min="15" value=auto_top_up
+ input type="number" name="auto_top_up_amount" min="15" max="35" value=auto_top_up
small Leave blank for no auto top-up.
input type="hidden" name="customer_id" value=customer_id
@@ 93,7 97,7 @@ javascript:
instance.requestPaymentMethod({
threeDSecure: {
- amount: "0.0",
+ amount: document.querySelector("input[name=amount]").value || "0.0",
requireChallenge: true
}
}, function(err, payload) {