Class AppStoreBillingProvider
- Namespace
- VpnHood.AppLib.Stores.AppStore
- Assembly
- VpnHood.AppLib.Stores.AppStore.dll
StoreKit 2 billing. The plan token is the App Store product id (Apple has no base plans — the product IS the plan+cycle); the purchase proof is the SK2 signed transaction (JWS), which the portal's POST /billing/purchases treats as a pointer and re-fetches server-to-server. The bridge mirrors StoreKit as it is, so this app's own rules live here: only subscriptions are sold, an introductory offer shapes the first price, the newest entitlement is the one restored, and a purchase is finished at StoreKit once the portal has redeemed it.
public class AppStoreBillingProvider : IBillingProvider, IDisposable
- Inheritance
-
AppStoreBillingProvider
- Implements
- Inherited Members
Constructors
AppStoreBillingProvider(IStoreKitBridge?)
StoreKit 2 billing. The plan token is the App Store product id (Apple has no base plans — the product IS the plan+cycle); the purchase proof is the SK2 signed transaction (JWS), which the portal's POST /billing/purchases treats as a pointer and re-fetches server-to-server. The bridge mirrors StoreKit as it is, so this app's own rules live here: only subscriptions are sold, an introductory offer shapes the first price, the newest entitlement is the one restored, and a purchase is finished at StoreKit once the portal has redeemed it.
public AppStoreBillingProvider(IStoreKitBridge? bridge = null)
Parameters
bridgeIStoreKitBridgeThe bridge to StoreKit, or null for the native one.
Properties
IsSubscriptionManagementSupported
Whether this device can show the store's own manage-subscriptions surface (cancel, change plan, payment method). A runtime question, not a constant: a TV carries a store that cannot show that screen, and answering false there is what lets the UI say "manage it where you bought it" instead of offering a control that does nothing.
public bool IsSubscriptionManagementSupported { get; }
Property Value
Remarks
StoreKit shows Apple's own sheet in the app, so the person never leaves it. tvOS has no such sheet, so it is false there: a TV's subscriptions are managed in its Settings or from another of the person's devices.
ProviderId
Which store this is (see StoreIds), in the vocabulary the account backend speaks. Declared here because the provider is the one thing that cannot be wrong about it — so nothing that wires a store has to state it a second time and risk disagreeing.
public string ProviderId { get; }
Property Value
Remarks
It is AppStore.
PurchaseState
Gets where the store's payment flow stands.
public PurchaseState PurchaseState { get; }
Property Value
Methods
Dispose()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
FinishPurchase(PurchaseProof, CancellationToken)
Tells the store the purchase behind this proof is delivered: the account backend has redeemed it. Called only then, so a purchase whose redeeming failed stays open where the store keeps one open (StoreKit's unfinished transaction), and a later Restore presents it again. A store whose backend closes the purchase itself (Play's acknowledgement) has nothing to do here.
public Task FinishPurchase(PurchaseProof purchaseProof, CancellationToken cancellationToken)
Parameters
purchaseProofPurchaseProofThe proof the account backend has redeemed.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
Remarks
StoreKit keeps a transaction unfinished until the app has delivered it, which here is the portal redeeming its JWS. A restored transaction may be unfinished too; finishing one already finished changes nothing.
Exceptions
- BillingException
The store failed; its Code says how.
- ArgumentException
The proof is not one this provider answered, as the App Store's checks.
- OperationCanceledException
cancellationTokenwas canceled.
GetSubscriptionPlans(IReadOnlyList<string>, CancellationToken)
Prices the given products. The store is not asked WHICH products exist — it cannot answer that (neither StoreKit nor Play Billing can list an app's own catalog) and it is not the authority on it either: the account backend decides what may be sold, this prices and localizes it.
public Task<IReadOnlyList<SubscriptionPlan>> GetSubscriptionPlans(IReadOnlyList<string> productIds, CancellationToken cancellationToken)
Parameters
productIdsIReadOnlyList<string>The product IDs the account backend gives (GetProductIds(CancellationToken)).
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task<IReadOnlyList<SubscriptionPlan>>
A task whose result is the plans the store prices for them.
Remarks
Only subscriptions are priced; an introductory offer the person may still take sets the first price, or the free trial.
Exceptions
- ApiException
For the web store, the account backend refused the request.
- BillingException
The store failed; its Code says how.
- OperationCanceledException
cancellationTokenwas canceled.
OpenSubscriptionManagement(IUiContext?, CancellationToken)
Shows that surface. The store does it its own way — an in-app sheet, its own app — so no URL crosses to the UI and no browser has to exist. Only ever called for a subscription THIS store billed, and only while IsSubscriptionManagementSupported: the app refuses the call otherwise, so an implementation need not re-check its own answer.
public Task OpenSubscriptionManagement(IUiContext? uiContext, CancellationToken cancellationToken)
Parameters
uiContextIUiContextThe window, or null where none is open.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that completes once the store's surface is shown.
Remarks
StoreKit's manage-subscriptions sheet shows over the window.
Exceptions
- NotSupportedException
The store has no such surface here, as the web store's.
- UiContextNotAvailableException
uiContextis null, and the store needs a window.- OperationCanceledException
cancellationTokenwas canceled.- InvalidOperationException
StoreKit failed to show the sheet; the error comes as StoreKit's, not as a BillingException.
Purchase(IUiContext?, PurchaseParams, PurchaseAttribution, CancellationToken)
Runs the store's payment flow and answers its proof of purchase.
Two arguments because they have two owners: purchaseParams is the buyer's
choice, attribution is the app's answer to whose account is paying.
public Task<PurchaseProof> Purchase(IUiContext? uiContext, PurchaseParams purchaseParams, PurchaseAttribution attribution, CancellationToken cancellationToken)
Parameters
uiContextIUiContextThe window, or null where none is open.
purchaseParamsPurchaseParamsThe plan the buyer chose.
attributionPurchaseAttributionWhose account the purchase is for.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task<PurchaseProof>
A task whose result is the store's proof of purchase.
Remarks
The plan token is the App Store product ID; the account's ID goes to StoreKit as the purchase's app account token, which must be a UUID. The proof is StoreKit's signed transaction (JWS). A purchase StoreKit leaves pending, as one waiting for a parent's approval, throws a BillingException with Pending.
Exceptions
- BillingException
The store failed, or the person closed its payment flow (Cancelled); its Code says how.
- NotSupportedException
The store sells outside the app, as the web store, on its checkout page.
- InvalidOperationException
A purchase is under way already, or the account's ID does not fit the store, as the App Store's, which takes a UUID.
- ArgumentException
The plan names a product the store does not have.
- UiContextNotAvailableException
uiContextis null, and the store needs a window.- OperationCanceledException
cancellationTokenwas canceled.- InvalidOperationException
The account's ID is not a UUID, or StoreKit answered a purchase without its transaction.
RestorePurchase(IUiContext?, CancellationToken)
The proof for a subscription this store account already owns, or null when it owns none. Silent: it reads what the device already knows and never prompts.
public Task<PurchaseProof?> RestorePurchase(IUiContext? uiContext, CancellationToken cancellationToken)
Parameters
uiContextIUiContextThe window, or null where none is open.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task<PurchaseProof>
A task whose result is the proof, or null where the store account owns no subscription.
Remarks
It presents the newest of the store account's current entitlements, without a new charge.
Exceptions
- BillingException
The store failed; its Code says how.
- OperationCanceledException
cancellationTokenwas canceled.