How to implement prepaid credit packs in C# with Subscrio
SliceFoundry prepares 3D models for printing. Customers can use the application without a recurring paid plan, then buy a pack of 500 credits when they need slicing work. Each slice costs five credits. A repeated payment notification must not grant another pack.
In this guide we will implement prepaid credit packs in C#, record one confirmed purchase, and spend credits with separate keys for the order and the slicing job.
A credit top-up grants spendable application credits after a one-time purchase. The payment provider confirms the order before this example begins. Separate idempotency keys protect the top-up and each later job charge.
Define the customer promise
| Operation | Credit change |
|---|---|
| Confirmed prepaid order | +500 |
| Repeated delivery of that order | No new grant |
| One slicing job | −5 |
| Retry of that job | No new debit |
Run the example
The complete SliceFoundry sample contains the setup and executable assertions used below. Use Windows with the .NET SDK and SQL Server Express LocalDB. The runner connects with Windows integrated authentication, creates an isolated database, and removes it when finished.
git clone https://github.com/subscrio/samples.git
cd samples/examples/prepaid-credit-packs-csharp
sqllocaldb start MSSQLLocalDB
dotnet restore --locked-mode
dotnet run --no-restore
The snippets run in order within that program. Check prints the returned value and asserts that it matches the expected result. The outputs below come from the executable sample.
Initialize Subscrio
The database helper supplies this connection-string form. Select the SQL Server provider and install the schema before creating the catalog.
Server=(localdb)\MSSQLLocalDB;Database=SubscrioBlog_<generated name>;Integrated Security=true;TrustServerCertificate=true
var clock = new DemoClock();
using var app = new Subscrio.Core.Subscrio(
new SubscrioConfig
{
Database = new DatabaseConfig
{
ConnectionString = database.ConnectionString,
DatabaseType = DatabaseType.SqlServer
},
Clock = clock
}
);
await app.InstallSchemaAsync();
The full runner supplies the imports, database helper, and cleanup. DemoClock is a sample helper implementing Subscrio’s IClock; setting its UtcNow property changes the time used by the checks below. The clock uses the current time throughout this example.
Create the product
SliceFoundry’s catalog groups the purchase with the capability it controls.
await app.Products.CreateProductAsync(new("slicefoundry", "SliceFoundry"));
Define the entitlement
The slice toggle decides whether slicing is available. Credits will control how much paid slicing work the customer can perform.
await app.Features.CreateFeatureAsync(new("slice", "slice", "toggle", "false"));
Associate the feature
Make this feature available to SliceFoundry’s plans.
await app.Products.AssociateFeatureAsync(
"slicefoundry",
"slice",
new FeatureResolutionOptions(SubscriptionRule: "most_generous")
);
Create the plan
Create the Application access plan so customers can use slicing once they have enough prepaid credits. The plan itself does not grant a credit balance.
await app.Plans.CreatePlanAsync(
new("slicefoundry", "standard", "Application access")
);
Set the included value
The ongoing application plan enables slicing; it does not include or automatically grant credits.
await app.Plans.SetFeatureValueAsync("standard", "slice", "true");
Create the billing cycle
A forever cycle represents access with no scheduled renewal. It does not initiate a payment.
await app.BillingCycles.CreateBillingCycleAsync(
new("standard", "standard-cycle", "Ongoing", "forever")
);
Create the customer
The sample uses the key customer for the SliceFoundry purchaser. Creating this record identifies the recipient; the subscription will assign their plan.
await app.Customers.CreateCustomerAsync(new("customer", "SliceFoundry customer"));
Give the customer a subscription
The agreement subscription connects the customer to the ongoing Application access plan through standard-cycle. It enables slicing but grants no credits; the paid pack will fund the wallet separately.
await app.Subscriptions.CreateSubscriptionAsync(
new("agreement", "customer", "standard-cycle", ActivationDate: clock.UtcNow)
);
Define slicing credits
The free ongoing agreement supplies feature access. The prepaid wallet pays for individual slices.
await app.Credits.CreateCurrencyAsync(new("slice-credits", "Slicing credits"));
Set the cost per job
One slicing job consumes five credits.
await app.Credits.SetConsumptionRuleAsync("slice", "slice-credits", 5);
Fulfill the prepaid credit top-up once
This input comes from a confirmed-payment fixture. Use the stable paid order identifier as the grant key so the same purchase cannot be granted twice.
var pack = new CreditGrantInput(
"customer",
"slice-credits",
500,
"prepaid",
"paid-order-500"
);
await app.Credits.GrantAsync(pack);
await app.Credits.GrantAsync(pack);
Check(
"Credits after payment replay",
(await app.Credits.GetBalanceAsync("customer", "slice-credits")).Available,
500L
);
Output:
Credits after payment replay: 500
Consume prepaid credits for a job
Check feature access, then consume one priced unit. A job identifier is different from the purchase identifier because these are different operations.
if (
!await app.FeatureChecker.GetValueForCustomerAsync<bool>(
"customer",
"slicefoundry",
"slice",
false
)
)
throw new InvalidOperationException("Slicing is not included");
var slice = new CreditConsumeInput("customer", "slice", 1, "slice-model-bracket");
await app.Credits.ConsumeAsync(slice);
Check(
"Credits after slicing",
(await app.Credits.GetBalanceAsync("customer", "slice-credits")).Available,
495L
);
Output:
Credits after slicing: 495
Retry the job without charging twice
Replaying the consumption key preserves the first debit. This protects the credit charge; your worker must also avoid repeating the external slicing operation.
await app.Credits.ConsumeAsync(slice);
Check(
"Credits after job retry",
(await app.Credits.GetBalanceAsync("customer", "slice-credits")).Available,
495L
);
Output:
Credits after job retry: 495
Use the result in your application
The order key protects the 500-credit grant; the job key protects the five-credit charge. They remain separate because buying capacity and spending it are separate operations. SliceFoundry ends with 495 credits after both retries, with no paid recurring subscription involved.
Run the complete SliceFoundry example to reproduce the checks. The Subscrio credit entitlements guide explains the related model.
For a related TypeScript example, see prepaid rendering jobs in TypeScript. A related next step is monthly credit grants in C#.