In this post I will show how we added Stripe Express Checkout into our ASP.NET Core 8 e-commerce store at abelectronics.co.uk.

When we setup our e-commerce store in 2012 we first used PayPal then Secure Trading as an online payment provider to accept credit cards and debit cards. This also required a merchant account with our bank which incurs additional monthly fees.

This has been working for many years but over the past few months we noticed an increase in issues with payments not being processed. This has resulted in the loss of customers so we decided it was time to move to a different payment provider who also offers additional express checkout options.

We had previously used Stripe on other e-commerce websites for our clients but these have all been ASP.NET C# websites using webforms so we couldn’t reuse the code on our ASP.NET Core website.

The code below is for a ASP.NET Core 8 website project.

The code does not include the database functions to save your customer and order details, you will need to add this from your existing ecommerce software.

Add a Cart cshtml page to the project with will also add a Cart.cs file:

  • Cart.cshtml
  • Cart.cs

Controllers:

  • StripeController.cs
  • StripeReturnController.cs

Step 1: Adding the NuGet Stripe Library

We need to add the Stripe library to our project from NuGet using the NuGet Package Manager.

In Visual Studio, go to Tools > NuGet Package Manager > Manage NuGet Packages for Solution. Search for stripe.net and install the latest version.

Step 2: Save your Secret key and Public key from your Stripe dashboard

In this code the values are hard coded but it is better to store the keys in your appsettings.json file so they can easily be changed between test and live codes.

Cart Page Code

Step 1: Cart.cs

In Cart.cs, add public variables which will be used to add variables into the JavaScript for the button creation

public long StripePaymentTotal = 0;
public string StripeClientSecret = "";
public string StripePaymentId = "";

Step 2: Cart.cs OnGet()

In the OnGet() function we assign a value for the order into the StripePaymentTotal variable as a long so a value of £9.99 would be 999, we then create a Stripe PaymentIntent for the customer.

StripeConfiguration.ApiKey = “sk_xxxxxxxxxxxx”; // replace with your secret key
StripeConfiguration.ClientId = “pk_xxxxxxxxxxx”; // replace with your public key

var options = new PaymentIntentCreateOptions
{
    Amount = StripePaymentTotal,

    Currency = "gbp",
  
    AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
    {
        Enabled = true,
    },
};
var service = new PaymentIntentService();
PaymentIntent intent = service.Create(options);

StripeClientSecret = intent.ClientSecret;
StripePaymentId = intent.Id;

This will create a client secret for the PaymentIntent and ID value which is used to track the transaction.

Step 3: Cart.cshtml

In the Cart.cshtml file add the following code in the head section:

<script src="https://js.stripe.com/v3/"></script>

Step 4: Cart.cshtml

Add the buttons placeholder in the html of your page.

<div id="express-checkout-element">
    <!-- Express Checkout Element will be inserted here -->
</div>
<div id="error-message">
    <!-- Display an error message to your customers here -->
</div>

Step 5: Add script tags

Add script tags at the end of your page and we will add JavaScript code to interface with the Stripe JS library.

<script>

</script>

Step 6: Create stripe object

Add the following code between the script tags which will create the Stripe object and create an elements object containing the client secret, options and appearance.

 const stripe = Stripe('pk_xxxxxxxxxxx”'); // replace with your public key
          
 const options = {
     layout: {
         overflow: 'never', // delete to allow the buttons to show with a "more" link
         maxColumns: 1 // delete for automatic columns
     },
 }

 const elements = stripe.elements({
     clientSecret: '@Model.StripeClientSecret',
     appearance: {
         theme: 'stripe',
         variables: {
             borderRadius: '36px',
             spacingUnit: '13px',
         }
     }, 
 });

Step 7: Add expressCheckoutElement

Next we create the expressCheckoutElement and mount the buttons onto the html div on the page.

const expressCheckoutElement = elements.create("expressCheckout", options);
expressCheckoutElement.mount("#express-checkout-element");

Step 8: Show or hide button div

Use the following to check if the buttons can be shown or hide the div if there are not any payment methods available:

const expressCheckoutDiv = document.getElementById('express-checkout-element');
expressCheckoutDiv.style.visibility = 'hidden';

expressCheckoutElement.on('ready', ({ availablePaymentMethods }) => {
    if (!availablePaymentMethods) {
        // No buttons will show
    } else {
        expressCheckoutDiv.style.visibility = 'initial';
    }
});

Step 9: Handle Click Event

The Click event allows us to set additional parameters to request the customers email and shipping address, in this example we wll also set the allowed Shipping Countries to GB and US and add a standard Shipping Rate.

expressCheckoutElement.on('click', (event) => {
    const options = {
        emailRequired: true,
        shippingAddressRequired: true,
        allowedShippingCountries: [ 'GB', 'US'],
        shippingRates: [
            {
                id: 'standard-shipping',
                displayName: 'Standard shipping',
                amount: 0,
                deliveryEstimate: {
                    maximum: { unit: 'day', value: 14 },
                    minimum: { unit: 'day', value: 1 }
                }
            },
        ]
    };
    event.resolve(options);
});     

Step 10: Handle Shipping Address Change

To check for shipping address changes and to get shipping costs from the server we will use the following function:

expressCheckoutElement.on('shippingaddresschange', async (event) => {
    const shippingAddress = event.address;

    const response = await fetch('/stripe-get-total', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            country: shippingAddress.country,
            postalCode: shippingAddress.postalCode,
            id: '@Model.StripePaymentId',
        }),
    });

    const { newTotalAmount, newShippingAmount, newClientID, shippingOptions, _isValidCountry } = await response.json();
    if (_isValidCountry) {
        elements.update({ clientSecret: newClientID });
        event.resolve({});
    } else {
        event.reject({});
    }
});

This uses the /stripe-get-total controller to check for shipping costs and return data to the cart page.

Step 11: Handle confirm event

The confirm event is returned after a payment attempt has been made which is either successful or fails.

This uses the /stripe-save-order controller to verify the payment server side and either shows an error message or a confirmation message. You can also redirect to a confirmation page if required.

expressCheckoutElement.on('confirm', async (event) => {
const { error } = await stripe.confirmPayment({
        elements,
        clientSecret: '@Model.StripeClientSecret',
         redirect: 'if_required',
    });

    if (error) {
        document.getElementById("stripe-error-message").innerHTML = '<div class="alert alert-danger" role="alert">There was an error with your payment, please try a different payment method.</div>';
    } else {
        // Good Order
        return fetch('/stripe-save?paymentid=@Model.StripePaymentId', {
            method: 'POST', 
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(event)
        }).then(function (res) {
            if (!res.ok) {
                // server verification failed
                document.getElementById("stripe-error-message").innerHTML = '<div class="alert alert-danger" role="alert">There was an error with your payment, please try a different payment method.</div>';
            } else {
                document.getElementById("stripe-error-message").innerHTML = '<div class="alert alert-success" role="alert">Thank you for your order.</div>';
            }
        });
    }
});

Controllers Code

Step 1: StripeController.cs code

The StripeController contains the code to handle shippingaddresschange events from the client side page.

using Microsoft.AspNetCore.Mvc;
using Stripe;
using System.Text.Json;

namespace yournamespace.Pages.api
{
    public class StripeController : Controller
    {
        private bool _isValidCountry;

        [HttpPost("/stripe-get-total")]
        public async Task CalculateTotal([FromBody] ShippingCountryModel jsonData)
        {
            var jsonString = JsonSerializer.Serialize(jsonData);

            string safePaymentID = StringUtils.SanitiseInput(jsonData.id!);
            if (jsonData != null)
            {
                // check your database to see if the country code submitted is in your active delivery list
                using YourDBContext dbContext = new();
                var countries = dbContext.ShopCountries.FirstOrDefault(post => post.ShortCode == jsonData.country);
                if (countries != null)
                {
                    if (countries.IsActive == 1)
                    {
                        _isValidCountry = true;
                    }
                    else
                    {
                        _isValidCountry = false;
                    }
                }
            }

            // now we get the new order total and shipping costs

            var newClientID = "";
            long newTotalAmount = 1000; // Get your new order total from your cart code
            long newShippingAmount = 1000; // Get your new shipping total from your cart code
            if (_isValidCountry)
            {
                // update payment intent with new price 
                StripeConfiguration.ApiKey = “sk_xxxxxxxxxxxx”; // replace with your secret key
                StripeConfiguration.ClientId = “pk_xxxxxxxxxxx”; // replace with your public key
                var options = new PaymentIntentUpdateOptions
                {
                    Amount = newTotalAmount,
                };
                var service = new PaymentIntentService();
                var paymentIntent = await service.UpdateAsync(jsonData!.id, options);

                newClientID = paymentIntent.ClientSecret;
            }

            // Define shipping options if needed
            var shippingOptions = new[]
            {
                new { id = "standard-shipping", label = "Standard Shipping", detail = "Royal Mail Shipping", amount = 0 }
            };

            return Ok(new { newTotalAmount, newShippingAmount, newClientID, shippingOptions, _isValidCountry });
        }
    }
}

Step 2: StripeReturnController.cs code

The return controller processes the response from Stripe to confirm or fail the order and contains the customers delivery address which needs to be added to your order.

using Microsoft.AspNetCore.Mvc;
using Stripe;
using System.Text.Json;

namespace yournamespace.Pages.api
{
    public class StripeReturnController() : Controller
    {
        [HttpPost("/stripe-save")]
        public async Task CalculateTotalAsync(string paymentid, string cart, [FromBody] StripeDecode.Root jsonData)
        {
            string cartId = cart;
            var jsonString = JsonSerializer.Serialize(jsonData);

            StripeConfiguration.ApiKey = "sk_xxxxxxxxxxxx"; // replace with your secret key
            StripeConfiguration.ClientId = "pk_xxxxxxxxxxx"; // replace with your public key

            var service = new PaymentIntentService();
            var paymentIntent = await service.GetAsync(paymentid);

            var paymentstatus = paymentIntent.Status.ToString();

            if (paymentstatus == "succeeded")
            {
                // good payment
                if (jsonData != null)
                {
                    // update your customer data with their shipping address:
                    // Name: jsonData.shippingAddress!.name!;
                    // Email: jsonData.billingDetails!.email!;
                    // Address Line 1: jsonData.shippingAddress!.address!.line1!
                    // Address Line 2: jsonData.shippingAddress!.address!.line2!
                    // Address City: jsonData.shippingAddress.address.city!;
                    // Address State: jsonData.shippingAddress.address.state!;
                    // Address Post code: jsonData.shippingAddress.address.postal_code!;
                    // Address Country: jsonData.shippingAddress.address.country!;

                    // save the order
                    return Ok();
                }
                else
                {
                    return BadRequest();
                }
            }
            else
            {
                // failed order
                return BadRequest();
            }
        }
    }

    public class StripeDecode
    {
        public class Address
        {
            public string? line1 { get; set; }
            public string? line2 { get; set; }
            public string? city { get; set; }
            public string? state { get; set; }
            public string? postal_code { get; set; }
            public string? country { get; set; }
        }

        public class BillingDetails
        {
            public string? name { get; set; }
            public Address? address { get; set; }
            public string? email { get; set; }
        }

        public class Root
        {
            public string? elementType { get; set; }
            public string? expressPaymentType { get; set; }
            public BillingDetails? billingDetails { get; set; }
            public ShippingAddress? shippingAddress { get; set; }
            public ShippingRate? shippingRate { get; set; }
        }

        public class ShippingAddress
        {
            public string? name { get; set; }
            public Address? address { get; set; }
        }

        public class ShippingRate
        {
            public string? id { get; set; }
            public string? displayName { get; set; }
            public int? amount { get; set; }
        }
    }


}

You can now build the project and test the code which will add payment buttons to your page and the controllers will get the shipping costs and save the order.