Trust is earned, not given

A different perspective

2019-08-20 · Projects

Stripe in C#, part 1: keys, Stripe.net, and your first charge

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

This series assumes you have never touched Stripe. By the end of part 1 you will have a test-mode charge running from a C# console app, and — more importantly — the mental model for everything that follows: keys, the test card numbers, and the Charges-vs-PaymentIntent transition that is reshaping the whole platform in 2019.

Keys: four strings that define your world

Every Stripe account (test and live) has a publishable key (pk_test_...) meant for browsers and phones, and a secret key (sk_test_...) meant for servers. The secret key is a password that can move money — it never ships to a client, ever. Test mode is a complete parallel universe with its own dashboard, its own test cards, and no real money; live mode differs by one letter in the key prefix and one giant legal reality.

dotnet add package Stripe.net
using Stripe;

// Configure once at startup (in ASP.NET Core, do this in ConfigureServices).
// The SDK pins an API version for you; pin the package version in source control.
StripeConfiguration.ApiKey = "sk_test_51ABC...";

// The oldest, simplest money move: a one-off charge on a raw card token.
// "tok_visa" is a magic test token that stands in for a card from Stripe.js.
var options = new ChargeCreateOptions
{
    Amount = 2099,               // minor units! $20.99 is 2099, never a float
    Currency = "usd",            // ISO 4217, lowercase
    Description = "MapleCart order 1042",
    Source = "tok_visa",         // token created client-side by Stripe.js
    Metadata = new Dictionary<string, string>   // free-form, shows in dashboard
    {
        { "orderId", "1042" }    // your keys, your correlation IDs
    }
};
var service = new ChargeService();
Charge charge = service.Create(options);
Console.WriteLine($"{charge.Id} {charge.Status}");   // ch_... succeeded

The three rules beginners trip on

  1. Amounts are integers in minor units. Cents for USD. Using a decimal field is the classic double-charge bug.
  2. Idempotency keys exist and you should use them. A network timeout does not mean the request failed — it means the request is unknown. Retry with the same idempotency key and Stripe replays the original response instead of charging twice: service.Create(options, new RequestOptions { IdempotencyKey = "order-1042" });
  3. Test cards are documentation, not data. 4242 4242 4242 4242 always succeeds; 4000 0000 0000 3220 forces 3D Secure; any future expiry, any CVC.

2019's transition: Charge → PaymentIntent

Everything above works — and is already legacy. PSD2 in Europe made strong customer authentication (3DS challenges) mandatory, and a Charge has nowhere to put an authentication step. The PaymentIntent (API version 2019-03-14 onward) is the object that tracks a payment through its lifecycle, including "waiting for the customer to authenticate". All new integrations should start there; part 2 is entirely about it.

Next: the PaymentIntent state machine, server and client sides, and how 3DS fits without breaking your checkout.