Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate Charges #207

Open
codyjames opened this issue May 18, 2022 · 14 comments
Open

Duplicate Charges #207

codyjames opened this issue May 18, 2022 · 14 comments
Assignees
Labels

Comments

@codyjames
Copy link

codyjames commented May 18, 2022

Description

We're seeing duplicate charges occasionally. I'm unable to reproduce it. It doesn't have to do with pressing the submit button multiple times (we disable it after the first click). I'm wondering if this plugin is properly passing an idempotency_key.

https://stripe.com/docs/api/idempotent_requests

Screen Shot 2022-05-18 at 1 37 33 PM

@jonleverrier
Copy link

Same scenario @codyjames. I've just posted this #256. Have you seen any further duplicate charges?

@codyjames
Copy link
Author

@jonleverrier Yeah, we get them occasionally and just have to refund the buyer. We had one with four charges recently.

@jonleverrier
Copy link

@codyjames Four charges!!! Were the timestamps for the 4 charges exactly the same? It's bizarre this kind of thing can happen.

I'm thinking about writing a module that will

  • listen in on a payment event
  • check the number of successful stripe payments
  • if more than one successful card transaction, reject the other transactions?

@localcraig
Copy link

Is there any updates on this @lukeholder we've had the same issue.
Customer has been charged 3 times.

@lukeholder
Copy link
Member

Apologies for the delay in response. We are looking into this but haven't been able to reproduce.

Could you all please let us know:

  • the payment form html you are using (custom, paymentFormHtml() function)
  • the timestamps on the transactions. Are they at the same time or a few seconds apart? We might need a DB dump sent to [email protected] referencing this ticket, if you can.
  • the stripe API version you are on.
  • if you are overriding any Mutex yii component?
  • information on what your server hosting environment is. Are you doing any DB read/write splitting etc?
  • what version of commerce and craft are you on?
  • are you hooking into any commerce events?

Please let us know.

@jaydensmith
Copy link

@lukeholder we've just had 2 instances of customers being charged twice.

  • We are using a custom payment form (not paymentFormHtml).
  • Stripe API version: 2020-08-27
  • Not overriding mutex, but using Redis for sessions
  • Hosted on Ubuntu 20.04.3 LTS, MariaDB 10.6.3
  • Craft 4.6.0
  • Commerce 4.4.0
  • We are using a few commerce events, but nothing that mutates the order in a significant way.
    LineItems::EVENT_POPULATE_LINE_ITEM - dynamically sets the line item price
    Order::EVENT_BEFORE_SAVE - sets a single field on the order based on the line item contents
    Order::EVENT_AFTER_ORDER_PAID - processes a Stripe Connect payout

Screenshot 2024-01-22 at 10 47 30 am
Screenshot 2024-01-22 at 10 47 44 am

@jaydensmith
Copy link

This happened to us again today @lukeholder
Pretty embarrassing to explain to customers.

@angrybrad
Copy link
Member

angrybrad commented Mar 7, 2024

@jaydensmith what hosting provider are you using?

Guessing based on this you're managing your own infrastructure?

Hosted on Ubuntu 20.04.3 LTS, MariaDB 10.6.3

@jaydensmith
Copy link

jaydensmith commented Mar 7, 2024

@jaydensmith what hosting provider are you using?

Guessing based on this you're managing your own infrastructure?

Hosted on Ubuntu 20.04.3 LTS, MariaDB 10.6.3

@angrybrad It's managed via Laravel Forge

@angrybrad
Copy link
Member

angrybrad commented Mar 7, 2024

@jaydensmith can you share your config/app.php file? (redacting any sensitive bits of course)

@jaydensmith
Copy link

@angrybrad sure!

<?php
/**
 * Yii Application Config
 *
 * Edit this file at your own risk!
 *
 * The array returned by this file will get merged with
 * vendor/craftcms/cms/src/config/app.php and app.[web|console].php, when
 * Craft's bootstrap script is defining the configuration for the entire
 * application.
 *
 * You can define custom modules and system components, and even override the
 * built-in system components.
 *
 * If you want to modify the application config for *only* web requests or
 * *only* console requests, create an app.web.php or app.console.php file in
 * your config/ folder, alongside this one.
 */

use craft\helpers\App;

return [
    'id' => App::env('APP_ID'),
    'modules' => [
        'site-module' => \modules\sitemodule\SiteModule::class,
        'site-orders' => \modules\siteorders\SiteOrders::class,
        'site-scripts' => \modules\sitescripts\SiteScripts::class,
    ],
    'bootstrap' => [
        'site-module',
        'site-orders',
        'site-scripts',
    ],
    'components' => [
        'queue' => [
            'ttr' => 3600,
        ],
        'addresses' => [
            'class' => \craft\services\Addresses::class,
            'formatter' => new \modules\sitemodule\formatters\Address(
                new \CommerceGuys\Addressing\AddressFormat\AddressFormatRepository(),
                new \CommerceGuys\Addressing\Country\CountryRepository(),
                new \CommerceGuys\Addressing\Subdivision\SubdivisionRepository()
            )
        ],
        'session' => function() {
            // Get the default component config:
            $config = craft\helpers\App::sessionConfig();

            // Replace component class:
            $config['class'] = yii\redis\Session::class;

            // Define additional properties:
            $config['redis'] = [
                'hostname' => App::env('REDIS_HOSTNAME') ?: 'localhost',
                'port' => 6379,
                'password' => App::env('REDIS_PASSWORD') ?: null,
            ];

            // Return the initialized component:
            return Craft::createObject($config);
        }
    ],
];

@angrybrad
Copy link
Member

@jaydensmith thanks for that.

Usually, when we see sporadic behaviors like this, the culprit has been using Redis as a mutex, which, as it turns out, doesn’t make a reliable distributed locking mechanism. https://redis.io/docs/manual/patterns/distributed-locks/

That doesn’t seem to be the case here, though.

I’m not sure what’s going on in your custom modules - it’s possible something in there is causing the behavior.

Guessing there’s no database read/write splitting based on the contents of config/app.php?

@angrybrad
Copy link
Member

@jaydensmith I suppose as a next step, I'd update Craft and Commerce to the latest 4.x release to remove outdated versions as variables.

@codyjames
Copy link
Author

I have a hunch that this is related to failed payment intents. It seems like this happens when a customer has some sort of issue with their payment. Then they resolve the issue and a new payment intent is created and successfully processed, but perhaps some of the old payment intents also get re-processed. Or perhaps the errors happened on the Craft side, and blocked the payment intent from being processed, and then once the order is able to go through without errors, Craft just processes all of the payment intents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants