~singpolyma/jmp-pay

e1cf684fe34b386a6c358f9163e0a2e45a796757 — Stephen Paul Weber 1 year, 6 months ago 61c7c01
Allow fully 3DS'd transaction from the web
3 files changed, 87 insertions(+), 14 deletions(-)

M config.ru
M lib/three_d_secure_repo.rb
M views/credit_cards.slim
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) {