From 0db8ab75eddb00b0b3ba9760c4d71ae5ac36dbe6 Mon Sep 17 00:00:00 2001
From: Matthew Wire <mjw@mjwconsult.co.uk>
Date: Wed, 2 Nov 2022 16:12:48 +0000
Subject: [PATCH] Allow to configure a minimum amount that Stripe can process.
 Anything below this will fail with 'Bad Request'. This helps reduce card
 testing in some circumstances

---
 CRM/Stripe/PaymentIntent.php                  |  1 -
 .../StripePaymentintent/ProcessPublic.php     | 33 +++++++++++--------
 settings/stripe.setting.php                   | 19 +++++++++++
 3 files changed, 39 insertions(+), 14 deletions(-)

diff --git a/CRM/Stripe/PaymentIntent.php b/CRM/Stripe/PaymentIntent.php
index 641a1842..54c87f1c 100644
--- a/CRM/Stripe/PaymentIntent.php
+++ b/CRM/Stripe/PaymentIntent.php
@@ -341,7 +341,6 @@ class CRM_Stripe_PaymentIntent {
    *
    * @return object
    * @throws \CRM_Core_Exception
-   * @throws \CiviCRM_API3_Exception
    * @throws \Stripe\Exception\ApiErrorException
    */
   public function processIntent(array $params) {
diff --git a/Civi/Api4/Action/StripePaymentintent/ProcessPublic.php b/Civi/Api4/Action/StripePaymentintent/ProcessPublic.php
index dec6afc8..dee3b13c 100644
--- a/Civi/Api4/Action/StripePaymentintent/ProcessPublic.php
+++ b/Civi/Api4/Action/StripePaymentintent/ProcessPublic.php
@@ -12,6 +12,10 @@
 
 namespace Civi\Api4\Action\StripePaymentintent;
 
+use Brick\Math\RoundingMode;
+use Brick\Money\Context\DefaultContext;
+use Brick\Money\Money;
+
 /**
  * Process a Stripe Intent with public/anonymous permissions
  *
@@ -81,37 +85,40 @@ class ProcessPublic extends \Civi\Api4\Generic\AbstractAction {
    */
   protected $csrfToken = '';
 
-  /**
-   * @param \Civi\Api4\Generic\Result $result
-   *
-   * @return array
-   * @throws \CRM_Core_Exception
-   * @throws \CiviCRM_API3_Exception
-   */
   /**
    * @param \Civi\Api4\Generic\Result $result
    *
    * @return void
-   * @throws \API_Exception
    * @throws \CRM_Core_Exception
-   * @throws \CiviCRM_API3_Exception
    * @throws \Stripe\Exception\ApiErrorException
    */
   public function _run(\Civi\Api4\Generic\Result $result) {
     if (class_exists('\Civi\Firewall\Firewall')) {
       $firewall = new \Civi\Firewall\Firewall();
       if (!$firewall->checkIsCSRFTokenValid(\CRM_Utils_Type::validate($this->csrfToken, 'String'))) {
-        throw new \API_Exception($firewall->getReasonDescription());
+        throw new \CRM_Core_Exception($firewall->getReasonDescription());
       }
     }
 
     if (empty($this->amount) && !$this->setup) {
       \Civi::log('stripe')->error(__CLASS__ . 'missing amount and not capture or setup');
-      throw new \API_Exception('Bad request');
+      throw new \CRM_Core_Exception('Bad request');
     }
+
+    // If we configured a minimum allowed amount for processing check it here
+    $minAmount = \Civi::settings()->get('stripe_minamount');
+    if (!empty($minAmount)) {
+      $moneyAmount = Money::of($this->amount, $this->currency, new DefaultContext(), RoundingMode::CEILING);
+      $moneyMinAmount = Money::of($minAmount, $this->currency, new DefaultContext(), RoundingMode::CEILING);
+      if ($moneyAmount->isLessThan($moneyMinAmount)) {
+        \Civi::log('stripe')->error(__CLASS__ . 'amount: ' . $this->amount . ' is less than min_amount: ' . $minAmount);
+        throw new \CRM_Core_Exception('Bad request');
+      }
+    }
+
     if (empty($this->paymentProcessorID)) {
       \Civi::log('stripe')->error(__CLASS__ . ' missing paymentProcessorID');
-      throw new \API_Exception('Bad request');
+      throw new \CRM_Core_Exception('Bad request');
     }
 
     $intentProcessor = new \CRM_Stripe_PaymentIntent();
@@ -135,7 +142,7 @@ class ProcessPublic extends \Civi\Api4\Generic\AbstractAction {
       $result->exchangeArray($processIntentResult->data);
     }
     else {
-      throw new \API_Exception($processIntentResult->message);
+      throw new \CRM_Core_Exception($processIntentResult->message);
     }
   }
 
diff --git a/settings/stripe.setting.php b/settings/stripe.setting.php
index a677cfa7..ca3a97f3 100644
--- a/settings/stripe.setting.php
+++ b/settings/stripe.setting.php
@@ -222,4 +222,23 @@ Do NOT enable unless you\'ve enabled this feature on your Stripe account - see <
       ]
     ],
   ],
+  'stripe_minamount' => [
+    'name' => 'stripe_minamount',
+    'type' => 'Integer',
+    'html_type' => 'text',
+    'default' => 0,
+    'is_domain' => 1,
+    'is_contact' => 0,
+    'title' => E::ts('Minimum amount that Stripe is allowed to process.'),
+    'description' => E::ts('Default 0. This can help reduce the impact of card testing attacks as they tend to use low amounts eg. $1. If you know that your will never take payments under eg. $10 you can set that here and the request will fail for anything below that amount.'),
+    'html_attributes' => [
+      'size' => 10,
+      'maxlength' => 3,
+    ],
+    'settings_pages' => [
+      'stripe' => [
+        'weight' => 120,
+      ]
+    ],
+  ],
 ];
-- 
GitLab