woocommerce / woocommerce-gateway-stripe

The official Stripe Payment Gateway for WooCommerce
https://wordpress.org/plugins/woocommerce-gateway-stripe/
231 stars 202 forks source link

Level 3 data - Orders initially fail and then succeed #2307

Open AashikP opened 2 years ago

AashikP commented 2 years ago

Describe the bug

The order initially fails and then completes. The level 3 data fails for every transaction even though there should be a transient set to disable this feature. The transient isn't set.

To Reproduce

We haven't been able to replicate this behavior even with sending incorrect level 3 data (hardcoding the extension files), but on 4675737-zen :

  1. Create an order on the front end.

  2. Notice that the order fails initially

  3. And then succeeds

    https://d.pr/i/4BMwMH+

  4. Example error when the order fails:

2022-01-10T01:38:38+00:00 DEBUG 
====Stripe Version: 6.0.0====
====Start Log====
Level3 data sum incorrect: 
Invalid level3: pricing must be consistent. Sum(unit_cost * quantity + tax_amount - discount_amount) + shipping_amount != total charged. In this case, 60000 != 40000.
Order line items[....]

I've removed the notes from Follow-Ups so we can easily view the other system notes

Expected behavior

Either set the Transient correctly or make sure correct Level 3 data is shared with Stripe.

Environment (please complete the following information):

Additional context

Moved to GitHub after this slack discussion: p1640516356418600-slack-C7U3Y3VMY

dechov commented 2 years ago

The transient is set if level3 data isn't supported, not if the data itself is invalid, which appears to be the case here, due to negative amounts or non-integer quantities. With the understanding that WC doesn't specifically not support cases like those, we should probably aim for better handling than a failed request on each payment.

dreamtooloud commented 1 year ago

Another report of this error in 5769773-zen ; there are other surrounding issues (multiple charges created for the customers) that we are investigating, but it's unclear at this time if those are related to this bug.

maxrice commented 6 months ago

This happens consistently on our site for subscription renewals that were imported. An initial request is made to POST /v1/payment_intents that fails with the error: parameter_unknown - level3, and then another request is made without the level3 parameter that succeeds. CleanShot 2024-03-14 at 13 29 01@2x

james-allan commented 6 months ago

This happens consistently on our site for subscription renewals that were imported. An initial request is made to POST /v1/payment_intents that fails with the error: parameter_unknown - level3, and then another request is made without the level3 parameter that succeeds.

Looking at the relevant code, this looks to be the expected behaviour. This section of code adds level 3 data to the request, sends the request, if the response indicates that level 3 data is not allowed, it sets a transient that is suppose to prevent reattempting to send level 3 data for 3 months.

I couldn't find anything about the level3 parameter in Stripe's API docs but it's mentioned here https://stripe.com/au/guides/guide-to-managing-network-costs#level-ii-and-level-iii-data

Given we've taken this approach of attempting to send level3 data and then if a failure occurs, send it without it, it makes me think there mustn't be any account flags we can use to make this determination. Having said that this approach was added 4 years ago so it might be worth rechecking this.

james-allan commented 6 months ago

This happens consistently on our site for subscription renewals that were imported.

In the original issue description @AashikP mentions something similar and that the transient isn't being set too. Can you confirm it isn't being set? It sounds like if this is happening consistently, then it's occurring far more frequently than every 3 months.

If it isn't being set, can you let us know if you're using a object cache? I recall seeing an issue in the past for sites which use certain object caches. When setting a transient with a long TTL, as we are doing here, the transient expiration parameter is interpreted as a timestamp rather than a length of time. Consequently, the transient expires immediately because 7776000 seconds (equivalent to 3 months) interpreted as a timestamp (1 April 1970) falls well into the past."

maxrice commented 6 months ago

@james-allan yeah as best I can tell (from transients manager, although it indicates "you're using a persistent object cache so this list may be incomplete", but also from searching wp_options for wc_stripe_level3_not_allowed), the transient is not set, so perhaps that explains the constant attempts with every imported subscription renewal.

that said, this doesn't seem like an appropriate use case for a transient -- as I understand them, code should assume they can be removed at any time:

Furthermore, it is possible for the transient to not be available before the expiration time. Much like what is done with caching, your code should have a fall back method to re-generate the data if the transient is not available. source

I'm not sure how the code would know when the transient was removed, and thus how long it's been since level3 transactions were attempted. An alternative approach could use an option like wc_stripe_check_level_3 with the value set to when level3 request should be attempted again, along with maybe a daily cron/action scheduler event to either remove or reset that value to +3 months in the future.

nirkons commented 4 months ago

Chiming in, also having this issue unfortunately