Skip to content

Commit

Permalink
getPromotionalOffer: return ErrorCode.ineligibleError if receipt …
Browse files Browse the repository at this point in the history
…is not found

Fixes #2580.
Promotional offers require a receipt. If there is none (like in sandbox), it means no purchases were found, which implies that the user is not eligible.
  • Loading branch information
NachoSoto committed Jun 20, 2023
1 parent c9b36e5 commit 6aa1a64
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 4 deletions.
13 changes: 9 additions & 4 deletions Sources/Purchasing/Purchases/PurchasesOrchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,15 @@ final class PurchasesOrchestrator {
return
}

receiptFetcher.receiptData(refreshPolicy: .onlyIfEmpty) { receiptData, receiptURL in
guard let receiptData = receiptData,
!receiptData.isEmpty else {
completion(.failure(ErrorUtils.missingReceiptFileError(receiptURL)))
self.receiptFetcher.receiptData(refreshPolicy: .onlyIfEmpty) { receiptData, receiptURL in
guard let receiptData = receiptData, !receiptData.isEmpty else {
let underlyingError = ErrorUtils.missingReceiptFileError(receiptURL)

// Promotional offers require existing purchases.
// If no receipt is found, this is most likely in sandbox with no purchases,
// so producing an "ineligible" error is better.
completion(.failure(ErrorUtils.ineligibleError(error: underlyingError)))

return
}

Expand Down
11 changes: 11 additions & 0 deletions Tests/BackendIntegrationTests/StoreKitIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,17 @@ class StoreKit1IntegrationTests: BaseStoreKitIntegrationTests {
try await subscribe()
}

func testGetPromotionalOfferWithNoPurchasesReturnsIneligible() async throws {
let product = try await self.monthlyPackage.storeProduct
let discount = try XCTUnwrap(product.discounts.onlyElement)

do {
_ = try await Purchases.shared.promotionalOffer(forProductDiscount: discount, product: product)
} catch {
expect(error).to(matchError(ErrorCode.ineligibleError))
}
}

func testUserHasNoEligibleOffersByDefault() async throws {
let (_, created) = try await Purchases.shared.logIn(UUID().uuidString)
expect(created) == true
Expand Down
26 changes: 26 additions & 0 deletions Tests/StoreKitUnitTests/PurchasesOrchestratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,32 @@ class PurchasesOrchestratorTests: StoreKitConfigTestCase {
expect(self.offerings.invokedPostOfferParameters?.offerIdentifier) == storeProductDiscount.offerIdentifier
}

func testGetPromotionalOfferFailsWithIneligibleIfNoReceiptIsFound() async throws {
self.receiptFetcher.shouldReturnReceipt = false

let product = try await self.fetchSk1Product()
let storeProductDiscount = MockStoreProductDiscount(offerIdentifier: "offerid1",
currencyCode: product.priceLocale.currencyCode,
price: 11.1,
localizedPriceString: "$11.10",
paymentMode: .payAsYouGo,
subscriptionPeriod: .init(value: 1, unit: .month),
numberOfPeriods: 2,
type: .promotional)

do {
_ = try await Async.call { completion in
self.orchestrator.promotionalOffer(forProductDiscount: storeProductDiscount,
product: StoreProduct(sk1Product: product),
completion: completion)
}
} catch {
expect(error).to(matchError(ErrorCode.ineligibleError))
}

expect(self.offerings.invokedPostOffer) == false
}

func testGetSK1PromotionalOfferFailsWithIneligibleDiscount() async throws {
self.customerInfoManager.stubbedCachedCustomerInfoResult = mockCustomerInfo
self.offerings.stubbedPostOfferCompletionResult = .failure(
Expand Down

0 comments on commit 6aa1a64

Please sign in to comment.