Is this supported? https://stripe.com/docs/payments/payment-element
From what I understand currently, we can use Stripe elements and Stripe checkouts?
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
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:
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:
Then I created an SC API called CreatePaymentIntent
which is called on checkout page load and outputs the client_secret
value:
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>
On the checkout (content) page I added the Stripe Component (with LineItems array attribute) as well as the Stripe checkout form:
<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>
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 ) shown on the checkout page:
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!
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:
https://stripe.com/docs/payments/quickstart#fetch-payment-intent (I have to fetch the PaymentIntent from my CreatePaymentIntent API instead)
https://stripe.com/docs/payments/quickstart#init-elements-html
(should this be replaced by elements = dmx.stripe.instance.elements({ appearance, clientSecret });
perhaps?
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:
SC action CreatePaymentIntent ā returns client_secret value which is used as an argument to run @karhās createPaymentElement function on SC CreatePaymentIntent succes event.
Stripe.js script included on the layout page
Stripe component on the checkout page:
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ā¦
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!!
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:
Client side you define product ids taht youāll use in your db query. say [1, 2]
You pass that to the server. Note: Iām using a server connect here because of the orders API.
createPaymentIntent
)
In the server action, query the total amount of the products that are given in the lineitems
In server action, create the payment intent with that
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.
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ā¦
App Connect Stripe Elements 2.0 beta 1 are now available in Wappler 6.4.1
We will be adding new tutorials about stripe elements in the next days.
@Teodor just now starting to use stripe. Do you have a link for the elements tutorials?
Thanks @sitestreet I hadn't seen that one, I read a different one that didn't seem complete. Look forward to checking this one out.