verbb / formie

The most user-friendly forms plugin for Craft CMS.
Other
96 stars 72 forks source link

How to submit Stripe Payment with GraphQL #1375

Open aodihis opened 1 year ago

aodihis commented 1 year ago

Question

Hello I have a question how to submit the Stripe payment integration with GraphQL.

I have created the Stripe Integration and added the Payment field on my form, then how to submit it on Graph QL. In schema the field is listed as String, but when input it with the payment method Id I got error something like this Missing stripePaymentId from payload

I also have tried to pass it as something like this as JSON String : {"stripePaymentId":"some String payment Id","stripePaymentIntentId":"","stripeSubscriptionId":""} and still got the error.

Also if the user use the 3ds payment how to know it need next action in Graph QL?

engram-design commented 1 year ago

Formie's JS will handle the payment integration side of things, but if you're using GraphQL, you're likely using your own front-end JS as well. So like some captchas like (ReCAPTCHA), you're kind of on your own when it comes to the front-end implementation.

You're going to need to replicate our JS functionality on your own. A good start is reviewing what we're doing which also handles 3DS, although that requires some additional work. You'll need to trigger Stripe's JS on your front-end to generate the token, and handle any 3DS challenge.

aodihis commented 1 year ago

@engram-design Thank you for the answer. Yes, I understand on that parts, but our current issue is more about inputing and getting the payment data from GraphQL. Here is for the detail what we facing.

  1. How to input the paymentMethodId on GraphQL. If we using post request we can actually post the paymentMethodId into this kind of format : stripePayment [paymentMethodId] : a paymentMethodId but in GraphQL, the argument for the arguments for stripe Payment field is indicated as string not like the other complex file that usually have this kind schema {formHandle}_{fieldHandle}_FormieNameInput. So for workaround for this one, we do the sent something like this on {"stripePaymentId":"some String payment Id","stripePaymentIntentId":"","stripeSubscriptionId":""} on Json format. And then inside backend we need to decode it so we hook to event before submission and convert it to array

    Event::on(Submissions::class, Submissions::EVENT_BEFORE_SUBMISSION, static function(SubmissionEvent $event) {
            $submission = $event->submission;
    
                $strip = $submission->getFieldValue('stripeHandle');
                if (is_string($strip)) {
                    $stripObject = json_decode($strip, true);
                    $submission->setFieldValue('stripeHandle', $stripObject);
            }
        });
  2. This one on 3DS case. On 3DS after you submit the form if it is asking for 3DS, In post request Formie will sent the info to front end to be able to do the 3DS processing. The information I mean is this kind of thing : "stripe3dsEvent":[{"id":"xxx","client_secret":"xxxx"}]} But on GraphQL we cannot get these kind of information. For workaround we hook into event EVENT_AFTER_SUBMISSION and pass these kind of information to Error Message, so we will get these data on front end.

  3. 3DS case, After pass on second case, it will lead to another problem. We able to save the submission and make the payment received to stripe but the issue is in formie it will create 2 submission, because in We do 2 times gql mutation. The first one is the submission that marked as incomplete that created when we sent for the gql mutation for the first time that will return an error that you need to do some 3DS process. And the second one is the submission that marked as complete that created when we sent after completed the 3DS procedure. The strange thing is the first submission that is incomplete will show that there is payment integration but no in second submission that is completed.

engram-design commented 1 year ago

So yes, good point there's not really specific handling setup for the payment model going back, we may need to look at adding a new interface for Stripe payments. You can indeed use a JSON-encoded string with those three values, which is what Twig templates do (the JS populates those fields) but you do need to handle that yourself (which you are).

As for 3DS, there are ways we check for that, and it will always require 2 requests to the server. One for the initial payment attempt (where you have a stripePaymentId), which if this is deemed to require "further action" will send back the information needed to trigger the 3DS challenge. But that is tied heavily to our JS, but the JSON response from the server should contain this information for you to extract, we might just need some better handling for GraphQL mutations.

Once the challenge has been confirmed, the payment is triggered again, this time with the stripePaymentIntentId value, and processed

So I will say, that we need to add a lot of handling for payments and GraphQL. We'll add that to our todo list.

chasegiunta commented 1 year ago

FYI, I am not trying use to GraphQL, but still am also getting an MissingstripePaymentIdfrom payload error when including a Stripe field in the form.

engram-design commented 1 year ago

Hmm, any other JS errors @chasegiunta ? All on the latest versions?

chasegiunta commented 1 year ago

@engram-design I am up to date on everything, currently. There are zero JS errors.

Stripe integration seems to be set up successfully.

image

This is on a multipage form. I haven't tried with single page (yet). The payment field is on the last page, but it's as if clicking the next page button I have set up attempts to submit the entire form perhaps (wild guess)?

This form was working great prior to putting in the Payment field.

Here's the stack.

2023-04-04 21:56:04 [INFO] Submission triggered for athleteRegistration.
2023-04-04 21:56:04 [ERROR] Stripe: Payment error: “Missing `stripePaymentId` from payload: .” /Users/chasegiunta/git/next/vendor/verbb/formie/src/integrations/payments/Stripe.php:398. Payload: “[]”. Response: “[]”
2023-04-04 21:56:04 [ERROR] Stripe: API error: “Missing `stripePaymentId` from payload: .” /Users/chasegiunta/git/next/vendor/verbb/formie/src/integrations/payments/Stripe.php:398
2023-04-04 21:56:04 [ERROR] Couldn’t save submission due to errors - {"payDues":["Missing `stripePaymentId` from payload: ."]}.
2023-04-04 21:56:04 [INFO] Request context:
$_GET = []

$_POST = [
    'CRAFT_CSRF_TOKEN' => '••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••'
    'action' => 'formie/submissions/submit'
    'submitAction' => 'submit'
    'handle' => 'athleteRegistration'
    'siteId' => '1'
    'fields' => [
        'firstName' => 'Chase'
        'lastName' => 'Giunta'
        'personalEmail' => 'me@chasegiunta.com'
        'schoolEmail' => ''
        'dateOfBirth' => '01/03/1988'
        'phoneNumber' => [
            'number' => '(469) 555-555'
        ]
        'weightClass' => '116'
        'teamId' => '2285'
        'previouslyCompetedIn' => [
            0 => 'None'
        ]
        'highSchool' => ''
        'hsGradYear' => ''
        'hsCity' => ''
        'hsState' => ''
        'hsWins' => ''
        'hsLosses' => ''
        'highestStatePlace' => 'didNotPlace'
        'firstName1' => ''
        'lastName1' => ''
        'relationship' => ''
        'emailAddress' => ''
        'phoneNumber1' => ''
        'streetAddress' => ''
        'city' => ''
        'state' => ''
        'zipCode' => ''
        'payDues' => [
            'stripePaymentId' => ''
            'stripePaymentIntentId' => ''
            'stripeSubscriptionId' => ''
        ]
    ]
]
engram-design commented 1 year ago

Does the credit card form actually get rendered?

image

And then a request to Stripe should kick in

image

To fetch the stripePaymentId and then submit the form. If the form is submitting immediately, it sound like there might be something amiss with the JS for either the form itself, or just Stripe.

chasegiunta commented 1 year ago

@engram-design The stripe form isn't getting rendered because it's on the last page (5th), and attempting to fill out the first page of form and proceed to the 2nd will cause this error and prevent saving form to proceed.

I'll try this on a single page form later today, but just guessing this is due to multi-page form.

engram-design commented 1 year ago

@chasegiunta sorry I did misunderstand that! Validation shouldnt kick in until the Stripe field is visible on the page. Your form will need to use Ajax page submission in case that’s no already set. I wouldn’t have thought there be much else to trigger that…

chasegiunta commented 1 year ago

@engram-design no worries! It is set to use AJAX, I saw that part mentioned in the docs 🙂. If you're unable to recreate I can attempt some further troubleshooting later today

engram-design commented 1 year ago

Yep, all working for me (sorry!). We use intersecting observer to watch for when the field becomes visible on the page. So unless there’s something invalid about that logic..

chasegiunta commented 1 year ago

@engram-design FYI I don't really need this functionality, but was toying with it, so simply just doing this to hopefully help sort it out.

Looks like the payment being on a multipage form is the differentiator. Putting the payment field on a single page form caused no issues.

I set up a bare min template, with default Formie form template:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>
  {% set form = craft.formie.forms({ handle: 'testForm' }).one() %}
  {{ craft.formie.renderForm('testForm') }}
</body>
</html>
image

This is rendering not only stripe.js, but also the hidden stripe <input>s

image

Resulting in upon clicking Next page (granted, it goes to the next page, as you can see).

image

Is formie's JS actually preventing a default submit here when clicking the Next Page <button>? That's the only reason I can think as to why this form seems to be prematurely submitting, before the actual stripe field renders.

engram-design commented 1 year ago

Appreciate the help!

So while the input may be getting rendered the Stripe JS should be be initialized until the input is visible on the page to get around these multi-page concerns https://github.com/verbb/formie/blob/8c7df87e145110133214938d84f0fe2258f73e8e/src/web/assets/frontend/src/js/payments/stripe.js#L35

So if Stripe’s JS is being loaded, then something with this logic must be off. I’ll keep looking at it!