Problem/Motivation
From September 2019, most ecommerce payments will have to undergo Strong Customer Authentication (SCA). A new authentication protocol is being introduced by the Card Schemes (Mastercard, Visa, etc.) called 3D Secure 2 (3DS v2).
Proposed resolution
Support the 3DS v2 protocol by introducing options that will make it easy for to comply with the SCA requirement.
- Introduction: https://stripe.com/guides/3d-secure-2
- Testing information: https://stripe.com/docs/testing#three-ds-cards
- Migration guide: https://stripe.com/docs/payments/payment-intents/migration
- Setup intents (recurring): https://stripe.com/docs/api/setup_intents/confirm
Remaining tasks
Testing:
Capture flowAuth + CapturePayment refundVoid paymentAuth + AnonAbanon checkout and come back with the same order- Test integration with Commerce Recurring
Tasks
Require stripe/stripe-php:~6.9Ensure JavaScript error handling(#48,#53)Declining authorization does nto display error messagestripeErrorHandler(result);, element missing- Write FunctionalJavascript tests?
User interface changes
- stripe_review checkout pane added and is required
API changes
Data model changes
- Stripe payment method type added, to store customer ID for anonymous users
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #128 | Screen Shot 2019-09-02 at 3.03.35 pm.png | 301.03 KB | blacklabel_tom |
| #128 | Screen Shot 2019-09-02 at 3.03.28 pm.png | 59.44 KB | blacklabel_tom |
| #128 | Screen Shot 2019-09-02 at 2.59.33 pm.png | 39.36 KB | blacklabel_tom |
| #124 | 3039032-124.patch | 76.27 KB | mglaman |
Comments
Comment #2
bojanz commentedWe are committed to fixing this in the near future.
Comment #3
bojanz commentedForgot to mention that the promise applies to the D8 version.
D7 already has its own issue: #2717383: Add support for 3D Secure.
Comment #4
czigor commentedUploading a patch for backup purposes.
Creates a checkout pane and adds it to the review checkout page. The 3DS2 option needs to be enabled on the payment gateway form.
It already kind of works, but it's not fully tested. I know it breaks anonymous checkout.
Terminal payments are not handled.
Comment #5
czigor commentedThe patch fixes anonymous checkout. For this a new payment method type has been added with a 'Stripe customer ID' field.
I tested the patch both with 3DS enabled and disabled, both with logged in and anonymous users and with test cards that do and do not require 3DS authentication. https://stripe.com/docs/testing#three-ds-cards Everything seems to work fine.
Added update and post_update hooks for:
- creating the new 'Stripe customer ID' field
- updating stripe payment gateways to use the new stripe payment method type.
- updating existing stripe payment methods to be of stripe bundle
Comment #6
czigor commentedComment #9
czigor commentedFixed all the coding standards issues, except for the namespacing ones.
Comment #11
czigor commentedFails only because of the full namespacing of Stripe classes.
Comment #12
handkerchiefGreat work so far. Thx you so much for your work. This feature is very important and I look forward to using it soon.
Comment #13
czigor commentedAdding 3d2 setting to config schema.
Also adding some tests. They mostly fail, sometimes pass.
Comment #15
yong.gi commentedThank you for the great work. The commerce_stripe-3039032-3ds-13.patch works for me with drupal/commerce_stripe:1.x-dev. in my composer based drupal installation.
However, I can't complete applying the db update. When run "drush updatedb", I got following output:
The following updates are pending:
commerce_stripe module :
Update Stripe payment methods to be of stripe bundle.
Do you wish to run all pending updates? (y/n): y
Post updating commerce_stripe [ok]
Failed: Drupal\Core\Database\InvalidQueryException: Query condition 'payment_gateway = ()' cannot be empty. in [error]
Drupal\Core\Database\Query\Condition->condition() (line 103 of core/lib/Drupal/Core/Database/Query/Condition.php).
Cache rebuild complete. [ok]
Finished performing updates. [ok]
The error is caused by the code in commerce_stripe/commerce_stripe.post_update.php.
/**
* Update Stripe payment methods to be of stripe bundle.
*/
function commerce_stripe_post_update_payment_methods() {
$gateways = PaymentGateway::loadMultiple(['plugin' => 'stripe']);
$gateway_names = array_keys($gateways);
db_update('commerce_payment_method')
->fields(['type' => 'stripe'])
->condition('payment_gateway', $gateway_names)
->execute();
}
The empty $gateway_names causes the db_update fail. Would it be better to add a check to $gateway_names?
/**
* Update Stripe payment methods to be of stripe bundle.
*/
function commerce_stripe_post_update_payment_methods() {
$gateways = PaymentGateway::loadMultiple(['plugin' => 'stripe']);
$gateway_names = array_keys($gateways);
if (!empty($gateway_names)) {
db_update('commerce_payment_method')
->fields(['type' => 'stripe'])
->condition('payment_gateway', $gateway_names)
->execute();
}
}
Comment #16
czigor commentedThanks for the testing, yong.gi. Added the !empty() check to the post_update hook.
Tests still fail.
Comment #18
czigor commentedRemoving the #description of the 3DS setting since it is not true.
Comment #20
czigor commentedAs per https://stripe.com/docs/payments/payment-intents/quickstart
Comment #21
czigor commented1. Removed the test from the patch. #howtofixtestfailures
2. We were creating 2 payment intents during a checkout while we only need 1. Fixed.
3. Failed 3DS validation broke the payment intent. Fixed.
TODOs:
1. Further testing of both 3DS and no-3DS checkouts.
2. Pass billing info to stripe.handleCardPayment if possible (see previous comment).
Comment #23
mglamanQuick review.
Won't this fail unless the cached payment method type definitions are cleared? because the target bundle will not exist
Why do we need this on all pages?
This needs loadByProperties. This update hook will not work.
db_update is deprecated.
We cannot use `let` or `const` without transpiling.
This should be an HTTP access exception. Or does Drupal core convert this to one?
Does not match description
We should first check `get('payment_gateway')->isEmpty()` and then assert `->entity` is `instanceof` the gateway interface.
Then we don't need to check the plugin ID either.
This isn't right.
Comment #24
czigor commented1. What would this cause? I tested the (post)update hooks and they work well. The field configuration does get created, even if I use a non-existent bundle name, like 'stripe2'.
2. Added a comment to explain.
3-5, 7-9: Fixed
6. What's an HTTP access exception?
Comment #25
czigor commentedAs per #20 we're also sending shipping information now. I don't think it matters much but I tested this with latest devs from everything and the patch in #3054351: Add support for the Commerce 2.14 address book.
Comment #27
czigor commentedComment #28
Rafal LukawieckiI do not use commerce_stripe as my company site is Ubercart and so uc_stripe based. However, I would like to offer a heads-up to the maintainers of this issue and of the module that I have been contacted by Chris Traganos (@trag-stripe) from Stripe to discuss the help that Stripe may be able to offer in order to get Drupal Stripe plug-ins SCA compliant. If you are interested in my discussion and the suggestions that Stripe made, please see comment #10 of #3040854: Implement Stripe PaymentIntents for SCA (Strong Customer Authentication).
Ideally, commerce_stripe would end up being listed by Stripe as one of their SCA compliant integrations. @AndraeRay (maintainer of uc_stripe) and I will be working with Stripe to get uc_stripe to that status. Further, while I am unsure if Ubercart can be listed as a Stripe "Partner" (see #3061293: Identify uc_stripe as a plug-in to Stripe) perhaps that is something that Commerce Guys could be interested in—unsure, as I do not know the nature of the Drupal Commerce codebase organisational maintainership.
I hope this is of some help to you. I am only sharing this in the Drupal spirit of mutual assistance and collaboration. Fingers crossed for your SCA compliance. I will post this comment on both SCA issues that commerce_stripe is tracking at the moment.
Comment #29
czigor commentedThanks Rafal, this drew my attention to some SCA issues we have in the current patch.
Switched to using PaymentMethods instead of Cards and Sources and to using stripe.createPaymentMethod() from stripe.createToken().
Did some manual testing, these work:
1. Checkout with authenticated user having payment methods using an existing payment method.
2. Checkout with authenticated user using a new payment method.
3. Checkout with authenticated user having no payment methods using an existing payment method.
4. Checkout with anonymous user.
Comment #31
czigor commentedComment #32
czigor commentedComment #33
audriusb commented\Stripe\PaymentIntent::create,\Stripe\PaymentIntent::update,\Stripe\PaymentIntent::retrievecan throw errors, therefore surround them with try/catch. You have someretrievesurrounded but not all of them.I can't see anywhere in code where you
\Stripe\PaymentIntent::cancel. If payment intent is not used, cancel it, don't leave it hanging in Stripe dashboard.Also add idempotency_key to
\Stripe\PaymentIntent::createComment #34
czigor commentedThanks audriusb!
Added the try-catch blocks and the idempotency keys. As per the payment intent cancellation, I'm not sure where I should do that unless we create a cronjob for it to clean them up.
Comment #36
audriusb commentedYeah, cronjob sounds good. Couple more points from our experience:
1. Add
descriptionto\Stripe\PaymentIntent::create. It helps users to see what payment is for when viewing transactions list in the Stripe dashboard. Without description, all they see is payment intent ids.2. I see you have support for Stripe webhooks. This one has it's own unique twists:
2.1 If Stripe account has any connected Stripe accounts, then all connected account's webhooks are fired against main account as well, therefore you get unrelated requests. To mitigate that, add some unique data to
\Stripe\PaymentIntent::createmetadata and check for it in the webhook endpoint. Check for livemode=true, too.2.2 Couple times Stripe stopped sending webhooks for 10 minutes straight but was accepting payments. Users ended up paying but not receiving goods. To mitigate this, find where Stripe returns success in javascript and fire ajax call to server to double check the payment status and complete the order manually. This way you have double check for completing orders. Make sure 2nd try to complete order doesnt throw error. Usually webhook still completes first and manual attempt is 2nd.
Comment #37
czigor commented1. Added the order_id to the description.
2. Not sure what you mean by webhooks. I don't think we use them. We retrieve the payment intent by intent id (which is stored on the order) when capturing the payment, so there's no way we would try to handle payment intents we should not.
Comment #39
czigor commentedFor handling unused payment intents in a cronjob let's open a different issue. I'm not sure if it should not go to a custom module instead and this patch is big enough already.
Comment #40
mglamanReviewing today
Comment #41
mglamanHere is an initial review of the code, I need to go through the comments once again
Do we need to clear the payment method type definitions and bundle info prior to running this so that the field has a valid destination to install to?
We can perform an entity query against the plugin ID.
We could use an entity query with conditions to load the IDs.
We need to use Drupal.url to ensure proper prefix of the path.
s/markup/plain_text ?
Should visibility check if 3DS2 is enabled?
We should open up a follow up issue and add link here
Did the library change causing a break here? should we reflect it in the composer.json?
Comment #42
mglamanI received an error immediately after going back and having to re-enter card information
Idempotency keys are meant to reply a request if there's a 500 response. PHP isn't concurrent and we have no ways to retry a request (easily) before the request ends.
Per https://stripe.com/docs/api/idempotent_requests, lets use UUIDs for the keys.
Another easy way to reproduce w/ last patch: 🤦♂️forget to select shipping and have the form re-render on validation failures.
---
JavaScript error due to incorrect detach
Comment #43
mglamanThis patch got me to testability. It needs works. But it uses UUID's for the idempotency key. I also added an ES6 -> JS setup so we can manage the JavaScript according to core's Prettier standards and ensure we support IE11.
Comment #45
mglamanHere's a fix for some JS. Also attaching an interdiff from #37
Comment #47
Marko B commentedComposer.json should be changed to "stripe/stripe-php": "~6.9" otherwise versions below produce
Error: Class 'Stripe\PaymentIntent' not foundcan be seen that this class is first introduced in https://github.com/stripe/stripe-php/tree/v6.9.0/lib
Other then that I tried latest patch on vanilla commerce install, works nice. No problems with sandbox account.
Important note is that regular test cards wont trigger 3DS (like often used 4242424242424242)
There is a special section for 3DS cards that need to be used so that this functionality is triggered.
https://stripe.com/docs/testing#three-ds-cards
Comment #48
manish palawat commentedI received an error after enter wrong details for cards like
Drupal\Core\Database\DatabaseExceptionWrapper : SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'expires' at row 1: INSERT INTO {commerce_payment_method} (type, uuid, payment_gateway, payment_gateway_mode, uid, remote_id, billing_profile__target_id, billing_profile__target_revision_id, reusable, is_default, expires, created, changed) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8, :db_insert_placeholder_9, :db_insert_placeholder_10, :db_insert_placeholder_11, :db_insert_placeholder_12); Array ( [:db_insert_placeholder_0] => stripe [:db_insert_placeholder_1] => b297c08c-9e51-4fef-88dc-5398fd293236 [:db_insert_placeholder_2] => stripe [:db_insert_placeholder_3] => test [:db_insert_placeholder_4] => 234 [:db_insert_placeholder_5] => pm_1EzOe6EHyNH8KK0xpM28PGQH [:db_insert_placeholder_6] => 250 [:db_insert_placeholder_7] => 249 [:db_insert_placeholder_8] => 1 [:db_insert_placeholder_9] => [:db_insert_placeholder_10] => 2308780799 [:db_insert_placeholder_11] => 1563890817 [:db_insert_placeholder_12] => 1563890817 ) dans Drupal\Core\Entity\Sql\SqlContentEntityStorage->doSaveFieldItems() (ligne 978 de httpdocs/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).I think Patch does not handling these error messages . It should be thrown error by javascript when typing.
Also Sometimes got error anytime when click on payment button
Notice : Undefined index: #parents dans Drupal\Core\Form\FormState->getError()Comment #49
Marko B commentedThere is an error with inline validation but it happens on wider range, like if you enter 01 / 74 you will get "Your card's expiration year is invalid." so this needs to match range with data storage.
Comment #50
manish palawat commentedYes , if I enter past and wider range Month/Year it works fine (shows error) but when enter 02/42 should be handle with drupal itself . It should be in try catch function. What should i do in that case ?
Comment #51
Marko B commentedYeah, should be fixed :)
Comment #52
mglamanThis is a different issue than 3DSv2, if you could open that.
Comment #53
darrenwh commentedI'm just having a play with this. I have applied the patch, updated the stripe-php lib and using the latest version of the module 8.x-1.0-rc.
I get this exception on submitting payment details:
This is using this card 4000000000003220 from here https://stripe.com/docs/testing#three-ds-cards
I previously tested a payment without the patch and it worked fine, I'm just running a vanilla build on docksal.
Comment #54
mglamanForgot to unassign.
Comment #55
darrenwh commentedPlease disregard my comment https://www.drupal.org/project/commerce_stripe/issues/3039032#comment-13... looks as if the patch need to be built against the development version.
Is there a timescale for getting this resolved and to have a stable release before September, it would be beneficial to have this ready at least 2 weeks before then in order to fully test it.
I feel that if its not ready there is a potential for clients to lose revenue revenue due to rejected payments, (or will this not be so, will sites continue to take payments reverting back to version 1?)
Thanks
Comment #56
amelio commentedI tested the patch in #45 against a fresh Drupal 8 install. As darrenwh mentioned, I used the dev version of the module:
composer require 'drupal/commerce_stripe:1.x-dev'I enabled commerce_stripe and related required modules, created a Store and a product. I was able to complete payment & checkout using cards that both required or did not require 3D Secure 2.
I didn't have to change the version of stripe-php in composer.json as Marko B said - I left it as
"stripe/stripe-php": "~6.0"and it worked fine. The version it actually installed is 6.40.0.I also tested the patch on a client site. The client's Stripe account is using an old version of the API by default, so I had to force using the latest version. Wherever
\Stripe\Stripe::setApiKeyis called inStripe.php, I added this:\Stripe\Stripe::setApiVersion("2019-05-16");- see https://stripe.com/docs/api/versioning
However, it doesn't work when 'Enable 3D Secure 2.0' is checked. The first problem is that it never brings up the 3D Secure authentication popup, so the card is declined if this is required. I think this is probably a problem with our particular checkout as we have a lot of custom stuff going on.
However, I also get this error if I attempt to checkout when logged out:
InvalidArgumentException: Sorry, we cannot create a payment method without an email. in Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\Stripe->doCreatePaymentMethod() (line 473 of /var/www/docroot/modules/contrib/commerce_stripe/src/Plugin/Commerce/PaymentGateway/Stripe.php).It seems that it's expecting an
emailfield incontact_information, which doesn't exist (we do capture email address but it's in a different checkout pane). Again, this is probably an issue on our side, but does anyone know how I could make sure this field is available?Another question: is the new Stripe Review checkout pane required for 3DS2?
Comment #57
audriusb commented@darrenwh don't quote me on this but I believe 14th September is the hard deadline after which banks will be required to authenticate transactions. So you need to get Stripe SCA running before that.
Stripe held and recorded a conference call about SCA couple months back. I strongly suggest to watch it, at the end there is a QA session that might answer all your questions.
https://www.youtube.com/watch?v=WWJ4VAgqRUM
Comment #58
mglamanI'm going to try and triage the issue summary to state where we are and what needs to be done. There's a lot of comments.
Comment #59
mglamanComment #60
mglamanComment #61
mglamanComment #62
mglamanComment #63
mglamanThe biggest remaining issue is what to do if the user declines authentication? Right now it stays on review and
Comment #64
mglamanFound out why errors do not display! The JavaScript theme method is defined in the form library, which is not loaded.
Comment #65
mglamanThis moves the messaging to its own library. We just need to decide if it should redirect the user back to the order information pane or not.
Comment #67
mglamanInject
Inject
Will this type of exception go uncaught and break the site? Is there a Payment API friendly exception
In Square we combined the remote instrument token with another identifier using a
|Should we be doing that over making a new payment method type and bundle field?
This ends up calling the submit credit card form method twice?
Ah, no. because we have a custom bundle.
Comment #68
jons commentedThanks for update @mglaman! Not sure I can make sensible responses to your questions - but happy to test final result
Comment #69
bojanz commentedNeeds a reroll now that #3073323: Mark stripe/stripe-php as ^6.36 and #3073327: Cannot set id on this object. have landed.
Anyone who needs to test the patch before that can revert the last two commits, then apply the patch.
Comment #70
Marko B commentedPatch re-rolled
Comment #71
amelio commentedHave been doing some more testing with this. The error I was getting above about the missing email wasn't resolved just by enabling the Contact Information pane (which we weren't using before) - I also had to amend
submitCreditCardForm()as it assumes that the form values are in the structure[contact_information][email]. In our case the panes are inside a parent wrapper so this didn't work. Is it possible to retrieve the email in a more generic way - can something like$order->getEmail()be used?Another problem is that the current JS doesn't work with a single-page checkout form. In
commerce_stripe.form.js, the creation of the payment method is triggered by form submit. However, for a single-page checkout we want to trigger it when the user clicks the 'next' button on the Payment section, not when they submit the whole form.stripePaymentMethodHandler()also submits the form after setting the payment method id. I know that a single-page checkout isn't the normal use-case, but is there some way to alter the triggers for the payment method handler?I'm able to get past these errors by altering the module code, and the payment method id is created successfully. However when I then submit the form, payment fails, with these errors in the logs:
It seems like it's trying to reuse the payment method token when it's already been saved against a customer. Has anyone come across this?
Comment #72
amelio commentedAlso, a couple of minor notes:
#stripe_tokenis referenced, don't believe this is needed any more?ThreeDScontroller, which has been removedComment #73
mglaman@amelio:
We're going to focus on the conventional out of the box flow to start, can you help test on a vanilla install? Then we can help support more customized checkout flows.
I'll be working on this today.
Comment #74
mglamanWe can't use Charge objects anymore.
Comment #75
mglamanThis simplifies the patch by not introducing a new payment method type and removes any upgrade path.
Comment #76
mglamanAnother go. This ensures orders only use one payment intent.
Comment #77
mglamanOkay, ready for review.
My concern:
Testing all of the test cards as anonymous + authenticated users. Testing both authentication approvals and failures. Abandoning checkout with re-enter, Abandon and adding items to cart and re-enter.
I think there is a bug I missed, but out of time for the day. We need an `updatePaymentIntent` method which allows updating the price and payment method, etc, on an intent
The above would fail if the order total changed, as the intent is no longer correct.
--
For amelio:
* No longer exceptions on missing email
* Payment intents are created in the \Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\StripeInterface::createPaymentIntent method
* Custom checkout flows will, for now, need to replicate parts of \Drupal\commerce_stripe\Plugin\Commerce\CheckoutPane\StripeReview::buildPaneForm
For 3DSv2 to work we must create a payment intent as a "contract" of the price. Then the user agrees/authenticates in the modal and that is what captures/authorizes the payment. Nothing in the PaymentProcess pane.
Comment #78
Collins405 commentedHey guys, is the plan to finish Drupal 8 and backport to Drupal 7?
I can see there are 2,889 Drupal 7 sites using this module, and only 700 using the Drupal 8 version. Do we know if anyone is working on Drupal 7?
Comment #79
mglaman@Collins405: we're doing it in 8.x first and then backporting. See #2717383: Add support for 3D Secure. I recently talked to torgosPizza and he's in the middle of a move
Comment #80
bojanz commented@Collins405
Drupal 7 and Drupal 8 versions are developed by different people and do not share code. The D7 maintainer (torgosPizza) might look at this code for inspiration, but the D7 issue is not blocked on the D8 one, or vice-versa.
Comment #81
mglamanWe need to verify the intent has a charge object.
The intent is never returned.
Comment #82
mglaman- Payment intent secret is null randomly on the first load of the review pane.
- StripeReview processes the intent on form submission.
- Need to verify that the payment intent has a charge attached.
Comment #83
mglamanThis enhances how authentication failures are handled.
However, payment intent amounts are out of sync. If I apply a coupon on the Review page, I am still charged the full amount. If I abandon checkout and come back, the payment amount will persist.
Comment #84
mglamanAJAX operations break the current process as the ID for the button changes to be unique for each request.
Comment #85
mglamanThis patch:
- Supports ensuring the PaymentIntent amount is sync'd with the order, which is critical as this is the amount charged once authentication is verified
- Fixes the mounting of the Stripe scripts due to dynamic IDs on AJAX.
Comment #86
mglamanI forgot some debugging lines while testing the payment amount sync.
This needs a `@todo` to remove once the commerce issue is fixed to make this a public method on the payment gateway.
Does not exist anymore.
Not used.
Inline form manager is not used.
Logger is not used.
"The HTML ID of the button that represents Pay and Complete"
Better message
This no longer influences the transaction data. That event should have the methods deprecated.
Comment #87
harika gujjula commentedOne more safe check i have used on this patch is adding the below condition.
if (($gateway->isEmpty()) || (!isset($gateway->entity)))
Comment #88
mglamanTechnically
->entityis set, it's just a null value. The check I prefer is that the value returned from ->entity matches the expected entity interface. It's mixed throughout the patch, I will update. Thanks for the suggestion, @harika gujjulaI'll be finishing this up today along with reviewing SetupIntent for recurring payments.
Comment #89
darrenwh commentedI've just updated a current site that will now go through QA:
Updated commerce to 8.x-2.13
Updated stripe-php to 6.36.0
Updated commerce_stripe to 8.0-dev
Added latest patch #85
Pointed it at testing servers and all went fine, had a notification about 3D Secure 2 and payment completed.
So working well so far, will await results from QA
Comment #90
darrenwh commentedhttps://www.bbc.co.uk/news/business-49332023
Comment #91
mglamanHere is a new patch! It's big! It's got tests! It's cleaned up!
I have added automated test coverage for payment intent creation, order total sync w/ intents, and payment creation. This fixes my code nits and phpcs.
Next is reviewing reusable payment methods and ensuring they work.
Comment #92
mglamanComment #93
mglamanReviewing https://stripe.com/docs/api/setup_intents/confirm, it looks like any and all calls to payment methods need to be wrapped in setup intents, so that future charges will not fail due to the customer unable to perform authentication client-side.
Comment #94
mglamanThis patch adds reusable payment method support. If the user is authenticated, this will provide a SetupIntent for the payment method so that it can be authenticated for recurring charges.
I think we may need a follow up for those who use an anonymous -> authenticated flow with recurring. However, that is difficult as the payment intent cannot be made from a payment method generated through setup intents unless it is attached to a customer.
I think this is an edge case for anonymous recurring signups. And is hard to test without real-world examples.
Comment #95
mglamanComment #96
darrenwh commentedDoing some testing and I'm seeing this error :
Message Error: Class 'Stripe\SetupIntent' not found in Drupal\commerce_stripe\PluginForm\Stripe\PaymentMethodAddForm->buildCreditCardForm() (line 30 of /var/www.current/docroot/modules/contrib/commerce_stripe/src/PluginForm/Stripe/PaymentMethodAddForm.php) #0 /var/www.current/docroot/modules/contrib/commerce/modules/payment/src/PluginForm/PaymentMethodAddForm.php(100): Drupal\commerce_stripe\PluginForm\Stripe\PaymentMethodAddForm->buildCreditCardForm(Array, Object(Drupal\Core\Form\FormState)) #1 /var/www.current/docroot/modules/contrib/commerce_stripe/src/PluginForm/Stripe/PaymentMethodAddForm.php(109): Drupal\commerce_payment\PluginForm\PaymentMethodAddForm->buildConfigurationForm(Array, Object(Drupal\Core\Form\FormState)) #2 /var/www.current/docroot/modules/contrib/commerce/modules/payment/src/Plugin/Commerce/InlineForm/PaymentGatewayForm.php(108): Drupal\commerce_stripe\PluginForm\Stripe\PaymentMethodAddForm->buildConfigurationForm(Array, Object(Drupal\Core\Form\FormState)) #3 /var/www.current/docroot/modules/contrib/commerce/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php(251): Drupal\commerce_payment\Plugin\Commerce\InlineForm\PaymentGatewayForm->buildInlineForm(Array, Object(Drupal\Core\Form\FormState)) #4 /var/www.current/docroot/modules/contrib/commerce/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php(207): Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentInformation->buildPaymentMethodForm(Array, Object(Drupal\Core\Form\FormState), Object(Drupal\commerce_payment\PaymentOption)) #5 /var/www.current/docroot/modules/contrib/commerce/modules/checkout/src/Plugin/Commerce/CheckoutFlow/CheckoutFlowWithPanesBase.php(558): Drupal\commerce_payment\Plugin\Commerce\CheckoutPane\PaymentInformation->buildPaneForm(Array, Object(Drupal\Core\Form\FormState), Array) #6 [internal function]: Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase->buildForm(Array, Object(Drupal\Core\Form\FormState), 'order_informati...') #7 /var/www.current/docroot/core/lib/Drupal/Core/Form/FormBuilder.php(519): call_user_func_array(Array, Array) #8 /var/www.current/docroot/core/lib/Drupal/Core/Form/FormBuilder.php(276): Drupal\Core\Form\FormBuilder->retrieveForm('commerce_checko...', Object(Drupal\Core\Form\FormState)) #9 /var/www.current/docroot/core/lib/Drupal/Core/Form/FormBuilder.php(217): Drupal\Core\Form\FormBuilder->buildForm('commerce_checko...', Object(Drupal\Core\Form\FormState)) #10 /var/www.current/docroot/modules/contrib/commerce/modules/checkout/src/Controller/CheckoutController.php(94): Drupal\Core\Form\FormBuilder->getForm(Object(Drupal\odl_checkout\Plugin\Commerce\CheckoutFlow\OdlMultistepDefault), 'order_informati...') #11 [internal function]: Drupal\commerce_checkout\Controller\CheckoutController->formPage(Object(Drupal\Core\Routing\RouteMatch)) #12 /var/www.current/docroot/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array) #13 /var/www.current/docroot/core/lib/Drupal/Core/Render/Renderer.php(582): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() #14 /var/www.current/docroot/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\Core\Render\Renderer->executeInRenderContext(Object(Drupal\Core\Render\RenderContext), Object(Closure)) #15 /var/www.current/docroot/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) #16 /var/www.current/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() #17 /var/www.current/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1) #18 /var/www.current/docroot/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #19 /var/www.current/docroot/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\Core\StackMiddleware\Session->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #20 /var/www.current/docroot/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #21 /var/www.current/docroot/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass(Object(Symfony\Component\HttpFoundation\Request), 1, true) #22 /var/www.current/docroot/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\page_cache\StackMiddleware\PageCache->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #23 /var/www.current/docroot/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #24 /var/www.current/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #25 /var/www.current/docroot/core/lib/Drupal/Core/DrupalKernel.php(693): Stack\StackedHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true) #26 /var/www.current/docroot/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request)) #27 {main}.Comment #97
darrenwh commentedComment #98
mglamanPlease update your Stripe library SDK.
EDIT:
composer update stripe/stripe
Comment #99
mglamanAh this is on SetupIntents! Sorry. 6.36 was for PaymentIntent.
6.40 added SetupIntent https://github.com/stripe/stripe-php/releases/tag/v6.40.0
Comment #100
darrenwh commentedI added "stripe/stripe-php": "^6.41" as a requirement in composer.json and it works now local SDK is now 6.43.0
Comment #101
mglamanBumped stripe/stripe-php SDK to the version which includes SetupIntent.
Comment #102
harika gujjula commentedTested the following cases on 8.x-1.x with the latest patch.
Commerce Version: 8.x-2.11
1. Initial Payment or Payment by entering the card details is failed with Any Test card(3D supported, 3D Required)
- Drupal Error Set message With Invalid Payment Details.
Logs:
InvalidArgumentException: $payment_details must contain the stripe_payment_method_id key. in Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\Stripe->createPaymentMethod() (line 357 of /Users/harika/Sites/bb_dev/docroot/modules/contrib/commerce_stripe/src/Plugin/Commerce/PaymentGateway/Stripe.php).
Invalid parameters were supplied to Stripe's API.
2. Recurring Payment Selection with the existing card details:
- Works Fine.
3. Payment Refund for successful paid order.
- Works Fine.
(Ref: I have attached the screenshot).
Comment #103
mglaman@harika gujjula 🥶 that's not good on #1. Can you try with Commerce 2.13 just to check. However it seems to be more linked to JavaScript bypassing clientside validation and we need better server-side handling.
Comment #104
nicolas bouteille commentedHello
I would like to report some positive testings of mine from this morning.
On a very recent Drupal 8 website which is currently on D8.7.5
I am using the Drupal Composer project.
I have installed Drupal Commerce (all submodules except Payment Examples) version 8.x-2.13
I have created a Store working for France in euros and French VAT, and one test product.
First I installed Commerce Stripe 1.0-rc2, created a new Stripe account and made a simple test.
Payment was working fine with regular VISA test card 4242424242424242
https://stripe.com/docs/testing
However, when I tried with the test card that requires 3DS 2.0 : 4000000000003220
It failed. I got errors on both Drupal Commerce and Stripe, saying the bank had refused payment requiring authentication.
And I could see the /charges API has been used.
I then removed Commerce Stripe from composer so that it would also removed old stripe-php library and then required Commerce Stripe 1.x-dev after adding the latest patch (#101) in composer.json so that it gets applied.
I tested again with the 3DS 2.0 test card from Stripe. This time I could see Stripe’s JS pop up from 3DS 2.0 and it worked! And I could see Payment Intents API had been used.
Only problem I noticed is that in Stripe Dashboard, I no longer have information about the « Client ». With 1.0-RC2, even though I checkout as Guest, I can see "First Name Last Name" as the Client. And in Stripe’s payment metadata, I now only see the order_id whereas on rc2 I would see order_id and store_id.
Thank you very much for the hard work!
Comment #105
mglamanUse \Drupal\commerce_payment\Exception\InvalidRequestException for better handling if stripe_payment_method_id is missing (client side prevents the form from submitting without this.
Added back the store_id to the payment intent metadata.
These were throwing warnings that they didn't exist. Maybe that's why the name disappeared? The name is always available for authenticated users.
Comment #107
mglamanFix mocked order.
Comment #108
nicolas bouteille commentedI might have found a use case where 3DS pop up shows up twice.
In short : if I register during checkout, 3DS pop up shows up before and after Review step.
FYI I am using Stripe 3DS 2.0 Testing Card : 4000000000003220
Steps to reproduce :
I have disabled "Allow Guest checkout" and enabled "Allow Register during checkout" in the Checkout Process settings.
So now my first step of the Checkout process is either Login or Register.
When I register, the next step is asking for Paiement information (Card + Billing Address).
When I validate this step, Stripe's 3DS 2.0 pop up shows up and I think it should not.
Indeed, the payment still has not been made which is normal I guess since the next step is Review.
When I validate the Review step, the payment is made and Stripe's 3DS pop up again, which is normal I guess.
If I disabled Register during checkout and enable Checkout as Guest again, 3DS pop up does not show up twice. It only shows up after Review, when the payment is actually made. So it seems the problem only occurs when I register during checkout. Just to make sure, I also tested with Login during checkout and the problem does not occur either.
Comment #109
bojanz commentedThis patch has come a long way! Here's an initial review.
We should provide a real docblock here, detailing what the subscriber around intents, and why it's needed (why it can't be done in the checkout pane or the gateway).
This should be protected. We don't do private methods.
I would also like this docblock to be extended, to clarify what the actual output of the pane is.
We can make this generic by using the 2.14 code:
Also allows us to stop injecting moduleHandler.
Is this true? I see the event used in createPayment.
What's going on here? When will the remote ID be set?
Comment #110
mglamanOnly the transaction data is deprecated, not the event itself. At this point, it is too late to modify any of the transaction data as it would invalidate the payment intent to which the user has agreed upon and been charged.
We would need to refactor that event to work without the payment entity and instead receive an order object from
\Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\Stripe::createPaymentIntent.The remote ID is set in doCreatePayment method. The remote ID needs to contain the payment method ID and customer ID.
Actually. I think this is an artifact from earlier versions of the patch where anonymous customers were having Stripe customer objects created and this section can be reverted back to the way it once was. There is no need for
REMOTE_PAYMENT_METHOD_ID|REMOTE_CUSTOM_IDanymore, I do not think.Comment #111
mglamanHere is a patch to address #109.
I'll be reviewing #108. This sounds like the SetupIntent is triggering 3DS for adding your card as a stored payment method. Then you're receiving the second 3DS for confirming the charge. But, it is weird it only occurs when you register during checkout.
Comment #112
nicolas bouteille commentedIndeed I noticed that the card is stored in the available payment methods on the user profile. Good point!
Is it normal though to have two 3DS validation so close one from the other? Maybe this is only because we're testing? But in real life an exemption would be requested?
For anonymous user it makes sense since the card won't be stored. For existing user, I think I might have only tested this with a user that had already stored the card. We need to test this with an existing user that does not have his/her credit card stored yet.
Comment #114
mglaman@Nicolas Bouteille thanks for the reply. I was afraid of this in #94 when we switched to SetupIntent. I was hoping it wouldn't cause two triggers. I'll test this today.
(patch attached to fix tests, renamed a class but forgot about services.yml, whoops!)
Comment #115
nicolas bouteille commentedFYI: I had a side question related to 3DS 2.0 so I opened a separate issue: #3077741: PSD2 / SCA / 3D Secure 2 : what's going to happen on September 14th?
Thanks!
Comment #116
mglamanAdds a FunctionalJavascript test for checkout. Fixes a bug I made in #111 where the remote ID became null.
Comment #118
mglamanThis adds a lot more testing. It also fixes the double modal! The problem is running SetupIntent when we're also doing a PaymentIntent. We only need SetupIntent when a PaymentIntent will not follow to capture authorization.
Comment #120
mglamanMarking that we need 2.x-dev, I think that'll fix the errors on checkout.
Adding more tests for setup intents for payment methods created outside of checkout. It is seeming redundant as future charges still require authentication for the amount. With or without setup intent the payment methods can be used and require approval at the end of checkout.
Comment #122
mglamanI've fixed up the test asserts for better timing. They should be faster and more reliable. The fail in #120 was due to bad timing.
We're creating setup intents when a payment method is added off-checkout. It seems it won't solve recurring. But it's part of the correct flow.
I want to see what DrupalCI says and then we just commit this to -dev.
Comment #124
mglamanThis should fix the error. I got on IRC and chatted with the Stripe folks to clear up some concerns.
Comment #125
mglamanUpdating issue credits before commit. Crediting those who helped do testing.
Comment #127
mglaman🚀 COMMITTED! 🎉
Thank you, everyone!
Comment #128
blacklabel_tom commentedHi,
I wanted to check the process of entering the 3D secure challenge. Currently I enter the credit card details, then the form submits and I see an error about processing the payment:
I can see in the logs that there is an error about an empty payment intent ID `Could not determine which URL to request: Stripe\PaymentIntent instance has invalid ID: `
On reload the payment method is saved and clicking the 'Pay and complete purchase' button presents the 3D secure challenge.
Looking at `drupal/web/modules/contrib/commerce_stripe/js/commerce_stripe.form.js` I can't see an AJAX call that I would expect to see to check if the card requires 3D secure or not.
I may be missing a setup step here as I've upgraded an existing install instead of creating one from scratch.
I'm using the latest dev of commerce_stripe and commerce 2.1.4.
Any help is appreciated!
Cheers
Tom
Comment #129
bojanz commented@blacklabel_tom
The current issue is fixed, you will need to open a new one with your comment.
Comment #130
Marko B commentedSeems there is some leftover in JS script commerce_stripe.form.js around line 120
this debugger; seems odd thing, looks like leftover?
Comment #131
mglamanCan you open a follow up issue? That did sneek in.
Comment #132
bojanz commentedPushed a quick fix: https://git.drupalcode.org/project/commerce_stripe/commit/271c408
Comment #134
nicolas bouteille commented@blacklabel_tom : (#128) I encountered a similar problem after removing patch #101 and updating to newest dev version. In the end, because of composer.lock my composer update command had not actually retrieved the latest dev version of commerce_stripe. I had to composer remove commerce_stripe and require it again to actually get the newest dev version (commit 5fb79fd).
Comment #135
duneblI have updated today my stripe module:
composer update drupal/commerce_stripe 1.x-devAnd I have made a payment (<30€) with my mastercard on my site.
Unfortunately, when going to the stripe dashboard and looking at my payment, I have the following warning:
This payment didn’t use an SCA-ready product. Unless you update your integration, payments like this are at risk of being declined.Am I missing something?
Should I clear cache and retry a new payment?
Comment #136
duneblI could solve #135 by re-installing the commerce_paiement module
This step allows me to remove the error message in the report/status page:
The Client Secret field needs to be installedOn my Dev server everything runs fine, but on my production server, I had to recreate a table (paiement_client_secret) as I got another sql error message during the un-installation of commerce_paiement (table not found)
If you have the same problem, check also that you have the last versions of:
-drupal/commerce
-stripe/stripe-php
-drupal/profile =>>!!!!important
-and of course drupal/commerce_stripe
After that, I could get SCA compliant paiements.
Thank you guys!!!
Comment #138
darrenwh commented@blacklabel_tom re #128 I added the patches on this page https://www.drupal.org/node/3079830
Comment #139
remaye commentedSorry to reopen this issue.
It is just to have a summary of what is needed at this time to make 3D secure 2 working.
I have updated to Drupal 8.9.11 and Commerce 8.x-2.21
By now, what are the correct versions of Commerce related modules ? Are last stable or dev versions required ?
I'm using of course Commerce_stripe (last dev version) and also Commerce_shipping (8.x-2.0-rc2) and Profile last dev version and strpe/stripe-php v7.67.0.
Is there also any patch requiered for some modules ?
And also how to see and be sure on local site that 3D secure is ok before to make it live ?
Thanks for help !
Comment #140
audriusb commentedConnect Stripe to sandbox and use test card details https://stripe.com/docs/testing
Comment #141
krzysztof domańskiTransactionData methods were removed as part of the 3D authentication issue.
Methods were useful for:
#3170903: Allow customising the payment intent
#3121771: Fix TransactionData event, include description and initial metadata