79505680

Date: 2025-03-13 07:27:16
Score: 0.5
Natty:
Report link

I was totally confounded how difficult it was to pass metadata for both subscriptions and payments, so I'm posting some C# in hopes it may provide some illumination. Here, invoices are being created for both subscriptions and payments, and provisioning happens only in invoice.paid. Search for metadata.

First create the checkout session:

[Authorize]
[HttpPost("create-checkout-session/{id}")]
public async Task<IActionResult> CreateCheckoutSession(string id)
{
this.options.Value.Domain = $"https://{this.ControllerContext.HttpContext.Request.Host}";
var email = Account.Email;

var price = prices.First(prices => prices.Id == id);
if (price == null)
{
    return BadRequest();
}

var priceId = price.PriceId;
var mode = price.Mode;
var modeStr = (mode == PurchaseMode.Subscription ? "subscription" : "payment");
var metadata = new Dictionary<string, string>()
    {
        { "priceId", priceId },
        { "accountId", Account.Id},
        { "domains", domains }
    };

var options = new Stripe.Checkout.SessionCreateOptions
{
    SuccessUrl = $"{this.options.Value.Domain}/payday/ok",
    CancelUrl = $"{this.options.Value.Domain}/payday/cancel",
    Mode = modeStr,
    LineItems = new List<SessionLineItemOptions>
    {
        new SessionLineItemOptions
        {
            Price = priceId,
            Quantity = 1,
        },
    },
    Metadata = metadata,
};

if (mode == PurchaseMode.Payment)
{
    options.InvoiceCreation = new SessionInvoiceCreationOptions()
    {
        InvoiceData = new SessionInvoiceCreationInvoiceDataOptions() { Metadata = metadata },
        Enabled = true,
    };
    options.PaymentIntentData = new SessionPaymentIntentDataOptions() { Metadata = metadata };
}
else if (mode == PurchaseMode.Subscription)
{
    options.SubscriptionData = new SessionSubscriptionDataOptions() { Metadata = metadata };
}


// Blech.  Can't set both email and customerId, so pick one.
if (Account?.StripeInfo?.CustomerId != null)
{
    // already has an account, so use it!
    options.Customer = Account.StripeInfo.CustomerId;
}
else
{
    // we'll be creating a new account.
    options.CustomerEmail = email;
    options.CustomerCreation = (mode == PurchaseMode.Payment) ?
        "always" : "if_required";
}

var service = new Stripe.Checkout.SessionService(this.client);
...

Then, in the webhook, fetch out the metadata (WHICH CONFUSINGLY IS DIFFERENT FOR SUBSCRIPTIONS vs PAYMENTS in invoice.paid!)

// This webhook is called by Stripe as a customer is established, and then for Subscription and invoicing events

[HttpPost("webhook")]
public async Task<IActionResult> Webhook()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
Event stripeEvent;
try
{
    stripeEvent = EventUtility.ConstructEvent(
        json,
        Request.Headers["Stripe-Signature"],
        this.options.Value.WebhookSecret,
        throwOnApiVersionMismatch: false
    );
    Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}");
}
catch (Exception e)
{
    _logger.LogError(e, e.Message);
    return BadRequest();
}

string accountId = null;  // our account id
string domains = null;    // comma separated list of domains
Stripe.Customer customer = null;
Stripe.Invoice invoice = null;
Stripe.Checkout.Session session = null;
string customerId = null;  // Stripe customer id
string email = null;
string mode = null; //  payment, setup, or Subscription.
string priceId = null;
string invoiceId = null;
string subscriptionId = null;
string subscriptionStatus = null; // incomplete, incomplete_expired, trialing, active, past_due, canceled, or unpaid.

switch (stripeEvent.Type)
{
    case "customer.created":
        customer = stripeEvent.Data.Object as Stripe.Customer;
        customerId = customer.Id;
        email = customer.Email;
        break;

    case "invoice.paid":
        // Continue to provision the Subscription as payments continue to be made.
        // Store the status in your database and check when a user accesses your service.
        // This approach helps you avoid hitting rate limits.
        invoice = stripeEvent.Data.Object as Stripe.Invoice;

        // HERE'S THE TRICKY PART
        if (invoice?.SubscriptionDetails?.Metadata != null)
        {
            invoice.SubscriptionDetails.Metadata.TryGetValue("priceId", out priceId);
            invoice.SubscriptionDetails.Metadata.TryGetValue("accountId", out accountId);
            invoice.SubscriptionDetails.Metadata.TryGetValue("domains", out domains);
        }
        else
        {
            invoice.Metadata.TryGetValue("priceId", out priceId);
            invoice.Metadata.TryGetValue("accountId", out accountId);
            invoice.Metadata.TryGetValue("domains", out domains);
        }

SubscriptionDetails.Metadata!!!??? What?

Reasons:
  • Blacklisted phrase (1): ???
  • Long answer (-1):
  • Has code block (-0.5):
  • Ends in question mark (2):
  • High reputation (-1):
Posted by: Jay Borseth