Skip to content

Commit

Permalink
StoreTransaction: read Storefront from StoreKit.Transaction (#2611
Browse files Browse the repository at this point in the history
)

This was added in iOS 17, and it's potentially more accurate than
reading `Storefront.currentStorefront` later.
  • Loading branch information
NachoSoto authored Jun 12, 2023
1 parent 2a95520 commit 01ed2cb
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 1 deletion.
1 change: 1 addition & 0 deletions Sources/Misc/Deprecations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ extension CustomerInfo {
let purchaseDate: Date
let transactionIdentifier: String
let quantity: Int
var storefront: Storefront? { return nil }

init(with transaction: NonSubscriptionTransaction) {
self.productIdentifier = transaction.productIdentifier
Expand Down
2 changes: 1 addition & 1 deletion Sources/Purchasing/Purchases/PurchasesOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ extension PurchasesOrchestrator: StoreKit2TransactionListenerDelegate {
_ listener: StoreKit2TransactionListener,
updatedTransaction transaction: StoreTransactionType
) async throws {
let storefront = await Storefront.currentStorefront
let storefront = await transaction.storefrontOrCurrent

_ = try await Async.call { completed in
self.transactionPoster.handlePurchasedTransaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ internal struct SK1StoreTransaction: StoreTransactionType {
let transactionIdentifier: String
let quantity: Int

var storefront: Storefront? {
// This is only available on StoreKit 2 transactions.
return nil
}

func finish(_ wrapper: PaymentQueueWrapperType, completion: @escaping @Sendable () -> Void) {
wrapper.finishTransaction(self.underlyingSK1Transaction, completion: completion)
}
Expand Down
11 changes: 11 additions & 0 deletions Sources/Purchasing/StoreKitAbstractions/SK2StoreTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ internal struct SK2StoreTransaction: StoreTransactionType {
self.purchaseDate = sk2Transaction.purchaseDate
self.transactionIdentifier = String(sk2Transaction.id)
self.quantity = sk2Transaction.purchasedQuantity

#if swift(>=5.9)
if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
self.storefront = .init(sk2Storefront: sk2Transaction.storefront)
} else {
self.storefront = nil
}
#else
self.storefront = nil
#endif
}

let underlyingSK2Transaction: SK2Transaction
Expand All @@ -31,6 +41,7 @@ internal struct SK2StoreTransaction: StoreTransactionType {
let purchaseDate: Date
let transactionIdentifier: String
let quantity: Int
let storefront: Storefront?

func finish(_ wrapper: PaymentQueueWrapperType, completion: @escaping @Sendable () -> Void) {
Async.call(with: completion) {
Expand Down
17 changes: 17 additions & 0 deletions Sources/Purchasing/StoreKitAbstractions/StoreTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public typealias SK2Transaction = StoreKit.Transaction
@objc public var purchaseDate: Date { self.transaction.purchaseDate }
@objc public var transactionIdentifier: String { self.transaction.transactionIdentifier }
@objc public var quantity: Int { self.transaction.quantity }
@objc public var storefront: Storefront? { self.transaction.storefront }

func finish(_ wrapper: PaymentQueueWrapperType, completion: @escaping @Sendable () -> Void) {
self.transaction.finish(wrapper, completion: completion)
Expand Down Expand Up @@ -96,6 +97,10 @@ internal protocol StoreTransactionType: Sendable {
/// - Note: multi-quantity purchases aren't currently supported.
var quantity: Int { get }

/// The App Store storefront associated with the transaction.
/// - Note: this is only available for StoreKit 2 transactions starting with iOS 17.
var storefront: Storefront? { get }

/// Indicates to the App Store that the app delivered the purchased content
/// or enabled the service to finish the transaction.
func finish(_ wrapper: PaymentQueueWrapperType, completion: @escaping @Sendable () -> Void)
Expand Down Expand Up @@ -128,6 +133,18 @@ extension StoreTransaction {

}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
extension StoreTransactionType {

/// - Returns: the `Storefront` associated to this transaction, or `Storefront.currentStorefront` if not available.
var storefrontOrCurrent: Storefront? {
get async {
return await self.storefront ??? (await Storefront.currentStorefront)
}
}

}

extension StoreTransaction: Identifiable {

/// The stable identity of the entity associated with this instance.
Expand Down
8 changes: 8 additions & 0 deletions Tests/StoreKitUnitTests/StoreTransactionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class StoreTransactionTests: StoreKitConfigTestCase {
expect(transaction.purchaseDate) == sk1Transaction.mockTransactionDate
expect(transaction.transactionIdentifier) == sk1Transaction.mockTransactionIdentifier
expect(transaction.quantity) == payment.quantity
expect(transaction.storefront).to(beNil())
}

@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
Expand All @@ -53,6 +54,13 @@ class StoreTransactionTests: StoreKitConfigTestCase {
expect(transaction.purchaseDate.timeIntervalSinceNow) <= 5
expect(transaction.transactionIdentifier) == String(sk2Transaction.id)
expect(transaction.quantity) == sk2Transaction.purchasedQuantity

if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
let expected = await Storefront.currentStorefront
expect(transaction.storefront) == expected
} else {
expect(transaction.storefront).to(beNil())
}
}

func testSk1TransactionDateBecomesAnInvalidDateIfNoDate() {
Expand Down
2 changes: 2 additions & 0 deletions Tests/UnitTests/Mocks/MockStoreTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ final class MockStoreTransaction: StoreTransactionType {
let purchaseDate: Date
let transactionIdentifier: String
let quantity: Int
let storefront: RevenueCat.Storefront?

init() {
self.productIdentifier = UUID().uuidString
self.purchaseDate = Date()
self.transactionIdentifier = UUID().uuidString
self.quantity = 1
self.storefront = nil
}

private let _finishInvoked: Atomic<Bool> = false
Expand Down

0 comments on commit 01ed2cb

Please sign in to comment.