diff --git a/README.md b/README.md index eaea63f..5d04441 100644 --- a/README.md +++ b/README.md @@ -202,9 +202,10 @@ let store = TransactionStore( ## Unrecognized subscriptions A non-upgraded product added to the same subscription group after this binary -ships can still arrive through another device or purchase path. By default, the -store keeps the verified raw transaction, grants no typed entitlement, and -leaves an unfinished delivery unfinished. +ships can still arrive after a plan change in Manage Subscriptions, an offer +code or win-back offer redeemed through the App Store, or a purchase on another +device. By default, the store keeps the verified raw transaction, grants no +typed entitlement, and leaves an unfinished delivery unfinished. Supply a separate delegate only when the app knows how to handle that product: diff --git a/Sources/StoreTransactionKit/StoreTransactionKit.docc/UnderstandingTransactionHandling.md b/Sources/StoreTransactionKit/StoreTransactionKit.docc/UnderstandingTransactionHandling.md index 890da1b..d5fa687 100644 --- a/Sources/StoreTransactionKit/StoreTransactionKit.docc/UnderstandingTransactionHandling.md +++ b/Sources/StoreTransactionKit/StoreTransactionKit.docc/UnderstandingTransactionHandling.md @@ -3,6 +3,23 @@ Follow a transaction from admission through policy, finishing, entitlement publication, failure ownership, and shutdown. +## App-owned purchase initiation + +StoreTransactionKit starts handling a purchase when the app passes a +`Product.PurchaseResult` to ``TransactionStore/process(_:)`` or when the live +store receives a transaction through StoreKit's monitoring or reconciliation +paths. It doesn't observe `PurchaseIntent.intents` or initiate purchases. + +On platforms where `PurchaseIntent` is available, StoreKit can emit an intent +for a supported App Store-initiated purchase, such as a promoted In-App Purchase +or a win-back offer when Streamlined Purchasing is disabled. The app owns +observing the sequence while it is running. Call `purchase(options:)` on the +intent's product, include any purchase options required by the intent, and pass +the resulting `Product.PurchaseResult` to +``TransactionStore/process(_:)``. Completed purchases that StoreKit instead +exposes through `Transaction.updates` or `Transaction.currentEntitlements` +enter the store's monitoring and reconciliation paths without app forwarding. + ## Exact-revision processing A direct `Product.PurchaseResult`, `Transaction.updates`, and @@ -80,6 +97,11 @@ doesn't block publication or repeat its decision in the same store session. Current entitlements that were finished by another process or device may still require an unrecognized-subscription decision for typed projection. +The live store observes `Product.SubscriptionInfo.Status.updates`. +Subscription-status changes trigger current-entitlement reconciliation; status +isn't a second entitlement source and isn't copied into the app's entitlement +type. + ``TransactionStore/entitlements``, ``TransactionStore/activeEntitlements``, and ``TransactionStore/entitlementStatus`` derive from one availability value: diff --git a/Sources/StoreTransactionKit/TransactionStore.swift b/Sources/StoreTransactionKit/TransactionStore.swift index b1a25c5..ce8b2ec 100644 --- a/Sources/StoreTransactionKit/TransactionStore.swift +++ b/Sources/StoreTransactionKit/TransactionStore.swift @@ -91,12 +91,12 @@ where Entitlement: Hashable & Sendable { /// Creates the process's live StoreKit store for an auto-renewable subscription catalog. /// - /// Initialization starts transaction monitoring and the first entitlement - /// reconciliation. The store strongly retains both delegates until terminal - /// shutdown. Creating a second live store in the same process before the - /// first store finishes ``close()`` is a programmer error. Without - /// `unrecognizedSubscriptionDelegate`, valid non-upgraded undeclared - /// same-group subscriptions use + /// Initialization starts transaction and subscription-status monitoring, + /// along with the first entitlement reconciliation. The store strongly + /// retains both delegates until terminal shutdown. Creating a second live + /// store in the same process before the first store finishes ``close()`` is + /// a programmer error. Without `unrecognizedSubscriptionDelegate`, valid + /// non-upgraded undeclared same-group subscriptions use /// ``UnrecognizedSubscriptionPolicy/leaveUnfinished``. public convenience init( subscriptionCatalog: AutoRenewableSubscriptionCatalog, @@ -257,8 +257,10 @@ where Entitlement: Hashable & Sendable { activeEntitlements?.contains(entitlement) == true } - /// Processes a direct result from custom purchase UI. + /// Processes a StoreKit purchase result supplied by the app. /// + /// Use this method for a result from `Product.purchase(options:)`, including + /// a purchase the app completes after observing `PurchaseIntent.intents`. /// A verified purchase returns only after policy selection, the selected /// finish or leave action, causal entitlement reconciliation, and main-actor /// publication. Pending and user-cancelled results return their corresponding