Block users who get too many card declines

Stephen Paul Weber created

If a customer has > 2 card declines in 24 hours or an ip has > 4, then treat all
attempts as declines without looking as an anti-fraud measure.

Change summary

config.ru | 34 +++++++++++++++++++++++++++-------
1 file changed, 27 insertions(+), 7 deletions(-)

Detailed changes

config.ru 🔗

@@ -65,6 +65,7 @@ class Plan
 			"INSERT INTO plan_log VALUES ($1, $2, $3, $4)",
 			[customer_id, @plan[:name], Time.now, Date.today >> months]
 		)
+		true
 	end
 end
 
@@ -125,17 +126,35 @@ class CreditCardGateway
 		)
 	end
 
-	def buy_plan(plan_name, months, nonce)
+	def decline_guard(ip)
+		customer_declines, ip_declines = REDIS.mget(
+			"jmp_pay_decline-#{@customer_id}",
+			"jmp_pay_decline-#{ip}"
+		)
+		customer_declines.to_i < 2 && ip_declines.to_i < 4
+	end
+
+	def sale(ip:, **kwargs)
+		return false unless decline_guard(ip)
+		result = @gateway.transaction.sale(**kwargs)
+		return true if result.success?
+
+		REDIS.incr("jmp_pay_decline-#{@customer_id}")
+		REDIS.expire("jmp_pay_decline-#{@customer_id}", 60 * 60 * 24)
+		REDIS.incr("jmp_pay_decline-#{ip}")
+		REDIS.expire("jmp_pay_decline-#{ip}", 60 * 60 * 24)
+		false
+	end
+
+	def buy_plan(plan_name, months, nonce, ip)
 		plan = Plan.for(plan_name)
-		result = @gateway.transaction.sale(
+		sale(
+			ip: ip,
 			amount: plan.price(months),
 			payment_method_nonce: nonce,
 			merchant_account_id: plan.merchant_account,
 			options: {submit_for_settlement: true}
-		)
-		return false unless result.success?
-		plan.activate(@customer_id, months)
-		true
+		) && plan.activate(@customer_id, months)
 	end
 
 protected
@@ -251,7 +270,8 @@ class JmpPay < Roda
 						Plan.active?(gateway.customer_id) || gateway.buy_plan(
 							request.params["plan_name"],
 							5,
-							request.params["braintree_nonce"]
+							request.params["braintree_nonce"],
+							request.ip
 						)
 					end
 					if result