Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ update-version version:
echo "{{ version }}" > VERSION
perl -pi -e 's|<Version>[.\-\d\w]+</Version>|<Version>{{ version }}</Version>|' src/Stripe.net/Stripe.net.csproj
perl -pi -e 's|Current = "[.\-\d\w]+";|Current = "{{ version }}";|' src/Stripe.net/Constants/Version.cs

[working-directory("src/Examples/")]
run-example example:
dotnet run --project Examples.csproj {{ example }}
9 changes: 4 additions & 5 deletions src/Examples/ExampleTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace Examples
{
using System;
using System.Threading.Tasks;
using Stripe;

/// <summary>
///
Expand All @@ -21,14 +22,12 @@ public class ExampleTemplate
{
public static async Task Run()
{
var apiKey = "{{API_KEY}}";
var apiKey = Environment.GetEnvironmentVariable("STRIPE_API_KEY");

try
{
Console.WriteLine("Hello World");

// var client = new StripeClient(apiKey);
// client.V1...
var client = new StripeClient(apiKey);
Console.WriteLine(await client.V1.Customers.ListAsync());
}
catch (Exception ex)
{
Expand Down
61 changes: 55 additions & 6 deletions src/Examples/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Examples
{
using System;
using System.Reflection;
using System.Threading.Tasks;

public class Program
Expand All @@ -9,14 +10,62 @@ public Program()
{
}

/// <summary>
/// To create an example, clone ExampleTemplate.cs, implement the example
/// copy this line and replace the class name with your new class.
///
/// To run an example from this folder, execute:
/// dotnet run --project Examples.csproj NameOfExample
///
/// The name of the example should include any namespace parts other than "Examples"
/// which is prepended when looking up the type, for example"
/// dotnet run --project Examples.csproj <see cref="ExampleTemplate"/>
/// or
/// dotnet run --project Examples.csproj <see cref="V2.MeterEventStream"/>.
///
/// Examples accept configuration via environment variables, so ensure all environment vars
/// are set before running the example.
///
/// </summary>
/// <param name="args">command line args</param>
/// <returns></returns>
public static async Task Main(string[] args)
{
// To create an example, clone NewExample.cs, implement the example
// copy this line and replace the class name with your new class.
// await NewExample.Run();
// e.g.
// await MeterEventExample.Run();
// then build the project and run bin/Debug/net8.0/Examples
if (args.Length != 1)
{
Console.WriteLine("Usage: dotnet run --project Examples <name_of_example>");
Environment.Exit(1);
return;
}

var type = Assembly.GetExecutingAssembly().GetType($"Examples.{args[0]}");

if (type == null)
{
Console.WriteLine($"Unable to find example class {args[0]}");
Environment.Exit(1);
return;
}

var runMethod = type.GetMethod(
"Run",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
if (runMethod == null)
{
Console.WriteLine($"Example class {args[0]} is missing Run method");
Environment.Exit(1);
return;
}

Task? result = (Task?)runMethod.Invoke(null, null);
if (result == null)
{
Console.WriteLine($"Unable to invoke Run method on {args[0]}");
Environment.Exit(1);
return;
}

await result;
}
}
}
113 changes: 58 additions & 55 deletions src/Examples/V2/MeterEventStream.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,70 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Stripe;
using Stripe.V2.Billing;

/// <summary>
/// Use the high-throughput MeterEventStream to report create billing meter events.
///
/// In this example, we:
/// - create a meter event session and store the session's authentication token
/// - define an event with a payload
/// - use the meterEventStream service to create an event stream that reports this event
///
/// This example expects a billing meter with an event_name of 'alpaca_ai_tokens'. If you have
/// a different meter event name, you can change it before running this example.
/// </summary>
public class MeterEventStream
namespace Examples.V2
{
private static MeterEventSession? meterEventSession;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Stripe;
using Stripe.V2.Billing;

public static async Task Run()
/// <summary>
/// Use the high-throughput MeterEventStream to report create billing meter events.
///
/// In this example, we:
/// - create a meter event session and store the session's authentication token
/// - define an event with a payload
/// - use the meterEventStream service to create an event stream that reports this event
///
/// This example expects a billing meter with an event_name of 'alpaca_ai_tokens'. If you have
/// a different meter event name, you can change it before running this example.
/// </summary>
public class MeterEventStream
{
var apiKey = "{{API_KEY}}";
var customerId = "{{CUSTOMER_ID}}"; // Replace with actual customer ID
private static MeterEventSession? meterEventSession;

try
{
await SendMeterEvent(apiKey, "alpaca_ai_tokens", customerId, "25");
Console.WriteLine("Meter event sent successfully!");
}
catch (Exception ex)
public static async Task Run()
{
Console.WriteLine($"Error sending meter event: {ex.Message}");
}
}
var apiKey = Environment.GetEnvironmentVariable("STRIPE_API_KEY");
var customerId = Environment.GetEnvironmentVariable("CUSTOMER_ID");

private static async Task RefreshMeterEventSession(string apiKey)
{
// Check if session is null or expired
if (meterEventSession == null || meterEventSession.ExpiresAt <= DateTime.UtcNow)
{
// Create a new meter event session in case the existing session expired
var client = new StripeClient(apiKey);
meterEventSession = await client.V2.Billing.MeterEventSession.CreateAsync(new MeterEventSessionCreateOptions());
try
{
await SendMeterEvent(apiKey, "alpaca_ai_tokens", customerId, "25");
Console.WriteLine("Meter event sent successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"Error sending meter event: {ex.Message}");
}
}
}

private static async Task SendMeterEvent(string apiKey, string eventName, string stripeCustomerId, string value)
{
// Refresh the meter event session if necessary
await RefreshMeterEventSession(apiKey);

if (meterEventSession == null)
private static async Task RefreshMeterEventSession(string apiKey)
{
throw new Exception("Unable to refresh meter event session");
// Check if session is null or expired
if (meterEventSession == null || meterEventSession.ExpiresAt <= DateTime.UtcNow)
{
// Create a new meter event session in case the existing session expired
var client = new StripeClient(apiKey);
meterEventSession = await client.V2.Billing.MeterEventSession.CreateAsync(new MeterEventSessionCreateOptions());
}
}

// Create a meter event
var client = new StripeClient(meterEventSession.AuthenticationToken);
var options = new MeterEventStreamCreateOptions
private static async Task SendMeterEvent(string apiKey, string eventName, string stripeCustomerId, string value)
{
Events =
[
new MeterEventStreamCreateEventOptions
// Refresh the meter event session if necessary
await RefreshMeterEventSession(apiKey);

if (meterEventSession == null)
{
throw new Exception("Unable to refresh meter event session");
}

// Create a meter event
var client = new StripeClient(meterEventSession.AuthenticationToken);
var options = new MeterEventStreamCreateOptions
{
Events =
[
new MeterEventStreamCreateEventOptions
{
EventName = eventName,
Payload = new Dictionary<string, string>
Expand All @@ -72,7 +74,8 @@ private static async Task SendMeterEvent(string apiKey, string eventName, string
},
},
],
};
client.V2.Billing.MeterEventStream.Create(options);
};
client.V2.Billing.MeterEventStream.Create(options);
}
}
}
99 changes: 51 additions & 48 deletions src/Examples/V2/ThinEventWebhookHandler.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,63 @@
#pragma warning disable SA1101 // Prefix local calls with this

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Stripe;
using Stripe.Events;

/// <summary>
/// Receive and process thin events like the v1.billing.meter.error_report_triggered event.
///
/// In this example, we:
/// - use parseThinEvent to parse the received thin event webhook body
/// - call StripeClient.v2.core.events.retrieve to retrieve the full event object
/// - if it is a V1BillingMeterErrorReportTriggeredEvent event type, call fetchRelatedObject
/// to retrieve the Billing Meter object associated with the event.
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class ThinEventWebhookHandler : ControllerBase
namespace Examples.V2
{
private readonly StripeClient _client;
private readonly string _webhookSecret;
#pragma warning disable SA1101 // Prefix local calls with this

public ThinEventWebhookHandler()
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Stripe;
using Stripe.Events;

/// <summary>
/// Receive and process thin events like the v1.billing.meter.error_report_triggered event.
///
/// In this example, we:
/// - use parseThinEvent to parse the received thin event webhook body
/// - call StripeClient.v2.core.events.retrieve to retrieve the full event object
/// - if it is a V1BillingMeterErrorReportTriggeredEvent event type, call fetchRelatedObject
/// to retrieve the Billing Meter object associated with the event.
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class ThinEventWebhookHandler : ControllerBase
{
var apiKey = Environment.GetEnvironmentVariable("STRIPE_API_KEY");
_client = new StripeClient(apiKey);

_webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET");
}
private readonly StripeClient _client;
private readonly string _webhookSecret;

[HttpPost]
public async Task<IActionResult> Index()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
public ThinEventWebhookHandler()
{
var thinEvent = _client.ParseThinEvent(json, Request.Headers["Stripe-Signature"], _webhookSecret);
var apiKey = Environment.GetEnvironmentVariable("STRIPE_API_KEY");
_client = new StripeClient(apiKey);

_webhookSecret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET");
}

// Fetch the event data to understand the failure
var baseEvent = await _client.V2.Core.Events.GetAsync(thinEvent.Id);
if (baseEvent is V1BillingMeterErrorReportTriggeredEvent fullEvent)
[HttpPost]
public async Task<IActionResult> Index()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var meter = await fullEvent.FetchRelatedObjectAsync();
var meterId = meter.Id;
var thinEvent = _client.ParseThinEvent(json, Request.Headers["Stripe-Signature"], _webhookSecret);

// Record the failures and alert your team
// Add your logic here
}
// Fetch the event data to understand the failure
var baseEvent = await _client.V2.Core.Events.GetAsync(thinEvent.Id);
if (baseEvent is V1BillingMeterErrorReportTriggeredEvent fullEvent)
{
var meter = await fullEvent.FetchRelatedObjectAsync();
var meterId = meter.Id;

return Ok();
}
catch (StripeException e)
{
return BadRequest(e.Message);
// Record the failures and alert your team
// Add your logic here
}

return Ok();
}
catch (StripeException e)
{
return BadRequest(e.Message);
}
}
}
}