Is the 'Stripe Payment Element' supported?

Is this supported? https://stripe.com/docs/payments/payment-element

From what I understand currently, we can use Stripe elements and Stripe checkouts?

Hi @karh, did you ever find the answer to this question and find out how to implement it?

Hey Emiel,
The answer is no: itā€™s not supported as in, made easy by the Wappler team.

And yes, I have implemented it anyway with the stripe JS sdk.
Some screenshots if that helps:


Iā€™m using the orders api (Beta), but basically I just followed the logic from the stripe documentation.

If you get stuck at some point let me know and I can help out

1 Like

Thanks for sharing this @karh, however the order api is not accepting anymore beta users.

I am trying to setup the Stripe Payment Element following Stripeā€™s docs to setup a custom payment flow:
https://stripe.com/docs/payments/quickstart

Trying to combine that with DMX Stripe components as much as possible, but canā€™t get the payment element to show on the checkout page.

What I got so far:

  1. Of course I prefer to replace server.js file with a server connect api.
    So I entered my Stripe secret key in the server connect settings:
    image
    Then I created an SC API called CreatePaymentIntent which is called on checkout page load and outputs the client_secret value:
    image
    image

  2. On the layout page (Iā€™m on NodeJS) I added the required scripts and stylesheets which I copied from the Stripe docs and placed in the public/css and public/js folders:

     <link rel="stylesheet" href="/css/checkout.css" />
     <script src="https://js.stripe.com/v3/"></script>
     <script src="/js/checkout.js" defer></script>
    
  3. On the checkout (content) page I added the Stripe Component (with LineItems array attribute) as well as the Stripe checkout form:
    image

      <form id="payment-form" is="dmx-stripe-checkout-form" method="post" action="/api/Checkout" dmx-bind:prop-client-secret="PaymentIntent.data.client_secret" dmx-bind:success-url="'/'+checkout+'/'+success">
       <div id="payment-element">
              <!--Stripe.js injects the Payment Element-->
       </div>
       
       <button id="submit">
                <div class="spinner hidden" id="spinner"></div>
                      <span id="button-text">Pay now</span>
       </button>
       <div id="payment-message" class="hidden"></div>
       </form>
    
  4. The checkout.js file is equal to the Stripe docā€™s version, except for some modifications to insert the correct clientsecret and LineItems:

     const items = {{CartContents.data.ProductData}};
    
     const cliensecret = {{PaymentIntent.data.createPaymentIntent.client_secret}};
    

Well thatā€™s it, but I am clearly missing something, because thereā€™s an empty form (except for the button :upside_down_face:) shown on the checkout page:

image

Thereā€™s an error output by the console Unexpected token '{' which refers to this line:
const items = {{CartContents.data.ProductData}}; which I canā€™t get fixed. Apart from that there are no errors shown.

Itā€™s probably possible to replace even more of the checkout.js file by dmx components too, but for now I am trying to get it to work with the full Stripe docā€™s version first.

Any help would be very much appreciated! :grimacing:

Youā€™re mixing javascript and appconnect bindings.

If you want to create the payment element with the clientsecret, you can do something like this:

    // create the payment element with the clientsecret returned from 'create_order'
    function createPaymentElement(clientSecret){
        const appearance = {
        theme: 'stripe',
        };
        elements = dmx.stripe.instance.elements({ appearance, clientSecret });
        // options for rendering the payment element
        let options = {
            fields: {
                billingDetails: {
                name: 'never',
                email: 'never'
                }
            }   
        }
        const paymentElement = elements.create("payment", options); // added options to not render name and email
        paymentElement.mount("#payment-element");
}

Note that I wrapped it in a function createPaymentElement() and pass clientSecret as argument?
So I need to first create an order, I donā€™t think you need to do that. But you can apply the same logic as thisā€¦

Iā€™m calling the function createPaymentElement() with the argument of the client secret that I got from the server action.

So that way Iā€™m passing my app connect variable to javascript.

You can also access the javascript variables like this: const items = dmx.parse('CartContents.data.ProductData')

More info here:

Thanks again @karh!

So I inserted your createPaymentElement function to the checkout page and run it on success event of the PaymentIntent API:

But still however the payment element stays empty. I suppose the checkout.js file is not correct and some values need to be set correctly there. Could you take a look at this copy of that file (api_key set correctly or course in the used version)? checkout.zip (1.4 KB)

Following the Stripe docs I am quite sure I got everything setup correctly as described above, except for these parts:

  1. https://stripe.com/docs/payments/quickstart#fetch-payment-intent (I have to fetch the PaymentIntent from my CreatePaymentIntent API instead)

  2. https://stripe.com/docs/payments/quickstart#init-elements-html
    (should this be replaced by elements = dmx.stripe.instance.elements({ appearance, clientSecret }); perhaps?

  3. Is it necessary to insert the LineItems here in checkout.js as well(which is done on CreatePaymentIntent API already)? Guess notā€¦ const items = [{ id: "xl-tshirt" }];

More generally, would it not be possible to replace checkout.js by the AppConnect Stripe component which defines many of the variables included in checkout.js and a short script on the checkout page (to create the payment element)?

I donā€™t use a checkout.js.
You can use wapplerā€™s stripe component

Then you can use this elements = dmx.stripe.instance.elements({ appearance, clientSecret }) (I donā€™t remember changing this, so should work for you as well)

note: that screenshot is not showing my settings, just made a new one.

The only custom js I have is that function and one more function that handles the submit button, looks like this for me:

// submit event handler
async function handleSubmit(returnUrlParam) { //function name 'handleSubmit'
    // Get name and email from input fields
    name = document.querySelector("#inp_name").value;
    email = document.querySelector("#inp_email").value;
    orderId = document.querySelector("#inp_orderid").value;

    // Set return url
    let returnUrl = (returnUrlParam) ? returnUrlParam : "<%= $_SERVER.BASE_URL %>/order/status?order_id="+orderId;

    // if the address fields are visible
    if (document.querySelector('#inp_business_purchase').checked) {
        city = document.querySelector("#inp_city").value;
        street_address = document.querySelector("#inp_street_address").value;
        postal_code = document.querySelector("#inp_postal_code").value;
        country = document.querySelector("#inp_country").value;
        company = document.querySelector("#inp_business_name").value;
        tax_id = document.querySelector("#inp_vat_number").value;
    } else {
        city = null;
        street_address = null;
        postal_code = null;
        country = null;
        company = null;
        tax_id = null;
    }
    
    
    const { error } = await dmx.stripe.instance.processOrder({
    elements,
    confirmParams: {
        // Make sure to change this to your payment completion page
            return_url: returnUrl,
            receipt_email: email,
            payment_method_data: {
                metadata: {
                        "company_name": company,
                        "tax_id": tax_id
                    },
                billing_details: {
                        "name": name,
                        "email": email,
                        "address": {
                            "line1": street_address,
                            "city": city,
                            "postal_code": postal_code,
                            "country": country
                  },
                }
            }
        },
    });

    // This point will only be reached if there is an immediate error when
    // confirming the payment. Otherwise, your customer will be redirected to
    // your `return_url`. For some payment methods like iDEAL, your customer will
    // be redirected to an intermediate site first to authorize the payment, then
    // redirected to the `return_url`.
    if (error.type === "card_error" || error.type === "validation_error") {
    showMessage(error.message);
    } else {
    showMessage("An unexpected error occurred.");
    }

    // setLoading(false);
}

Had that component on the checkout page already, although I knew it was kind of doing the same as checkout.js. So I removed the checkout.js script call from the layout page now.
Summarizing what I got now:

  1. SC action CreatePaymentIntent --> returns client_secret value which is used as an argument to run @karhā€™s createPaymentElement function on SC CreatePaymentIntent succes event.

  2. Stripe.js script included on the layout page

  3. Stripe component on the checkout page:

  1. This code on the checkout page to generate the checkout form with ā€œstripe payment elementā€:

                <!-- Stripe Payment Element -->
                <script>
                     // create the payment element with the clientsecret returned from 'PaymentIntent ServerConnect'
                     function createPaymentElement(clientSecret){
                         const appearance = {
                         theme: 'stripe',
                         };
                        elements = dmx.stripe.instance.elements({ appearance, clientSecret });
    
                     // options for rendering the payment element
                     let options = {
                     fields: {
                     billingDetails: {
                     name: 'never',
                     email: 'never'
                                 }
                         }   
                     }
                         const paymentElement = elements.create("payment", options); // added options to not render name and email
                         paymentElement.mount("#payment-element");
                     }
                 </script>
    
                 <form id="payment-form" is="dmx-stripe-checkout-form" method="post" action="/api/Shop/Checkout" dmx-bind:prop-client-secret="PaymentIntent.data.client_secret" dmx-bind:success-url="'/'+checkout+'/'+success">
                     <div id="payment-element">
                         <!--Stripe.js injects the Payment Element-->
                     </div>
                     <button id="submit">
                         <div class="spinner hidden" id="spinner"></div>
                         <span id="button-text">Pay now</span>
                     </button>
                     <div id="payment-message" class="hidden"></div>
                 </form>
    

I must be missing something, but I canā€™t figure out what since I donā€™t know what exactly Wapplerā€™s Stripe components do and what they donā€™t include. Maybe @Teodor can help me out?

So whatā€™s happening now? Any error?

Thanks for following up @karh!

Made some progress. Someā€¦ :slight_smile:
The payment element is showing now, it appeared to be the TotalAmount value that was empty so I set a static value to it for testing purposes. How would you transfer that to the CreatePaymentIntent API? I guess inputting it as a GET variable is unsafe, right?

Besides that I added the submit handler, but got this error when the Pay button is clicked:
IntegrationError: Missing value for stripe.redirectToCheckout: lineItems.0.price should be a string.

Not really sure if I still need to setup the form as a stripe checkout form with a checkout API action (and including which action steps) on it with the submit handler function in place?

Thanks for any further help in advance, really appreciate it!! :pray:

Sorry donā€™t have a lot of headspace these days so Iā€™m just trying to help out while being brief.

I am not sure what you mean with ā€˜transfer totalamount to the createpaymentintent apiā€™. But I work with the products and prices APIā€™s from stripe.

Products and prices are defined beforehand.

I pass the IDā€™s (my db) idā€™s of these prices to the checkout page.

Then I create an order (not sure if paymentintents can work with products/prices), in the serverā€¦ I first do a db query to get the stripe price ids that belong to the products/prices on the checkout page.
Then I send those products/prices\

You could do the same, but then with a total amount. So your logic is:

  1. Client side you define product ids taht youā€™ll use in your db query. say [1, 2]

  2. You pass that to the server. Note: Iā€™m using a server connect here because of the orders API.


    But I think when I worked with paymentintents (long time ago) I just passed all the info I wanted to the linteitems object in the stripe componentin the lineitems object, and then manipulated that data in the server action (I got out my checkout_id and whatever else, and then cleaned up the array of lineitems to then pass it to the createPaymentIntent)

  3. In the server action, query the total amount of the products that are given in the lineitems

  4. In server action, create the payment intent with that

  5. Pass back client secret to the page to finish payment

Again: I dontā€™ have much headspace, just writing out more details. I hope you can piece it together! Otherwise I hope next week I can help more.

1 Like

My advice (and not being critical of Wappler in any way) is to learn the stripe API and use the Wappler API action to build your own Library of Stripe calls. Itā€™s quicker and far less frustrating and Wapplerā€™s API action has never let us down.

Thanks John, I think I am going to give that a try indeedā€¦