stripe/stripe-java

Simulate 3DS2 processing of the app

andreaskraschitzer opened this issue · 3 comments

Is your feature request related to a problem? Please describe.

I am using the Stripe Java SDK to create a setupIntent which is retrieved by the app which uses the Stripe Reactive Native SDK to process this intent and add a payment method for future OFF_SESSION payment processing.

I would like to test the whole backend flow in unit tests which includes all sorts or payment methods.
The "simulation" of 3DS verification works fine by extracting the redirect url from the payload and performing the browser interaction within the test.

How can I simulate the 3DS2 verification within a unit test?

I had a chat with a support agent on the website and they directed me here.

Describe the solution you'd like

No response

Describe alternatives you've considered

No response

Additional context

Detailed process:

        Stripe.apiKey = API_KEY;
        final UUID customerUUid = UUID.randomUUID();

        System.out.println("Creating customer with internal id=" + customerUUid);
        final Customer customer = Customer.create(CustomerCreateParams.builder()
                .setName("Foo Bar")
                .putMetadata(STRIPE_METADATA_GOURBAN_USER, customerUUid.toString())
                .build());
        System.out.println("Created customer with stripe id=" + customer.getId());

        final SetupIntentCreateParams params = SetupIntentCreateParams.builder()
                .setCustomer(customer.getId())
                .setUsage(SetupIntentCreateParams.Usage.OFF_SESSION)
                .addAllPaymentMethodType(List.of("card"))
                .build();
        final SetupIntent intent = SetupIntent.create(params);
        System.out.println("Created setupIntent with id=" + intent.getId());
      
        System.out.println(intent.confirm(SetupIntentConfirmParams.builder().setPaymentMethod("pm_card_threeDSecure2Required").build()));

Which results in the following output:

Creating customer with internal id=c12f7a69-ed0a-4bab-aa99-56dab2cbf0d0
Created customer with stripe id=cus_NltvC67Ol2To9o
Created setupIntent with id=seti_1N0M7xHoElaxnKTv3sKhUZSf
Received action_needed response from stipe:
{
  "application": null,
  "attach_to_self": null,
  "cancellation_reason": null,
  "client_secret": "seti_1N0M7xHoElaxnKTv3sKhUZSf_secret_NltvlojA6a2IqnBGXDZDL8s6c1Ts40H",
  "created": 1682330169,
  "customer": "cus_NltvC67Ol2To9o",
  "description": null,
  "flow_directions": null,
  "id": "seti_1N0M7xHoElaxnKTv3sKhUZSf",
  "last_setup_error": null,
  "latest_attempt": "setatt_1N0M7yHoElaxnKTvhuwCF51F",
  "livemode": false,
  "mandate": null,
  "metadata": {},
  "next_action": {
    "redirect_to_url": null,
    "type": "use_stripe_sdk",
    "use_stripe_sdk": {
      "directory_server_encryption": {
        "algorithm": "RSA",
        "certificate": "-----BEGIN CERTIFICATE-----\nMIIGAzCCA+ugAwIBAgIQDaAlB1IbPwgx5esGu9tLIjANBgkqhkiG9w0BAQsFADB2\nMQswCQYDVQQGEwJVUzENMAsGA1UECgwEVklTQTEvMC0GA1UECwwmVmlzYSBJbnRl\ncm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xJzAlBgNVBAMMHlZpc2EgZUNv\nbW1lcmNlIElzc3VpbmcgQ0EgLSBHMjAeFw0yMTA4MjMxNTMyMzNaFw0yNDA4MjIx\nNTMyMzNaMIGhMRgwFgYDVQQHDA9IaWdobGFuZHMgUmFuY2gxETAPBgNVBAgMCENv\nbG9yYWRvMQswCQYDVQQGEwJVUzENMAsGA1UECgwEVklTQTEvMC0GA1UECwwmVmlz\nYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xJTAjBgNVBAMMHDNk\nczIucnNhLmVuY3J5cHRpb24udmlzYS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQCy34cZ88+xfenoccRD1jOi6uVCPXo2xyabXcKntxl7h1kHahac\nmpnuiH+kSgSg4DEHDXHg0WBcpMp0cB67dUE1XDxLAxN0gL5fXpVX7dUjI9tS8lcW\nndChHxZTA8HcXUtv1IwU1L3luhgNkog509bRw/V1GLukW6CwFRkMI/8fecV8EUcw\nIGiBr4/cAcaPnLxFWm/SFL2NoixiNf6LnwHrU4YIHsPQCIAM1km4XPDb7Gk2S3o0\nkkXroU87yoiHzFHbEZUN/tO0Juyz8K6AtGBKoppv1hEHz9MFNzLlvGPo7wcPpovb\nMYtwxj10KhtfEKh0sS0yMl1Uvw36JmuwjaC3AgMBAAGjggFfMIIBWzAMBgNVHRMB\nAf8EAjAAMB8GA1UdIwQYMBaAFL0nYyikrlS3yCO3wTVCF+nGeF+FMGcGCCsGAQUF\nBwEBBFswWTAwBggrBgEFBQcwAoYkaHR0cDovL2Vucm9sbC52aXNhY2EuY29tL2VD\nb21tRzIuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC52aXNhLmNvbS9vY3Nw\nMEYGA1UdIAQ/MD0wMQYIKwYBBQUHAgEwJTAjBggrBgEFBQcCARYXaHR0cDovL3d3\ndy52aXNhLmNvbS9wa2kwCAYGZ4EDAQEBMBMGA1UdJQQMMAoGCCsGAQUFBwMCMDUG\nA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9lbnJvbGwudmlzYWNhLmNvbS9lQ29tbUcy\nLmNybDAdBgNVHQ4EFgQU/JtqQ7VLWNd3/9zQjpnsR2rz+cwwDgYDVR0PAQH/BAQD\nAgSwMA0GCSqGSIb3DQEBCwUAA4ICAQBYOGCI/bYG2gmLgh7UXg5qrt4xeDYe4RXe\n5xSjFkTelNvdf+KykB+oQzw8ZobIY+pKsPihM6IrtoJQuzOLXPV5L9U4j1qa/NZB\nGZTXFMwKGN/v0/tAj3h8wefcLPWb15RsXEpZmA87ollezpXeEHXPhFIit7cHoG5P\nfem9yMuDISI97qbnIKNtFENJr+fMkWIykQ0QnkM1rt99Yv2ZE4GWZN7VJ0zXFqOF\nNF2IVwnTIZ21eDiCOjQr6ohq7bChDMelB5XvEuhfe400DqDP+e5pPHo81ecXkjJK\ngS5grYYZIbeDBdQL1Cgs1mGu6On8ecr0rcpRlQh++BySg9MKkzJdLt1vsYmxfrfb\nkUaLglTdYAU2nYaOEDR4NvkRxfzegXyXkOqfPTmfkrg+OB0LeuICITJGJ0cuZD5W\nGUNaT9WruEANBRJNVjSX1UeJUnCpz4nitT1ml069ONjEowyWUcKvTr4/nrargv2R\npOD4RPJMti6kG+bm9OeATiSgVNmO5lkAS4AkOop2IcbRFcVKJUTOhx2Q37L4nuAH\nTCXQ9vwT4yWz6fVaCfL/FTvCGMilLPzXC/00OPA2ZtWvClvFh/uHJBjRUnj6WXp3\nO9p9uHfdV9eKJH37k94GUSMjBKQ6aIru1VUvSOmUPrDz5JbQB7bP+IzUaFHeweZX\nOWumZmyGDw\u003d\u003d\n-----END CERTIFICATE-----\n",
        "directory_server_id": "A000000003",
        "root_certificate_authorities": [
          "-----BEGIN CERTIFICATE-----\nMIIFqTCCA5GgAwIBAgIPUT6WAAAA20Qn7qzgvuFIMA0GCSqGSIb3DQEBCwUAMG8x\nCzAJBgNVBAYTAlVTMQ0wCwYDVQQKDARWSVNBMS8wLQYDVQQLDCZWaXNhIEludGVy\nbmF0aW9uYWwgU2VydmljZSBBc3NvY2lhdGlvbjEgMB4GA1UEAwwXVmlzYSBQdWJs\naWMgUlNBIFJvb3QgQ0EwHhcNMjEwMzE2MDAwMDAwWhcNNDEwMzE1MDAwMDAwWjBv\nMQswCQYDVQQGEwJVUzENMAsGA1UECgwEVklTQTEvMC0GA1UECwwmVmlzYSBJbnRl\ncm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xIDAeBgNVBAMMF1Zpc2EgUHVi\nbGljIFJTQSBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\n2WEbXLS3gI6LOY93bP7Kz6EO9L1QXlr8l+fTkJWZldJ6QuwZ1cv4369tfjeJ8O5w\nSJiDcVw7eNdOP73LfAtwHlTnUnb0e9ILTTipc5bkNnAevocrJACsrpiQ8jBI9ttp\ncqKUeJgzW4Ie25ypirKroVD42b4E0iICK2cZ5QfD4BSzUnftp4Bqh8AfpGvG1lre\nCaD53qrsy5SUadY/NaeUGOkqdPvDSNoDIdrbExwnZaSFUmjQT1svKwMqGo2GFrgJ\n4cULEp4NNj5rga8YTTZ7Xo5MblHrLpSPOmJev30KWi/BcbvtCNYNWBTg7UMzP3cK\nMQ1pGLvG2PgvFTZSRvH3QzngJRgrDYYOJ6kj9ave+6yOOFqj80ZCuH0Nugt2mMS3\nc3+Nksaw+6H3cQPsE/Gv5zjfsKleRhEFtE1gyrdUg1DMgu8o/YhKM7FAqkXUn74z\nwoRFgx3Mi5OaGTQbg+NlwJgR4sVHXCV4s9b8PjneLhzWMn353SFARF9dnO7LDBqq\ntT6WltJu1z9x2Ze0UVNZvxKGcyCkLody29O8j9/MGZ8SOSUu4U6NHrebKuuf9Fht\nn6PqQ4ppkhy6sReXeV5NVGfVpDYY5ZAKEWqTYgMULWpQ2Py4BGpFzBe07jXkyulR\npoKvz14iXeA0oq16c94DrFYX0jmrWLeU4a/TCZQLFIsCAwEAAaNCMEAwHQYDVR0O\nBBYEFEtNpg77oBHorQvi8PMKAC+sixb7MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P\nAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQC5BU9qQSZYPcgCp2x0Juq59kMm\nXuBly094DaEnPqvtCgwwAirkv8x8/QSOxiWWiu+nveyuR+j6Gz/fJaV4u+J5QEDy\ncfk605Mw3HIcJOeZvDgk1eyOmQwUP6Z/BdQTNJmZ92Z8dcG5yWCxLBrqPH7ro3Ss\njhYq9duIJU7jfizCJCN4W8tp0D2pWBe1/CYNswP4GMs5jQ5+ZQKN/L5JFdwVTu7X\nPt8b5zfgbmmQpVmUn0oFwm3OI++Z6gEpNmW5bd/2oUIZoG96Qff2fauVMAYiWQvN\nnL3y1gkRguTOSMVUCCiGfdvwu5ygowillvV2nHb7+YibQ9N5Z2spP0o9Zlfzoat2\n7WFpyK47TiUdu/4toarLKGZP+hbA/F4xlnM/8EfZkE1DeTTI0lhN3O8yEsHrtRl1\nOuQZ/IexHO8UGU6jvn4TWo10HYeXzrGckL7oIXfGTrjPzfY62T5HDW/BAEZS+9Tk\nijz25YM0fPPz7IdlEG+k4q4YwZ82j73Y9kDEM5423mrWorq/Bq7I5Y8v0LTY9GWH\nYrpElYf0WdOXAbsfwQiT6qnRio+p82VyqlY8Jt6VVA6CDy/iHKwcj1ELEnDQfVv9\nhedoxmnQ6xe/nK8czclu9hQJRv5Lh9gk9Q8DKK2nmgzZ8SSQ+lr3mSSeY8JOMRlE\n+RKdOQIChWthTJKh7w\u003d\u003d\n-----END CERTIFICATE-----\n"
        ]
      },
      "directory_server_name": "visa",
      "merchant": "acct_1LVYBkHoElaxnKTv",
      "one_click_authn": null,
      "server_transaction_id": "19a81822-76a6-4b4b-a530-fde70111237e",
      "three_d_secure_2_source": "src_1N0M7zHoElaxnKTv4PZi2xEo",
      "three_ds_method_url": "",
      "three_ds_optimizations": "kf",
      "type": "stripe_3ds2_fingerprint"
    },
    "verify_with_microdeposits": null
  },
  "object": "setup_intent",
  "on_behalf_of": null,
  "payment_method": "pm_1N0M7yHoElaxnKTvnMP02oGo",
  "payment_method_options": {
    "acss_debit": null,
    "blik": null,
    "card": {
      "mandate_options": null,
      "network": null,
      "request_three_d_secure": "automatic"
    },
    "link": null,
    "sepa_debit": null,
    "us_bank_account": null
  },
  "payment_method_types": [
    "card"
  ],
  "single_use_mandate": null,
  "status": "requires_action",
  "usage": "off_session"
}

Hello @andreaskraschitzer, thanks for writing in.

Let me know if I'm not interpreting your request correctly: I think what you want is a way to take the setup_intent from next_action: {type: 'use_stripe_sdk'} and cause it to transition past status: 'requires_action' using just stripe-java and without using one of the stripe frontend libraries (in your case Stripe React Native). Is that right?

I don't think this is something we would add to stripe-java.

  • It's more of a feature request for the Stripe API (a new testmode-only endpoint to manipulate setup intents), it's not something we can add to stripe-java directly.
  • Can you explain in more detail what value this adds, i.e. describe in more detail the test this would help you write? You can already cause the setup_intent to skip past status: 'requires_action' by using a different one of the test payment methods (i.e. pm_card_threeDSecureOptional) instead of pm_card_threeDSecure2Required. Why would it be better to use pm_card_threeDSecure2Required and then a test-mode-only method of artificially advancing the setup intent to arrive in the same place as if you just used pm_card_threeDSecureOptional? I understand the desire for end-to-end testing, but if your end-to-end test is using testmode-only flow that doesn't exist in production it's not actually helping test the real logic.

Please let me know if I've understood your request correctly. If so, I hate to redirect you again, but as (I think) this is a feature request for the API, the stripe-java repository isn't the correct place to track it and you should submit it as a feature request through support.

Hello @richardm-stripe!
Thank you for responding.

Almost correct!

using just stripe-java and without using one of the stripe frontend libraries (in your case Stripe React Native). Is that right?

It does not need to be stripe-java for me. I would already be happy to know a way to resolve it through for example some web requests where I pass the certificate to etc.

You are right concerning test flows, any test card that triggers status: 'requires action' flow would be enough to validate all of those flows, as all further steps need to be handled by the app.
As we have automated 3DS1 this is theoretically enough for test automation.

What I would actually use it for:
We supply backend and app for our customers. When setting up a new customer and configuring stripe for them, this usually happens a lot earlier than the app is available for them. After the setup we would like to test if it was done properly by performing a test transaction but as the app isn't available yet, we need to handle the status: 'requires_action' without the app.
When my credit card triggered 3DS1 approval, this was easy as I just took the link from the response, opened it in a browser, and then proceeded on my phone to approve the "transaction".
It is currently impossible with 3DS2.

Hello @andreaskraschitzer. I've investigated and this is not possible. We recommend running a test where you mock stripe, here, or rely on your testing of the other flows to build the confidence you need. It's not something we could build in stripe-java, as it needs to be client-side in order to trigger the 3DS2 authentication.