Trust is earned, not given

A different perspective

2022-10-11 · Projects

Stripe in C#, part 4: Checkout Sessions — the whole payment page, hosted

Part 4from the Stripe in C# series · 6 parts in all

Sometimes the right integration is no integration: Stripe Checkout is a hosted payment page — card fields, wallet buttons, 3DS handling, localization, and PCI scope all outsourced. You create a Session server-side, redirect the browser to Stripe's URL, and receive the customer back at a URL you chose. For MapleCart's store this collapsed two sprints of work into an afternoon.

Creating the session

using Stripe.Checkout;

var sessionService = new SessionService();
var session = sessionService.Create(new SessionCreateOptions
{
    // The mode IS the contract: payment = one-off, setup = save card only,
    // subscription = recurring (Stripe Billing drives it from here).
    Mode = "payment",

    LineItems = new List<SessionLineItemOptions>
    {
        new SessionLineItemOptions
        {
            PriceData = new SessionLineItemPriceDataOptions
            {
                Currency = "usd",
                UnitAmount = 4599,                  // minor units again
                ProductData = new SessionLineItemPriceDataProductDataOptions
                {
                    Name = "Canvas tote bag",
                    Description = "Heavy 16oz cotton, natural",
                },
            },
            Quantity = 2,
        },
    },

    SuccessUrl = "https://maplecart.example/order/thanks?session_id={CHECKOUT_SESSION_ID}",
    CancelUrl  = "https://maplecart.example/cart",
    CustomerEmail = "[email protected]",   // or Customer = cus_... if known
    Metadata = new Dictionary<string, string> { { "orderId", "1042" } },
});
return Redirect(session.Url);   // browser goes to Stripe, comes back after

What you get for free — and the two traps

Free: Apple Pay/Google Pay, card-element UX, 3DS, receipts, tax optional. Trap one: the success redirect is not a receipt — the customer can close the tab before redirecting, so fulfillment must key off checkout.session.completed and/or the PaymentIntent webhook, not off the redirect (part 6 builds the receiver). Trap two: sessions expire (24h default; configurable, minimum 30 minutes) — abandoned sessions are normal and fine.

Verifying the return trip

// On the success page: fetch the session server-side. Never parse the URL
// client-side and trust it - amounts and status are server-side facts.
var s = sessionService.Get(Request.Query["session_id"]);
if (s.PaymentStatus == "paid")
{
    var orderId = s.Metadata["orderId"];   // correlation back to your domain
    ShowThanks(orderId);                   // still: fulfill via webhook only
}

Payment Links: checkout without any code

For a fixed-price product, the dashboard can mint a payment link — a durable URL with the same session semantics, zero code. The session API remains the answer the moment quantity, metadata, or per-order logic enters. Next: the aftermath — refunds, disputes, and invoices.