YabbaDeck.Platform.Contracts 1.3.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.Platform.Contracts --version 1.3.0
                    
NuGet\Install-Package YabbaDeck.Platform.Contracts -Version 1.3.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.Platform.Contracts" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="YabbaDeck.Platform.Contracts" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="YabbaDeck.Platform.Contracts" />
                    
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.Platform.Contracts --version 1.3.0
                    
#r "nuget: YabbaDeck.Platform.Contracts, 1.3.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.Platform.Contracts@1.3.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.Platform.Contracts&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=YabbaDeck.Platform.Contracts&version=1.3.0
                    
Install as a Cake Tool

YabbaDeck.Platform.Contracts

The public message contracts of the YabbaDeck app platform — the events an app backend consumes to stay in step with the platform that owns its users and takes its money: the user lifecycle, and since 1.1.0 payments and entitlements.

Dependency-free by design. This package has no dependencies at all, and never will: it is restored by every app backend in the ecosystem, and anything pulled in here becomes a version conflict somebody else has to resolve.

You probably want the AppBackend packages instead

This package is the wire format. If you are building an app backend on .NET, take YabbaDeck.AppBackend — it consumes every event below, keeps the mirror rows, and gives you the entitlement gate and the reconciliation read, all with the rules on this page already applied. Add YabbaDeck.AppBackend.AspNetCore for the token validation, the client meta-headers and the health endpoints.

modelBuilder.ApplyYabbaDeckPlatform();                                  // your DbContext
services.AddYabbaDeckAppBackend<MyDbContext>(configuration);            // mirrors, gate, reconciliation
services.AddMassTransit(bus => bus.AddYabbaDeckConsumers());            // on your own bus

Read on if you are writing a consumer by hand — because you are on another stack, another language, or you want to know exactly what those packages do.

The events

All four carry the same three values and live in YabbaDeck.Platform.Contracts.Users:

Event Published when
UserVerified Someone followed the verification link in their mail and the account became active.
UserDisabled An operator suspended the account. Reversible.
UserEnabled A suspension was lifted.
UserRemoved The account was hard-deleted (right to erasure). Delete your mirror row.
public sealed record UserVerified(string AppId, Guid UserId, DateTimeOffset OccurredAt)
    : IUserLifecycleEvent;
  • AppId is the app's public id — the slug your backend knows itself by from its own configuration (the same value clients send as X-App-Id), never a YabbaDeck primary key.
  • UserId is the shared GUID. YabbaDeck mints it; your mirror row uses it as its own primary key. Never generate one on your side — supplied or nothing.
  • IUserLifecycleEvent carries the three common values, so one filter can cover the whole set.

The payment events

Since 1.1.0, in YabbaDeck.Platform.Contracts.Payments:

Event Published when
PaymentCompleted Money was captured for one of your users.
EntitlementGranted Somebody is now entitled to something — this is usually the one to act on.

IPaymentEvent carries the two common values (AppId, OccurredAt) so one filter can cover the set.

  • Nothing here names a payment provider. No provider name, id or status — every value is YabbaDeck's own. The moment this package named a PSP, every backend in the ecosystem would be coupled to the platform's choice of one.
  • ProductId is your string, echoed back exactly as your checkout named it. The platform holds no catalogue and never interprets it.
  • Money is minor units — 1050 is 10.50 kr. Never a decimal.
  • EntitlementGranted.ExpiresAt is nullable, and null means perpetual. Write your mirror row against a nullable date from the start: treating it as a boolean is the shortcut that becomes a migration the first time somebody sells a subscription.
  • Key your mirror row on EntitlementId, and gate on whether the user holds any active entitlement for the product. A redelivery then finds the row already there, and a renewal arrives as the same id with a later expiry, because the platform moves the date on the row rather than granting a second one. Not on (AppId, UserId, ProductId) — two separate purchases of one product are two grants with two ids, and collapsing them onto one row loses one of them.
  • Applying a grant only ever widens access. A redelivery changes nothing; a later expiry extends; an older delivery arriving late must not shorten what somebody already has; and null outranks every date and is never replaced by one. Delivery is at-least-once and unordered — this is what makes that harmless.
  • The event is a cue, not the authority. A broker promises at-least-once, which is a promise about retries, not about a message that was never published because something was down when it mattered. So before you tell somebody they own nothing, ask the platform: GET /api/v1/users/{userId}/entitlements with your backend's own machine token (entitlements:read). A dropped event then costs a moment of staleness rather than a customer's purchase. YabbaDeck.AppBackend does this for you.

The subscription events

Since 1.3.0, in the same namespace. A subscription is billed by the payment provider on its own schedule, and the platform tells you what happened:

Event Published when
SubscriptionStarted The first period was paid for. Carries the SubscriptionId your backend cancels or changes it by.
SubscriptionRenewed Another period was paid for, as a renewal or as a failed one the provider recovered.
SubscriptionPaymentFailed Charging for the next period failed and the provider is retrying. Access is not taken away.
SubscriptionCanceled Somebody canceled it. Immediately says whether access ended now or runs to the end of the paid period.
SubscriptionEnded It stopped billing for good. Reason is one of SubscriptionEndReasons.
  • Your entitlement mirror needs none of these. Access still arrives as EntitlementGranted. A renewal republishes it with the same EntitlementId and a later ExpiresAt, and the rules above already apply that correctly. A lapse needs no event: the entitlement just expires. An immediate cancellation arrives as EntitlementRevoked.
  • Every one carries AccessUntil, when access runs out: the end of the last paid period plus any grace period the platform gives. Use it to tell a user where they stand; gate on the entitlement.
  • Every billed period is its own payment. PaymentCompleted is published for each successful charge, renewals included, with the PaymentId the subscription event names.
  • Nothing is refunded by a cancellation. Money going back is its own act, with its own event.

Consuming them by hand

(YabbaDeck.AppBackend already does all of this — see the top of this page.)

Publishing is MassTransit fan-out: every app backend receives every event, and each consumer drops what is not addressed to it. That is what lets a generated backend work unmodified, with no per-app routing to configure.

public sealed class UserVerifiedConsumer(IOptions<AppOptions> app, IUserMirror users)
    : IConsumer<UserVerified>
{
    public async Task Consume(ConsumeContext<UserVerified> context)
    {
        // Not ours — another app on the same broker. Drop it. Ordinal: a public id is a slug, and
        // this comparison is the only thing standing between your app and another app's data.
        if (!string.Equals(context.Message.AppId, app.Value.PublicId, StringComparison.Ordinal))
        {
            return;
        }

        await users.EnsureAsync(context.Message.UserId, context.CancellationToken);
    }
}

Make consumers idempotent. The same event may be delivered more than once, and UserVerified for an id you already hold must be a no-op. The event is the sync path, not the only one: a protected endpoint should also upsert the local user from the access token's sub on first use, so a dropped or delayed message costs a moment of staleness rather than wedging the app.

Versioning

The version is explicit in the project file and bumped deliberately. Adding an event is a minor bump; changing the shape of an existing one is a major. Pin the version you build against.

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.
  • net10.0

    • No dependencies.

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