Implementing Stripe Payments in Docly: A Step-by-Step Guide
Today, we're excited to walk you through the process of integrating Stripe, a leading online payment platform, into your Docly applications. An effective and seamless payment process can drastically enhance your user experience, and we're here to show you just how straightforward this implementation can be.
Before you start, please ensure you have registered with Stripe and obtained your unique API keys. Remember, Stripe provides separate keys for testing and live environments, so make sure you use the correct key for your current development stage.
Now, let's dive into the steps needed to incorporate Stripe payments in your Docly application:
Installing the Stripe JavaScript library:
Begin by adding the Stripe.js library to your project. This is the base layer that will allow your application to interact with the Stripe API. It's as easy as including the Stripe.js script in your HTML.
Mounting the Stripe Payment Element:
With the Stripe library in place, you mount the Stripe Payment Element into your checkout page. The Payment Element collects your users' card information directly through Stripe, so the sensitive data never touches your own server, keeping you PCI compliant.
Creating a PaymentIntent on the server:
When a user submits the payment form, your server calls the Stripe API to create a PaymentIntent. Stripe returns a client secret, which is used on the client side to confirm the payment securely without the sensitive card data ever touching your own server.
Confirming the payment:
Using the client secret together with the Stripe Payment Element, the user confirms the payment from the browser. Stripe handles authentication (such as 3D Secure) and then redirects the user back to your return page.
Handling the response:
Finally, it's essential to handle the response from the Stripe API. Depending on the result of the transaction, you may need to update your application's UI or database.
Creating a config file
Create a schema for your application with your desired settings. We always recommend storing this under the # folder, and a great name for this config file is simply "Config". And a great best practice for your config file schema is "<Application name> config". Enter your application name in the brackets.
Example:
From your shopping cart - call API to create an order
function submitOrder() {
scroll("#contents");
document.querySelector("#checkout-element").style.display = "none";
document.querySelector("#loader").style.display = "";
var order = { items : cart.items, name : name, email:email, phoneno:phoneno, terms:terms, validation:validation, total: cart.total };
var data = new URLSearchParams({ order : JSON.stringify(order) });
console.log(order);
fetch("#request.sitepath#API/SubmitOrder", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" },
body: data.toString()
})
.then(function (response) {
if (!response.ok) {
return response.json().then(function (err) {
throw new Error(err.Message);
});
}
return response.json();
})
.then(function (result) {
console.log("Submit response", result);
window.localStorage.setItem("cart_payment", JSON.stringify(result));
setupPayment(result);
})
.catch(function (error) {
console.log(error);
alert(error.message);
document.querySelector("#checkout-element").style.display = "";
document.querySelector("#loader").style.display = "none";
});
} Create API function to create the order file and Stripe Order (SubmitOrder.js)
In this function we are also checking a google recaptcha validation, which is something you must do to prevent hacking.
export default () => {
// Read the order from the parameter posted from JS
if (!form.order) throw new Error("Posted form is missing in request!");
let order = JSON.parse(form.order);
// Validate form
if (!docly.isName(order.name)) throw new Error("Name is required!");
if (!docly.isEmail(order.email)) throw new Error("Email is required!");
if (!docly.isString(order.phoneno, 8)) throw new Error("Phoneno is required!");
if (!docly.isString(order.validation)) throw new Error("Google recaptcha validation token is missing!");
if(!order.items)
throw new Error("Shopping cart is empty!");
// Check Google token
// Iht. guide https://developers.google.com/recaptcha/docs/verify
let config = docly.getFile("#/Config");
let googleUrl = "https://www.google.com/recaptcha/api/siteverify?response=" + order.validation + "&secret=" + config.GoogleRE;
let result = docly.httpPost(googleUrl, {});
if (!result.success) throw new Error("Google validation failed!");
// Calculate value of items
let total = 0.0;
let items = [];
for(var line of order.items) {
let product = docly.getFile(docly.urlDecode(line.Url));
if (product == null)
throw new Error("An invalid / outdated product has been specified " + line.url);
total += parseFloat(product.Price);
let item = {
"Product" : product.filename,
"Url" : product.Url,
"Variant" : line.Variant,
"Length" : line.Length,
"Price" : product.Price
};
items.push(item);
}
if (total == 0) {
throw new Error("No items / invalid items in shopping cart (order totaled to 0,-)!");
}
if (parseFloat(total) != parseFloat(order.total)) {
throw new Error("Outdated prices in shopping cart, was "+ total+ " expected "+ order.total + " | items count: " + order.items.Count);
}
// Save order to Docly server
let save = {
"Name" : order.name,
"Email" : order.email,
"Phoneno" : order.phoneno,
"Items" : items,
"Total" : total,
"IP" : getRemoteIP(),
"Google_response" : JSON.stringify(result)
};
let timestamp = docly.format(getdate(), "yyyyMMdd-HHmmss");
let filename = timestamp + " - " + order.email;
let filepath = "#/Orders/" + filename;
if (FileExists(filepath))
throw new Error("Failed to create order, please try again!");
let saved = docly.saveFile(filepath, save, "#/My shop order");
let stripeamount = parseInt(total * 100);
// Create stripe order (payment intent)
let stripe = {
"amount" : stripeamount,
"currency" : config.Currency,
"description" : "Shop order",
"receipt_email" : order.email
};
let headers = { "Authorization" : "Bearer " + config.Key };
let payment = docly.httpFormPost("https://api.stripe.com/v1/payment_intents", stripe, headers);
if (payment === null || payment.error) throw new Error("Failed to create payment intent with Stripe!");
// Update order on server
save["StripeId"] = payment.id;
save["Stripe_response"] = JSON.stringify(payment);
let update = docly.saveFile(filepath, save, "#/My shop order");
// Return payment info / url
let final = {
"stripeSecret" : payment.client_secret,
"orderid" : filename,
"timestamp" : getdate()
};
return final;
} Initiating payment element in your HTML page
Next, initiate the payment element in your HTML page as shown below. The submitOrder() function (from the previous step) calls SubmitOrder and passes the returned client secret to setupPayment(), which mounts the Stripe Payment Element. When the user clicks pay, confirmPayment() finalizes the payment and redirects them to the return page.
<!-- Load Stripe.js and set up the Payment Element -->
<script src="https://js.stripe.com/v3/"></script>
<!-- Container where the Stripe Payment Element will be mounted -->
<div id="payment-element"></div>
<script>
#{
var config = docly.getFile("#/Config");
var publishableKey = config.ID;
}#
const stripe = Stripe("#publishableKey#", {
apiVersion: '2020-08-27',
});
let elements;
// Called after SubmitOrder returns the client secret from the server.
// It mounts the Stripe Payment Element using the returned secret.
function setupPayment(result) {
elements = stripe.elements({ clientSecret: result.stripeSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
document.querySelector("#loader").style.display = "none";
document.querySelector("#checkout-element").style.display = "";
}
// Confirms the payment with Stripe. Stripe handles any required
// authentication (e.g. 3D Secure) and then redirects the user to
// the return page, passing along the order id.
async function confirmPayment(orderId) {
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: "#request.sitepath#return?id=" + orderId
}
});
if (error) {
console.log(error);
alert(error.message);
}
}
</script> Create a return page
After a completed payment the user will be returned to your page:
<!--#master file="#/master.hash"-->
<html lang="en">
<head>
<title xdt:Transform="Replace">Checkout page</title>
</head>
<body>
<section id="contents" xdt:Transform="Replace" style="min-height:200px">
<section id="loader">
<div class="loader">
<div class="duo duo1">
<div class="dot dot-a"></div>
<div class="dot dot-b"></div>
</div>
<div class="duo duo2">
<div class="dot dot-a"></div>
<div class="dot dot-b"></div>
</div>
</div>
</section>
<div class="container" id="success" style="display:none">
<br>
<h1 class="hero-heading mb-1 mb-sm-3 mt-sm-2">
<i class="fas fa-check-circle text-success"></i> Thanks for your order</h1>
<p class="lead text-muted mb-6">Your receipt and more information about pickup has been sent to your email.</p>
</div>
<div class="container" id="failed" style="display:none">
<br>
<h1 class="hero-heading mb-1 mb-sm-3 mt-sm-2">
<i class="fas fa-times-circle text-danger"></i> Payment failed!</h1>
<p class="lead text-muted mb-6">Your payment was not registered successfully, please try again or contact support if you need assistanse.</p>
</div>
</section>
<div id="ActivePaymentDiv" xdt:Transform="Replace"></div>
<section id="pagescript" xdt:Transform="Replace">
<script>
function scroll(target) {
const el = document.querySelector(target);
if (el) {
const top = el.getBoundingClientRect().top + window.pageYOffset - 160;
window.scrollTo({ top: top, behavior: "smooth" });
}
}
function getParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// Submits order to server
function retreivePaymentStatus() {
const params = new URLSearchParams({ id: getParam("id") });
console.log(params.toString());
fetch("#request.sitepath#API/CheckPayment?" + params.toString(), {
method: "GET",
headers: { "Accept": "application/json" }
})
.then(function (response) {
if (!response.ok) {
return response.json().then(function (err) {
throw new Error(err.Message);
});
}
return response.json();
})
.then(function (result) {
console.log("Submit response", result);
if (result.Success) {
document.getElementById("loader").style.display = "none";
document.getElementById("success").style.display = "";
cartEmpty();
} else {
document.getElementById("loader").style.display = "none";
document.getElementById("failed").style.display = "";
}
})
.catch(function (error) {
console.log(error);
alert(error.message);
});
}
document.addEventListener("DOMContentLoaded", function() {
retreivePaymentStatus();
});
</script>
</section>
</body>
</html> Create API function to update payment (CheckPayment.js)
export default (id) => {
if (!id) throw new Error("ID parameter not specified!");
// Check the updated status from Stripe
let config = docly.getFile("#/Config");
let filename = "#/Orders/" + id;
let order = docly.getFile(filename);
if (order === null) throw new Error("Order not found with specified ID!");
// Retrieve the status of the order:
let headers = { "Authorization" : "Bearer " + config.Key };
let payment = docly.httpGet("https://api.stripe.com/v1/payment_intents/" + order.StripeId, headers);
if (payment === null || payment.error) throw new Error("Failed to retrieve payment status from Stripe!");
//return JSON.stringify(payment);
// Update the status of the order if it has changed:
if (order.StripeStatus != payment.status)
{
order["StripeStatus"] = payment.status;
docly.saveFile(filename, order);
if (payment.status == "succeeded") {
// Send receipt
docly.sendEmail(filename, "Receipt.html.pdf", order.Email, "Order confirmation", config.OrderMessage);
// Send notification
docly.sendEmail(filename, "Order.html.pdf", config.Notifications, "New order placed", "Someone placed an order. Order is attached. Please verify that payment has been successfully charged in Stripe.");
}
}
let data = {
Success : (payment.status == "succeeded")
};
return data;
} Happy Coding!
Get in touch with support if you need any help with this!