Selling Products with Kentico Cloud: Stripe Integration
When your website or app is live and you have finished final tweaks to your content presentation, it's a great time to implement a store which will allow your visitors to purchase from you.
Josef DvorakPublished on Nov 1, 2017
If you are using a headless CMS like Kentico Cloud, chances are you already have most of the information you need to use it as a lightweight product catalog. This makes your catalog widely available across many channels such as mobile apps, in-store signage, and your website.
Let’s have a look at how you could use Stripe—one of the most popular internet business platforms—to enable purchasing on your website.
We will be using:
- The Sample Dancing Goat project
- The Kentico Cloud Boilerplate
- The Kentico Cloud code generator
- The Stripe.net NuGet package
The Sample Dancing Goat project, available with every new subscription, already contains a Coffee content type that we can use and even has a Price field!
This is great! But, to make our product presentation clearer, let’s add one more field to our store: a Product number.
This way you can customize the product and add additional fields you need for your store. Remember to publish the page when you are finished defining the product.
We will use our sample Boilerplate project for the website. A stock version of the project can be downloaded from our GitHub, but you can also download the finished project used for the example below. The usage instructions are included in the readme file.
Remember to add your project ID to appsettings.json in order to connect the boilerplate project to your cloud project.
When you run the Boilerplate project, you will first see a list of Articles from the cloud project. But to keep it simple, let's change it to show Coffees instead:
We will first need to generate updated code for our Coffee class using the cloud generator and then add the Coffee class to the project:
CloudModelGenerator.exe --projectid "<ProjectGUID>" --namespace "<custom-namespace>" --outputdir "./NewCode"
Once we have added the class to our project and to the list of types:
public class CustomTypeProvider : ICodeFirstTypeProvider
{
public Type GetType(string contentType)
{
switch (contentType)
{
case "article":
return typeof(Article);
case "coffee":
return typeof(Coffee);
default:
return null;
}
}
}
We can modify the HomeController to list Coffees, instead of Articles:
var response = await DeliveryClient.GetItemsAsync<Coffee>(
new EqualsFilter("system.type", "coffee"),
new LimitParameter(3),
new DepthParameter(0),
new OrderParameter("elements.product_name")
);
And set the index view model to Coffee:
@model IEnumerable<Coffee>
Afterwards, we need to create a shared listing template for the Coffee model, which will contain the payment button. Since we already have an Article view in \Views\Shared\DisplayTemplates\Article.cshtml, we can re-use its code to create a new view—Coffee.cshtml for Coffee—by changing the referenced class to Coffee, and the listing markup to a form following the sample from the Stripe documentation:
<form action="/your-server-side-code" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="pk_test_6pRNASCoBOKtIshFeQd4XMUh"
data-amount="2000"
data-name="Stripe.com"
data-description="2 widgets"
data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
data-locale="auto"
data-zip-code="true">
</script>
</form>
From this markup we see that we will need to provide a price for the product so that the form can be processed. This is a good time to future-proof our project a little and to enforce one single way of getting the product price, no matter what the underlying content class is should we decide to sell more than one product in future.
For this purpose, we can define an interface with a single method to return the product price in \ECommerce\Interfaces\IProduct.cs:
public interface IProduct
{
/// <summary>
/// Get product price.
/// </summary>
decimal GetPrice();
}
This way, each content class can have different Price fields, should the need arise. All that we need to do now is implement the method inside a partial class, extending the code of our generated content class Coffee in \Models\Extensions\Coffee.cs:
public partial class Coffee : IProduct
{
/// <summary>
/// Get product price.
/// </summary>
public decimal GetPrice()
{
if (Price == null)
{
throw new Exception("Invalid product price.");
}
return (decimal) Price;
}
}
Since all our products will implement this interface, we can implement additional extension methods for price calculations and formatting in \ECommerce\Extensions\IProductExtensions.cs:
public static class IProductExtensions
{
/// <summary>
/// Price in cents format. I.e. $1.00 => 100
/// </summary>
/// <param name="product">Product which price to get.</param>
/// <returns>Integer representation of price in cents.</returns>
public static int GetPriceInCents(this IProduct product)
{
return Decimal.ToInt32(product.GetPrice() * 100);
}
/// <summary>
/// Formats price based on E-commerce settings price
/// </summary>
/// <param name="product">Product which price to format.</param>
/// <returns></returns>
public static string GetFormatedPrice(this IProduct product)
{
var culture = ECommerceSettings.Culture;
return String.Format(culture, "{0:c}", product.GetPrice());
}
}
Putting together all of the pieces, we can define a form with the following code inside Coffee.cshtml:
<form action="/API/Store/ProcessPayment/@Model.ProductNumber/@Model.GetPriceInCents()/@ECommerceSettings.Currency" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="@ECommerceSettings.StripePublishableKey"
data-amount="@Model.GetPriceInCents()"
data-name="Dancing Goat"
data-description="@Model.ProductName"
data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
data-locale="auto"
data-zip-code="true"
data-currency="@ECommerceSettings.Currency">
</script>
</form>
This way we have ensured that each item in the Coffee listing will have its own payment button, rendering an appropriate payment form using Stripe JavaScript code so we do not need to worry about PCI compliance or client data storage.
But as you can see, there is one last piece missing, which is the action URL. The form itself only ensures that we receive a payment token from Stripe. To claim the payment, we need to run a short server-side code inside a controller to charge the cart. We will use Stripe API Token, submitted via HTTP POST by the Payment form, and the Charge service, defined in the Stripe.net NuGet package. With these two tools we are ready to implement the controller logic in \Controllers\ProcessPaymentController.cs:
[Route("API/Store/ProcessPayment/{number}/{amount}/{currency}")]
public class ProcessPaymentController : Controller
{
[HttpPost]
public void Post(StripePaymentFormData formData, int number, int amount, string currency)
{
var charge = new StripeChargeCreateOptions()
{
Amount = amount,
Currency = currency,
Description = $"Product number: {number}",
SourceTokenOrExistingSourceId = formData.StripeToken
};
var chargeService = new StripeChargeService();
chargeService.Create(charge);
Response.Redirect(Url.LocalContent(Request.Query["return"]));
}
}
You might have noticed a reference to class ECommerceSettings throughout the code. This is a stand-in for business logic dealing with currencies, cultures, and similar. You can find its full code in the final project package. The class itself is initialized in Startup.cs along with the rest of the application, using application secrets:
StripeConfiguration.SetApiKey(Configuration["STRIPE_SECRET_KEY"]);
ECommerceSettings.StripePublishableKey = Configuration["STRIPE_PUBLISHABLE_KEY"];
ECommerceSettings.StripeSecretKey = Configuration["STRIPE_SECRET_KEY"];
With this, the project is ready to process payments. Upon accessing the website we are offered a list of Coffee products:
And can pay for them using a purchase form:
The processed payment will then appear in our Stripe account:
As you can see, we could rapidly re-use existing Kentico Cloud content and integrate with a third-party system to accept payments.
Thanks to this we can keep a centralized location for all our content and Product data in Kentico Cloud, and further expand on the basic purchase functionality with more advanced features.
Go ahead—give the sample project a try!