Skip to content

Commit

Permalink
PaymentQueueWrapper: also conform to SKPaymentTransactionObserver
Browse files Browse the repository at this point in the history
… to fix promoted purchases (#1962)

Follow up to #1901. That fix was incomplete. I missed it because the
protocols were mixed up (fixed in #1961). Now `PaymentQueueWrapper`,
when set up as the delegate (only when SK2 is enabled and SK1 is
disabled) will be added as a transaction observer.
  • Loading branch information
NachoSoto authored Oct 7, 2022
1 parent 0dda9c4 commit 7dd98ee
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 36 deletions.
10 changes: 9 additions & 1 deletion RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@
5791CE80273F26A000E50C4B /* base64encoded_sandboxReceipt.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5791CE7F273F26A000E50C4B /* base64encoded_sandboxReceipt.txt */; };
579234E327F7788900B39C68 /* BaseBackendIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579234E127F777EE00B39C68 /* BaseBackendIntegrationTests.swift */; };
579234E527F779FE00B39C68 /* SubscriberAttributesManagerIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579234E427F779FE00B39C68 /* SubscriberAttributesManagerIntegrationTests.swift */; };
5793397028E77A5100C1232C /* PaymentQuueWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5793396F28E77A5100C1232C /* PaymentQuueWrapperTests.swift */; };
5793397228E77A6E00C1232C /* MockPaymentQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5793397128E77A6E00C1232C /* MockPaymentQueue.swift */; };
5796A38127D6B78500653165 /* BaseBackendTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A38027D6B78500653165 /* BaseBackendTest.swift */; };
5796A38827D6B85900653165 /* BackendPostReceiptDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A38727D6B85900653165 /* BackendPostReceiptDataTests.swift */; };
5796A38A27D6B96300653165 /* BackendGetCustomerInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5796A38927D6B96300653165 /* BackendGetCustomerInfoTests.swift */; };
Expand Down Expand Up @@ -786,6 +788,8 @@
5791CE7F273F26A000E50C4B /* base64encoded_sandboxReceipt.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = base64encoded_sandboxReceipt.txt; sourceTree = "<group>"; };
579234E127F777EE00B39C68 /* BaseBackendIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseBackendIntegrationTests.swift; sourceTree = "<group>"; };
579234E427F779FE00B39C68 /* SubscriberAttributesManagerIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriberAttributesManagerIntegrationTests.swift; sourceTree = "<group>"; };
5793396F28E77A5100C1232C /* PaymentQuueWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentQuueWrapperTests.swift; sourceTree = "<group>"; };
5793397128E77A6E00C1232C /* MockPaymentQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPaymentQueue.swift; sourceTree = "<group>"; };
5796A38027D6B78500653165 /* BaseBackendTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseBackendTest.swift; sourceTree = "<group>"; };
5796A38727D6B85900653165 /* BackendPostReceiptDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendPostReceiptDataTests.swift; sourceTree = "<group>"; };
5796A38927D6B96300653165 /* BackendGetCustomerInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendGetCustomerInfoTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1400,6 +1404,7 @@
35F8B8F926E02AE1003C3363 /* MockTrialOrIntroPriceEligibilityChecker.swift */,
37E357D16038F07915D7825D /* MockUserDefaults.swift */,
2DDF41E924F6F844005BC22D /* SKProductSubscriptionDurationExtensions.swift */,
5793397128E77A6E00C1232C /* MockPaymentQueue.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -1549,19 +1554,20 @@
children = (
57E0474B27729A1E0082FE91 /* __Snapshots__ */,
2D1015DF275A67560086173F /* StoreKitAbstractions */,
5766AABB283E809D00FA6091 /* Purchases */,
37E3582920E16E065502E5FC /* EntitlementInfosTests.swift */,
B3F8418E26F3A93400E560FB /* ErrorCodeTests.swift */,
5752E8472892DC500069281E /* ErrorUtilsTests.swift */,
37E354B18710B488B8B0D443 /* IntroEligibilityCalculatorTests.swift */,
F575859126C08E3F00C12B97 /* OfferingsManagerTests.swift */,
37E357C2D977BBB081216B5F /* OfferingsTests.swift */,
5793396F28E77A5100C1232C /* PaymentQuueWrapperTests.swift */,
37E3583675928C01D92E3166 /* ProductRequestDataExtensions.swift */,
37E3548189DA008320B3FC98 /* ProductRequestDataInitializationTests.swift */,
37E350E57B0A393455A72B40 /* ProductRequestDataTests.swift */,
B390F5B9271DDC7400B64D65 /* PurchasesDeprecation.swift */,
37E351F0E21361EAEC078A0D /* ProductsFetcherSK1Tests.swift */,
37E359D8F304C83184560135 /* CustomerInfoTests.swift */,
5766AABB283E809D00FA6091 /* Purchases */,
2D84458826B9CD270033B5A3 /* ReceiptFetcherTests.swift */,
F5BE4244269676E200254A30 /* StoreKitRequestFetcherTests.swift */,
37E352F86A0A8EB05BAD77C4 /* StoreKit1WrapperTests.swift */,
Expand Down Expand Up @@ -2718,11 +2724,13 @@
F575858F26C0893600C12B97 /* MockOfferingsManager.swift in Sources */,
A563F586271E072B00246E0C /* MockBeginRefundRequestHelper.swift in Sources */,
2DA85A8A26DEA7DC00F1136D /* MockProductsRequestFactory.swift in Sources */,
5793397028E77A5100C1232C /* PaymentQuueWrapperTests.swift in Sources */,
57FDAA9A2846C2BD009A48F1 /* PurchasesDelegateTests.swift in Sources */,
351B516026D44BB600BD2BD7 /* MockAttributionDataMigrator.swift in Sources */,
2DDF41E024F6F527005BC22D /* MockInAppPurchaseBuilder.swift in Sources */,
37E352973B0901E3CAA717E1 /* DateFormatter+ExtensionsTests.swift in Sources */,
B3F8418F26F3A93400E560FB /* ErrorCodeTests.swift in Sources */,
5793397228E77A6E00C1232C /* MockPaymentQueue.swift in Sources */,
5796A38A27D6B96300653165 /* BackendGetCustomerInfoTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
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 @@ -618,9 +618,9 @@ extension PurchasesOrchestrator: PaymentQueueWrapperDelegate {
// when `StoreKit1Wrapper` is not initialized, which means that promoted purchases
// need to be handled as a SK2 purchase.
// This method converts the `SKPayment` into an SK2 purchase by fetching the product again.
assert(self.storeKit1Wrapper == nil, "This method should not be invoked if SK1 is enabled")
if self.storeKit1Wrapper != nil {
Logger.warn("Unexpectedly received PaymentQueueWrapperDelegate call with SK1 enabled")
assertionFailure("This method should not be invoked if SK1 is enabled")
}

guard let delegate = self.delegate else { return false }
Expand Down
13 changes: 12 additions & 1 deletion Sources/Purchasing/StoreKit1/PaymentQueueWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ class PaymentQueueWrapper: NSObject {
if #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) {
if self.delegate != nil {
self.paymentQueue.delegate = self
self.paymentQueue.add(self)
} else if self.delegate == nil, self.paymentQueue.delegate === self {
self.paymentQueue.delegate = nil
self.paymentQueue.remove(self)
}
}
}
Expand Down Expand Up @@ -71,15 +73,24 @@ class PaymentQueueWrapper: NSObject {

extension PaymentQueueWrapper: SKPaymentQueueDelegate {

}

extension PaymentQueueWrapper: SKPaymentTransactionObserver {

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
// Ignored. Either `StoreKit1Wrapper` will handle this, or `StoreKit2TransactionListener` if `SK2` is enabled.
}

#if !os(watchOS)
// Sent when a user initiated an in-app purchase from the App Store.
@available(watchOS, unavailable)
func paymentQueue(_ queue: SKPaymentQueue,
shouldAddStorePayment payment: SKPayment,
for product: SK1Product) -> Bool {
return self.delegate?.paymentQueueWrapper(self,
shouldAddStorePayment: payment,
for: product) ?? false
}
#endif

}

Expand Down
47 changes: 47 additions & 0 deletions Tests/UnitTests/Mocks/MockPaymentQueue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// MockPaymentQueue.swift
//
// Created by Nacho Soto on 9/30/22.

import StoreKit

final class MockPaymentQueue: SKPaymentQueue {

var addedPayments: [SKPayment] = []
override func add(_ payment: SKPayment) {
addedPayments.append(payment)
}

var observers: [SKPaymentTransactionObserver] = []
override func add(_ observer: SKPaymentTransactionObserver) {
observers.append(observer)
}

override func remove(_ observer: SKPaymentTransactionObserver) {
let index = observers.firstIndex { $0 === observer }
observers.remove(at: index!)
}

var finishedTransactions: [SKPaymentTransaction] = []
override func finishTransaction(_ transaction: SKPaymentTransaction) {
finishedTransactions.append(transaction)
}

#if os(iOS) || targetEnvironment(macCatalyst)
@available(iOS 13.4, macCatalyst 13.4, *)
func simulatePaymentQueueShouldShowPriceConsent() -> [Bool] {
return self.observers
.compactMap { $0 as? SKPaymentQueueDelegate }
.compactMap { $0.paymentQueueShouldShowPriceConsent?(self) }
}
#endif

}
93 changes: 93 additions & 0 deletions Tests/UnitTests/Purchasing/PaymentQuueWrapperTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// PaymentQuueWrapperTests.swift
//
// Created by Nacho Soto on 9/30/22.

import Foundation
import Nimble
import StoreKit
import XCTest

@testable import RevenueCat

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
class PaymentQueueWrapperTests: TestCase {

private var paymentQueue: MockPaymentQueue!
private var wrapper: PaymentQueueWrapper!
private var delegate: WrapperDelegate!

override func setUpWithError() throws {
try super.setUpWithError()

try AvailabilityChecks.iOS13APIAvailableOrSkipTest()

self.paymentQueue = MockPaymentQueue()
self.wrapper = .init(paymentQueue: self.paymentQueue)
self.delegate = WrapperDelegate()
}

func testNoDelegateIsSetByDefault() {
expect(self.paymentQueue.delegate).to(beNil())
}

func testNoObserverIsAddedByDefault() {
expect(self.paymentQueue.observers).to(beEmpty())
}

func testSettingDelegateSetsPaymentQueueDelegate() {
self.wrapper.delegate = self.delegate

expect(self.paymentQueue.delegate) === self.wrapper
}

func testSettingDelegateAddsTransactionObserver() {
self.wrapper.delegate = self.delegate

expect(self.paymentQueue.observers).to(haveCount(1))
expect(self.paymentQueue.observers.onlyElement) === self.wrapper
}

func testResettingDelegateClearsPaymentQueueDelegate() {
self.wrapper.delegate = self.delegate
self.wrapper.delegate = nil

expect(self.paymentQueue.delegate).to(beNil())
}

func testResettingDelegateRemovesTransactionObserver() {
self.wrapper.delegate = self.delegate
self.wrapper.delegate = nil

expect(self.paymentQueue.observers).to(beEmpty())
}

}

private final class WrapperDelegate: NSObject, PaymentQueueWrapperDelegate {

override init() {}

func paymentQueueWrapper(
_ wrapper: RevenueCat.PaymentQueueWrapper,
shouldAddStorePayment payment: SKPayment,
for product: RevenueCat.SK1Product
) -> Bool {
fail("Unexpected call")
return false
}

var paymentQueueWrapperShouldShowPriceConsent: Bool {
fail("Unexpected call")
return false
}

}
33 changes: 0 additions & 33 deletions Tests/UnitTests/Purchasing/StoreKit1WrapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,6 @@ import XCTest

@testable import RevenueCat

class MockPaymentQueue: SKPaymentQueue {

var addedPayments: [SKPayment] = []
override func add(_ payment: SKPayment) {
addedPayments.append(payment)
}

var observers: [SKPaymentTransactionObserver] = []
override func add(_ observer: SKPaymentTransactionObserver) {
observers.append(observer)
}

override func remove(_ observer: SKPaymentTransactionObserver) {
let index = observers.firstIndex { $0 === observer }
observers.remove(at: index!)
}

var finishedTransactions: [SKPaymentTransaction] = []
override func finishTransaction(_ transaction: SKPaymentTransaction) {
finishedTransactions.append(transaction)
}

#if os(iOS) || targetEnvironment(macCatalyst)
@available(iOS 13.4, macCatalyst 13.4, *)
func simulatePaymentQueueShouldShowPriceConsent() -> [Bool] {
return self.observers
.compactMap { $0 as? SKPaymentQueueDelegate }
.compactMap { $0.paymentQueueShouldShowPriceConsent?(self) }
}
#endif

}

class StoreKit1WrapperTests: TestCase, StoreKit1WrapperDelegate {
let paymentQueue = MockPaymentQueue()

Expand Down

0 comments on commit 7dd98ee

Please sign in to comment.