AMDevIT.StoreKitWrapper 0.130.0

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

AMDevIT StoreKit Wrapper

Version iOS .NET Swift Project status License

AMDevIT StoreKit Wrapper is a native StoreKit 2 framework with a .NET 10 for iOS binding. It provides a callback-based API for products, purchases, entitlements, transaction recovery, and StoreKit merchandising views without exposing Swift concurrency or native StoreKit types to .NET applications.

This project is currently a preview. The .NET binding builds successfully, but native compilation and runtime behavior still need final verification on macOS and iOS before production use.

Features

  • Loads complete product and subscription metadata.
  • Handles purchases, pending results, cancellation, and transaction verification.
  • Retrieves current entitlements and unfinished transactions.
  • Listens for StoreKit transaction updates throughout the manager lifecycle.
  • Finishes verified transactions explicitly after the application delivers the purchase.
  • Exposes an explicit App Store synchronization operation for Restore Purchases flows.
  • Maps StoreKit failures to stable, Objective-C-compatible error codes.
  • Bridges optional native structured logging to Microsoft.Extensions.Logging in the managed client.
  • Provides a managed StoreKitClient facade with task-based .NET APIs.
  • Provides UIKit controllers backed by StoreKit SwiftUI views on iOS 17 and later.
  • Keeps Swift concurrency, Product, Transaction, and VerificationResult behind the native boundary.

Architecture

The repository contains two cooperating layers:

  1. src/apple/StoreKitWrapper implements the StoreKit 2 framework in Swift and exposes an Objective-C-compatible surface.
  2. src/dotnet/AMDevIT.StoreKitWrapper binds the packaged StoreKitWrapper.xcframework for .NET 10 for iOS.

All asynchronous native operations complete through StoreKitManagerDelegate. The managed StoreKitClient converts those callbacks into .NET tasks while preserving StoreKitManager as the directly bound low-level API. This allows .NET consumers to use StoreKit 2 without crossing the binding boundary with Swift actors, tasks, async sequences, or generic StoreKit values.

Requirements

  • .NET 10 SDK with the iOS workload.
  • iOS 15.6 or later.
  • macOS and a compatible Xcode installation for native framework builds, signing, simulator execution, or device deployment.
  • iOS 17 or later when using the native StoreKit view controllers.
  • StoreKit products configured in App Store Connect or in an Xcode StoreKit Configuration file.

Quick start

1. Install or reference the binding

The binding is configured as the AMDevIT.StoreKitWrapper NuGet package. After it is published, install version 0.130.0 from the configured NuGet source:

dotnet add package AMDevIT.StoreKitWrapper --version 0.130.0

Until the first public release, clone this repository and either create a local package or add a direct project reference. To produce and consume the local package from the repository root:

dotnet pack src/dotnet/AMDevIT.StoreKitWrapper/AMDevIT.StoreKitWrapper.slnx --configuration Release
dotnet add package AMDevIT.StoreKitWrapper --version 0.130.0 --source ./artifacts/packages

Alternatively, add a project reference from the consuming .NET for iOS application:

<ItemGroup>
    <ProjectReference Include="../AMDevITStoreKitWrapper/src/dotnet/AMDevIT.StoreKitWrapper/AMDevIT.StoreKitWrapper/AMDevIT.StoreKitWrapper.csproj" />
</ItemGroup>

Adjust the relative path to match the location of the repositories on your machine.

2. Use the managed async client

StoreKitClient is the recommended application-facing API. It owns a native StoreKitManager, translates operation callbacks into tasks, and exposes independent transaction-listener updates as an event:

private readonly StoreKitClient storeKitClient;

public MyStoreService(ILogger<StoreKitClient> logger)
{
    this.storeKitClient = new StoreKitClient(logger);
    this.storeKitClient.TransactionUpdated += this.OnTransactionUpdated;
}

public async Task InitializeAsync(CancellationToken cancellationToken)
{
    IReadOnlyList<StoreKitProduct> products;

    await this.storeKitClient.InitializeAsync(cancellationToken);
    products = await this.storeKitClient.GetProductsAsync(["com.example.premium"],
                                                          cancellationToken);

    foreach (StoreKitProduct product in products)
    {
        Console.WriteLine($"{product.DisplayName}: {product.DisplayPrice}");
    }
}

public async Task PurchasePremiumAsync(CancellationToken cancellationToken)
{
    StoreKitPurchaseOutcome outcome;

    outcome = await this.storeKitClient.PurchaseAsync("com.example.premium",
                                                      cancellationToken: cancellationToken);

    if (outcome.Transaction?.VerificationStatus == StoreKitTransactionVerificationStatus.Verified)
    {
        DeliverPurchase(outcome.Transaction);
        await this.storeKitClient.FinishTransactionAsync(outcome.Transaction.Identifier,
                                                         cancellationToken);
    }
}

private void OnTransactionUpdated(object? sender,
                                  StoreKitTransactionUpdatedEventArgs eventArgs)
{
    if (eventArgs.Transaction.VerificationStatus == StoreKitTransactionVerificationStatus.Verified)
    {
        DeliverPurchase(eventArgs.Transaction);
    }
}

All task continuations are detached from the native callback through TaskCreationOptions.RunContinuationsAsynchronously. The wrapper doesn't dispatch callbacks or events to the main thread.

The optional ILogger<StoreKitClient> receives native StoreKit diagnostics with their mapped Microsoft log level, numeric and named EventId, message, and an NSErrorException when the native event carries an error. The package depends only on Microsoft.Extensions.Logging.Abstractions; the application remains responsible for selecting and configuring logging providers.

Every task-based operation honors CancellationToken both before and after its native invocation. Cancellation stops the managed wait immediately and forwards a cooperative cancellation request to the corresponding Swift task. The internal completion source remains registered until the native terminal callback arrives, preventing late callbacks from being associated with a later request.

Cancellation is best effort for StoreKit operations that may already have reached system UI or an irreversible system action. In particular, cancelling a purchase wait doesn't guarantee that the App Store confirmation flow or transaction stops, and cancelling a transaction finish doesn't roll back a finish already accepted by StoreKit. Transactions completed after a cancelled purchase wait remain recoverable through TransactionUpdated.

Errors reported by native operation callbacks become StoreKitWrapperException instances containing the stable StoreKitWrapperErrorCode. Pending and customer-cancelled purchases remain successful task completions represented by StoreKitPurchaseOutcome.

3. Use the low-level delegate API

Derive from the generated StoreKitManagerDelegate model and override every callback that the native protocol can invoke. The following delegate initializes a product catalog and shows the essential transaction flow:

using AMDevIT.StoreKitWrapper;

public sealed class AppStoreKitDelegate : StoreKitManagerDelegate
{
    #region Properties

    public StoreKitManager? Manager { get; set; }

    #endregion

    #region Methods

    public override void InitializationCompletedWithErrorCode(StoreKitWrapperErrorCode errorCode,
                                                               string? errorMessage)
    {
        if (errorCode == StoreKitWrapperErrorCode.None)
        {
            this.Manager?.GetProductsWithProductIdentifiers(["com.example.premium"]);
            return;
        }

        Console.WriteLine($"StoreKit initialization failed: {errorMessage}");
    }

    public override void AvailableProductsCompletedWithResult(StoreKitProduct[] withResult,
                                                              StoreKitWrapperErrorCode errorCode,
                                                              string? errorMessage)
    {
        foreach (StoreKitProduct product in withResult)
        {
            Console.WriteLine($"{product.DisplayName}: {product.DisplayPrice}");
        }
    }

    public override void PurchaseCompletedWithResult(StoreKitTransaction? withResult,
                                                     StoreKitPurchaseResult purchaseResult,
                                                     StoreKitWrapperErrorCode errorCode,
                                                     string? errorMessage)
    {
        Console.WriteLine($"Purchase result: {purchaseResult}");

        // Persist and deliver a verified purchase before finishing its transaction.
        if (withResult?.VerificationStatus == StoreKitTransactionVerificationStatus.Verified)
        {
            DeliverPurchase(withResult);
        }
    }

    public override void TransactionUpdatedWithResult(StoreKitTransaction withResult,
                                                      StoreKitWrapperErrorCode errorCode,
                                                      string? errorMessage)
    {
        // Updates include unfinished startup transactions and purchases from other devices.
        if (withResult.VerificationStatus == StoreKitTransactionVerificationStatus.Verified)
        {
            DeliverPurchase(withResult);
        }
    }

    public override void FinishTransactionCompletedWithTransactionIdentifier(ulong transactionIdentifier,
                                                                             StoreKitWrapperErrorCode errorCode,
                                                                             string? errorMessage)
    {
        Console.WriteLine($"Finished transaction {transactionIdentifier}: {errorCode}");
    }

    public override void CurrentEntitlementsCompletedWithResult(StoreKitTransaction[] withResult,
                                                                StoreKitWrapperErrorCode errorCode,
                                                                string? errorMessage)
    {
    }

    public override void UnfinishedTransactionsCompletedWithResult(StoreKitTransaction[] withResult,
                                                                   StoreKitWrapperErrorCode errorCode,
                                                                   string? errorMessage)
    {
    }

    public override void AppStoreSyncCompletedWithErrorCode(StoreKitWrapperErrorCode errorCode,
                                                            string? errorMessage)
    {
    }

    public override void ShutdownCompletedWithErrorCode(StoreKitWrapperErrorCode errorCode,
                                                        string? errorMessage)
    {
    }

    private void DeliverPurchase(StoreKitTransaction transaction)
    {
        // Persist the entitlement and deliver the content here. Finish only after success.
        this.Manager?.FinishTransactionWithTransactionIdentifier(transaction.Identifier);
    }

    #endregion
}

Replace DeliverPurchase with an idempotent delivery process backed by durable application storage. Never grant content solely because a transaction object exists: first confirm that VerificationStatus is Verified.

4. Create and initialize the low-level manager

Keep strong references to both objects for as long as StoreKit callbacks are required:

private readonly AppStoreKitDelegate storeKitDelegate;
private readonly StoreKitManager storeKitManager;

public MyStoreService()
{
    this.storeKitDelegate = new AppStoreKitDelegate();
    this.storeKitManager = new StoreKitManager(logger: null,
                                               @delegate: this.storeKitDelegate);
    this.storeKitDelegate.Manager = this.storeKitManager;
}

public void Initialize()
{
    this.storeKitManager.Initialize();
}

public void PurchasePremium()
{
    this.storeKitManager.PurchaseWithProductIdentifier("com.example.premium");
}

public void Shutdown()
{
    this.storeKitManager.Shutdown();
}

Call Initialize early in the application lifecycle so the persistent Transaction.updates listener can recover pending changes. Wait for InitializationCompletedWithErrorCode before requesting products or starting other operations.

Common operations

After successful initialization, the manager exposes these callback-based operations:

Operation Completion callback Purpose
GetProductsWithProductIdentifiers(...) AvailableProductsCompletedWithResult(...) Load the current product catalog.
PurchaseWithProductIdentifier(...) PurchaseCompletedWithResult(...) Start a programmatic purchase.
GetCurrentEntitlements() CurrentEntitlementsCompletedWithResult(...) Reconstruct access currently granted to the customer.
GetUnfinishedTransactions() UnfinishedTransactionsCompletedWithResult(...) Recover verified transactions that still require delivery or finishing.
FinishTransactionWithTransactionIdentifier(...) FinishTransactionCompletedWithTransactionIdentifier(...) Finish a transaction after successful delivery.
Sync() AppStoreSyncCompletedWithErrorCode(...) Implement an explicit, user-initiated Restore Purchases action.
Shutdown() ShutdownCompletedWithErrorCode(...) Stop the transaction listener deterministically.

Transactions arriving independently of a programmatic purchase are reported through TransactionUpdatedWithResult(...).

Native StoreKit views

On iOS 17 and later, the framework exposes ordinary UIKit controllers that internally host StoreKit SwiftUI merchandising views:

  • StoreKitProductViewController for a single product.
  • StoreKitProductsViewController for multiple products.
  • StoreKitSubscriptionsViewController for a subscription group.
StoreKitProductViewController productController = new("com.example.premium");

PresentViewController(productController, animated: true, completionHandler: null);

The application owns presentation, navigation, parent containment, and external Auto Layout constraints. Initialize StoreKitManager before presenting a controller: purchases started by these views are delivered through TransactionUpdatedWithResult(...), not PurchaseCompletedWithResult(...).

Build and verification

Build the .NET binding from its solution directory:

cd src/dotnet/AMDevIT.StoreKitWrapper
dotnet restore
dotnet build

Create the NuGet package from the repository root:

dotnet pack src/dotnet/AMDevIT.StoreKitWrapper/AMDevIT.StoreKitWrapper.slnx --configuration Release

The package is written to artifacts/packages and includes the binding assembly, its XML documentation for IDE IntelliSense, the native XCFramework, README, license, package icon, and NuGet metadata. Packing does not publish the package; use an authenticated NuGet source explicitly when it is ready for release.

On macOS with Xcode installed, build the native framework and verify its generated Objective-C interface:

bash scripts/verify-native-contract.sh

The script builds the Release framework for the iOS Simulator and validates the installed StoreKitWrapper-Swift.h, including the expected public API and the absence of internal Swift implementation types.

Project status

  • Native StoreKit implementation: implemented; macOS/Xcode verification pending.
  • Objective-C-compatible contract: implemented; generated-header verification script available.
  • Native deterministic tests: implemented; execution on macOS pending.
  • .NET 10 for iOS binding: restored and built successfully with zero warnings and errors in the recorded project verification.
  • NuGet packaging: configured for package ID AMDevIT.StoreKitWrapper; package creation and content inspection pending.
  • Consumer application and real StoreKit runtime validation: pending on macOS/iOS.

See the files in .agents for the progressive implementation notes and verification history.

License

Licensed under the Apache License 2.0.

Product Compatible and additional computed target framework versions.
.NET net10.0-ios26.0 is compatible. 
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
0.130.0 27 8/15/2026