Skip to content

Commit

Permalink
StoreKit 1: changed result of cancelled purchases to be consistent …
Browse files Browse the repository at this point in the history
…with `StoreKit 2` (#1910)

Fixes [CSDK-464]. Follow up to #1869.

SK1 and SK2 had inconsistent behavior during cancelled purchases.

### Before this PR:
- SK1 / completion-block: no `CustomerInfo` + cancelled +
`ErrorCode.purchaseCancelledError`
- **SK1 / `async`: thrown `ErrorCode.purchaseCancelledError`** ⚠️
- SK2 / completion-block: `CustomerInfo` + cancelled +
`ErrorCode.purchaseCancelledError`
- SK2 / `async`: `CustomerInfo` + cancelled (not error)

### After this PR:

The output is now consistent across the board:

- SK1 / completion-block: `CustomerInfo` + cancelled +
`ErrorCode.purchaseCancelledError`
- SK1 / `async`: `CustomerInfo` + cancelled (not error)
- SK2 / completion-block: `CustomerInfo` + cancelled +
`ErrorCode.purchaseCancelledError`
- SK2 / `async`: `CustomerInfo` + cancelled (not error)

## Notes

This is of course a somewhat backwards incompatible change, but this
output is now what makes the most sense based on the types. **This would
only break existing code if users are relying on the existence of
`CustomerInfo` alone without checking `error` or `userCancelled`.**

- Completion-block API:

There's 2 ways of detecting cancellation, both valid and reasonable.
Either checking that `cancelled` is `true`, or comparing `error` with
`ErrorCode.purchaseCancelledError`

- `async` API:

A cancelled purchase no longer throws. This didn't make sense in SK1,
because it meant that `userCancelled` was always `false` in the case
that the purchase method returned. This also matches `StoreKit`'s
behavior in `Product.purchase()`

[CSDK-464]:
https://revenuecats.atlassian.net/browse/CSDK-464?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
NachoSoto authored Sep 22, 2022
1 parent 0ee540a commit 6d176f0
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 12 deletions.
24 changes: 19 additions & 5 deletions Sources/Purchasing/Purchases/PurchasesOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -697,11 +697,25 @@ private extension PurchasesOrchestrator {
let completion = self.getAndRemovePurchaseCompletedCallback(forTransaction: storeTransaction) {
let purchasesError = ErrorUtils.purchasesError(withSKError: error)

self.operationDispatcher.dispatchOnMainActor {
completion(storeTransaction,
nil,
purchasesError.asPublicError,
purchasesError.isCancelledError)
let isCancelled = purchasesError.isCancelledError

if isCancelled {
self.customerInfoManager.customerInfo(appUserID: self.appUserID,
fetchPolicy: .cachedOrFetched) { customerInfo in
self.operationDispatcher.dispatchOnMainActor {
completion(storeTransaction,
customerInfo.value,
purchasesError.asPublicError,
true)
}
}
} else {
self.operationDispatcher.dispatchOnMainActor {
completion(storeTransaction,
nil,
purchasesError.asPublicError,
false)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ class PurchasesPurchasingTests: BasePurchasesTests {
expect(receivedUserCancelled).toEventuallyNot(beNil())

expect(receivedTransaction).toNot(beNil())
expect(receivedCustomerInfo).to(beNil())
expect(receivedCustomerInfo).toNot(beNil())
expect(receivedUserCancelled) == true
expect(receivedError).to(matchError(ErrorCode.purchaseCancelledError))
expect(receivedUnderlyingError?.domain) == SKErrorDomain
Expand All @@ -334,13 +334,13 @@ class PurchasesPurchasingTests: BasePurchasesTests {

let product = StoreProduct(sk1Product: MockSK1Product(mockProductIdentifier: "com.product.id1"))

var result: PurchaseResultData?
var receivedError: NSError?

_ = Task {
// Need to do this async so the code below can invoke the `updatedTransaction` delegate method.
_ = Task<Void, Never> {
do {
_ = try await self.purchases.purchase(
product: product
)
result = try await self.purchases.purchase(product: product)
} catch {
receivedError = error as NSError
}
Expand All @@ -354,8 +354,11 @@ class PurchasesPurchasingTests: BasePurchasesTests {
transaction.mockError = NSError(domain: SKErrorDomain, code: SKError.Code.paymentCancelled.rawValue)
self.storeKit1Wrapper.delegate?.storeKit1Wrapper(self.storeKit1Wrapper, updatedTransaction: transaction)

expect(receivedError).toEventuallyNot(beNil())
expect(receivedError).to(matchError(ErrorCode.purchaseCancelledError))
expect(result).toEventuallyNot(beNil())
expect(result?.customerInfo).toNot(beNil())
expect(result?.transaction).toNot(beNil())
expect(result?.userCancelled) == true
expect(receivedError).to(beNil())
}

func testDoNotSendEmptyReceiptWhenMakingPurchase() {
Expand Down

0 comments on commit 6d176f0

Please sign in to comment.