YabbaDeck.AppBackend 1.4.0

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package YabbaDeck.AppBackend --version 1.4.0
                    
NuGet\Install-Package YabbaDeck.AppBackend -Version 1.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="YabbaDeck.AppBackend" Version="1.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="YabbaDeck.AppBackend" Version="1.4.0" />
                    
Directory.Packages.props
<PackageReference Include="YabbaDeck.AppBackend" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add YabbaDeck.AppBackend --version 1.4.0
                    
#r "nuget: YabbaDeck.AppBackend, 1.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package YabbaDeck.AppBackend@1.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=YabbaDeck.AppBackend&version=1.4.0
                    
Install as a Cake Addin
#tool nuget:?package=YabbaDeck.AppBackend&version=1.4.0
                    
Install as a Cake Tool

YabbaDeck.AppBackend

The platform half of an app backend on the YabbaDeck platform: the mirror rows for the platform's users and entitlements, the consumers that keep them in step, the entitlement gate an app protects its own features with, and the reconciliation read that makes a dropped event a moment of staleness rather than a lost purchase.

Pair it with YabbaDeck.AppBackend.AspNetCore in a web host. This half has no ASP.NET Core dependency, so a worker or a message handler can take it alone.

Why it is a package

Every service generated from YabbaDappBackendTemplate used to carry this code as a copy. A fix made here had to be made again by hand in each of them, by somebody who knew to — and a service generated before a feature existed never got it at all. A version bump is what this package buys.

What it gives you

User The mirror row. Three fields, and the id is the platform's — supplied, never generated. It is the hook your app hangs its own columns and foreign keys on.
Entitlement What somebody bought. ExpiresAt is nullable and null means perpetual — never store this as a boolean.
IUserSyncService Keeps the mirror user in step. Used by the lifecycle consumers and by your protected endpoints, so both have one idea of what idempotent means.
IEntitlementService The gate. HasActiveAsync(userId, productId) is what an app checks before unlocking a feature.
IPlatformEntitlementsClient The reconciliation read. null means "the platform did not answer" — never an empty list.
IPlatformCheckoutClient Opens a checkout, which a client cannot do — the request carries the price.
IPlatformSubscriptionsClient Starts, cancels and re-plans a subscription (1.3.0). Renewals need nothing from you: they arrive as the same EntitlementGranted, extended.
Four consumers UserVerified, UserRemoved, EntitlementGranted, PaymentCompleted. Each drops what is not addressed to this app.

Wiring it up

// 1. Your DbContext takes the platform's tables. They stay in YOUR model, so your own tables can
//    hold a foreign key to User — which is the whole reason that row exists.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyYabbaDeckPlatform();
    // ... your own configurations
}

// 2. Registration. The type parameter is your context.
services.AddYabbaDeckAppBackend<MyDbContext>(configuration);

// 3. The consumers go on YOUR bus, beside your own, so you keep the endpoint name formatter.
services.AddMassTransit(bus =>
{
    bus.SetEndpointNameFormatter(new KebabCaseEndpointNameFormatter(prefix: "my-service", includeNamespace: false));
    bus.AddYabbaDeckConsumers();
    bus.UsingRabbitMq((context, cfg) => { /* ... */ cfg.ConfigureEndpoints(context); });
});

The queue-name prefix is not cosmetic. Every app backend on the platform shares one RabbitMQ virtual host, because that is the only way the platform's events reach any of them. Two services declaring a plain user-verified queue would be competing consumers on one queue — each event reaching exactly one of them, silently, looking for all the world like a flaky broker.

Opening a checkout

Your backend is the seller, so your backend opens the sale:

var checkout = await platformCheckout.OpenAsync(new PlatformCheckoutRequest(
    UserId: userId,
    ProductId: "premium",
    AmountMinor: 4900,                    // 49.00 kr — minor units, never a decimal
    Currency: "SEK",
    ReturnUrl: $"myapp://payments/return?order={orderId}",   // YOUR reference — see below
    CancelUrl: "myapp://payments/cancel",
    IdempotencyKey: orderId));            // a double-tapped button is one payment

The price is in the request, and that is the whole reason this call exists here rather than in your app. The platform holds no catalogue and cannot derive an amount from a product string, so the seller states it — over a machine credential the customer never sees. A client naming its own price is a free-money bug.

The catalogue stays yours. This package takes an amount and sends it; what anything costs is your decision, and a package that shipped a catalogue would be a package with an opinion about your business.

The return URL cannot carry the platform's payment id. The id is minted while your request is being handled, so it does not exist when you build the URL — and the platform then redirects there verbatim, appending nothing of its own.

Usually nothing needs it to. PaymentId comes back from this call before the customer ever leaves for the hosted page, so hand it to your client and let it watch; the return link only has to mean "they are back". When you do need the payment named in the link — a cold start after the app was killed mid-payment — either put your own reference in the URL and map it afterwards, or point the URL at your own backend, which knows the mapping by then and can redirect on to your deep link with the payment id in it.

Failures are a PlatformCheckoutException whose Reason distinguishes the four things that actually go wrong — no credential, a credential not granted payments:checkout, a request the platform will not take, and no gateway selected for the app — because each is fixed by a different person.

It needs payments:checkout on top of entitlements:read. The token request asks for no scope and takes what your client is granted: asking for both would fail outright in a deployment granted only one, and take entitlement reconciliation down with it. A missing grant shows up as a clear failure on the call that needed it.

Subscriptions

Since 1.3.0. The payment provider bills every renewal on its own schedule; your backend starts the subscription, and can cancel it or move it to another plan:

var checkout = await platformSubscriptions.OpenAsync(new PlatformSubscriptionRequest(
    UserId: userId,                        // required: a subscription keeps somebody's access alive
    ProductId: "premium-monthly",
    AmountMinor: 4900,                     // per period, in minor units — your price, as at checkout
    Currency: "SEK",
    Interval: PlatformBillingInterval.Month,
    IntervalCount: 1,
    ReturnUrl: "myapp://subscriptions/return",
    CancelUrl: "myapp://subscriptions/cancel",
    IdempotencyKey: orderId));

// Keep checkout.SubscriptionId — it is what you cancel or change it by, for the user it belongs to.
await platformSubscriptions.CancelAsync(subscriptionId, userId);                       // at the end of the paid period
await platformSubscriptions.CancelAsync(subscriptionId, userId, atPeriodEnd: false);   // now — access ends at once
await platformSubscriptions.ChangePlanAsync(subscriptionId, userId,
    new PlatformSubscriptionPlan("premium-yearly", 49000, "SEK", PlatformBillingInterval.Year));

userId is the user your backend is acting for: from their token, never from the request (1.4.0). The platform touches only a subscription that belongs to them, and anybody else's fails with NotFound, as one that does not exist does. A subscription id reaches you from a client, and nothing in your database says whose it is, so this is what stops one of your users canceling another's. 1.3.0's calls took no user.

Your entitlement mirror already does the rest. Access is an entitlement like any other. A paid renewal republishes EntitlementGranted with the same id and a later expiry; a failed renewal extends nothing, so access lapses at the end of the last paid period without any event; an immediate cancellation arrives as EntitlementRevoked. The subscription events in YabbaDeck.Platform.Contracts 1.3.0 are there for what else a renewal means to you, not for gating.

Nothing is refunded by a cancellation. A plan change takes effect from the next period, with no proration; if the product changes, the old one's access runs to the end of what was paid for and the new one is granted once its first period is paid.

Canceling and changing need subscriptions:manage on top of payments:checkout, because they take access away. A deployment granted only the first gets a NotPermitted failure that names the scope, with everything else working. Failures are a PlatformSubscriptionException whose Reason tells a missing credential, a missing grant, a rejected request, an app that cannot take payments, an unknown subscription and one that is no longer live apart.

The migrations are yours

The entities are mapped into your context, so dotnet ef migrations add runs in your project against your database. A version of this package that changes the model costs you a migration — visible, in your own diff, on your own schedule. That is the price of the foreign key, and it is the right way round.

Configuration

{
  // Which app this is on the platform, and which platform. No secret: the platform signs tokens with
  // RS256 and publishes the public half, so onboarding a backend means handing out nothing.
  "YabbaDeckAuth": {
    "AppId": "my-app",
    "Authority": "https://yabbadeck.example.com",
    "Issuer": "yabbadeck-bff"
  },

  // Optional. Empty means no reconciliation and no checkout — the events are then the only way an
  // entitlement arrives. Configuring one of the two and not the other fails startup naming the key.
  // The platform issues these: YabbaProv does it when it provisions your app, or an administrator
  // does it in the Back Office (PUT api/app-backend-client). The secret is shown once — store it
  // then. A lost one is replaced by issuing again, which rotates it; the client id never changes.
  "YabbaDeckClient": {
    "ClientId": "",
    "ClientSecret": ""
  }
}

Two properties worth keeping

  • null from the platform client is not an empty list. Null means it did not answer — not configured, unreachable, refused, or asked again too soon — and your mirror is left as it was. Collapsing the two is how an outage becomes every customer losing what they bought.
  • The gate re-reads only when it is about to say no. A yes is already the platform's own answer. The consequence is that a revocation reaches the gate on the next list or when the row lapses; nothing on the wire revokes an entitlement yet.

MIT licensed. Part of the YabbaDeck platform.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated