Files
purchases-ios/Sources/Purchasing/StoreKit2/SK2BeginRefundRequestHelper.swift
T
aboedo f5697de730 renamed Purchases folder to Sources (#1416)
* renamed `Purchases` folder to `Sources`

* temporarily enabling installation test jobs

* re-disabling installation test jobs
2022-03-28 11:53:24 -03:00

139 lines
5.3 KiB
Swift

//
// 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
//
// SK2BeginRefundRequestHelper.swift
//
// Created by Madeline Beyl on 10/21/21.
#if os(iOS) || targetEnvironment(macCatalyst)
import Foundation
import StoreKit
import UIKit
/// Helper class responsible for calling into StoreKit2 and translating results/errors for consumption by RevenueCat.
@available(iOS 15.0, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
class SK2BeginRefundRequestHelper {
/// Calls `initiateSK2RefundRequest` and maps the result for consumption by `BeginRefundRequestHelper`
@MainActor
func initiateRefundRequest(transactionID: UInt64, windowScene: UIWindowScene) async throws -> RefundRequestStatus {
let sk2Result = await initiateSK2RefundRequest(transactionID: transactionID, windowScene: windowScene)
return try mapSk2Result(from: sk2Result)
}
/* Checks with StoreKit2 that the given `productID` has an existing verified transaction, and maps the
* result for consumption by `BeginRefundRequestHelper`.
*/
func verifyTransaction(productID: String) async throws -> UInt64 {
let result = await StoreKit.Transaction.latest(for: productID)
guard let nonNilResult = result else {
let errorMessage = Strings.purchase.product_unpurchased_or_missing.description
Logger.error(errorMessage)
throw ErrorUtils.beginRefundRequestError(withMessage: errorMessage)
}
switch nonNilResult {
case .unverified(_, let verificationError):
let message = Strings.purchase.transaction_unverified(
productID: productID,
errorMessage: verificationError.localizedDescription).description
Logger.error(message)
throw ErrorUtils.beginRefundRequestError(withMessage: message)
case .verified(let transaction): return transaction.id
}
}
/*
* Attempts to begin a refund request for the given transactionID and current windowScene with StoreKit2.
* If successful, passes result on as-is. If unsuccessful, calls `getErrorMessage` to add more
* descriptive details to the error.
*
* This function allows for us to mock the StoreKit2 response in unit tests.
*/
@MainActor
func initiateSK2RefundRequest(transactionID: UInt64, windowScene: UIWindowScene) async ->
Result<StoreKit.Transaction.RefundRequestStatus, Error> {
do {
let sk2Status = try await StoreKit.Transaction.beginRefundRequest(for: transactionID, in: windowScene)
return .success(sk2Status)
} catch {
let message = getErrorMessage(from: error)
Logger.error(message)
return .failure(ErrorUtils.beginRefundRequestError(withMessage: message, error: error))
}
}
}
@available(iOS 15.0, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
private extension SK2BeginRefundRequestHelper {
func getErrorMessage(from sk2Error: Error?) -> String {
let details = sk2Error?.localizedDescription ?? "No extra info"
if let skError = sk2Error as? StoreKit.Transaction.RefundRequestError {
switch skError {
case .duplicateRequest:
return Strings.purchase.duplicate_refund_request(details: details).description
case .failed:
return Strings.purchase.failed_refund_request(details: details).description
@unknown default:
return Strings.purchase.unknown_refund_request_error_type(details: details).description
}
} else {
return Strings.purchase.unknown_refund_request_error(details: details).description
}
}
/*
* - Parameter sk2Result: The Result returned from StoreKit2
* - Returns The result expected by `BeginRefundRequestHelper`, converting from a StoreKit RefundRequestStatus
* to our `RefundRequestStatus` type and adding more descriptive error messages where needed.
*/
func mapSk2Result(from sk2Result: Result<StoreKit.Transaction.RefundRequestStatus, Error>) throws ->
RevenueCat.RefundRequestStatus {
switch sk2Result {
case .success(let sk2Status):
guard let rcStatus = RefundRequestStatus.from(sk2RefundRequestStatus: sk2Status) else {
let message = Strings.purchase.unknown_refund_request_status.description
Logger.error(message)
throw ErrorUtils.beginRefundRequestError(
withMessage: message)
}
return rcStatus
case .failure(let error):
Logger.error(error.localizedDescription)
throw error
}
}
}
@available(iOS 15.0, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
private extension RefundRequestStatus {
static func from(sk2RefundRequestStatus status: StoreKit.Transaction.RefundRequestStatus) -> RefundRequestStatus? {
switch status {
case .userCancelled:
return .userCancelled
case .success:
return .success
@unknown default:
return nil
}
}
}
#endif