Skip to content

Commit

Permalink
Revert "[in_app_purchase_storekit] Add support for purchase and trans…
Browse files Browse the repository at this point in the history
…actions #7574" (#7886)

Reverts #7812

See #7812 (comment)
  • Loading branch information
stuartmorgan authored Oct 17, 2024
1 parent f12eda7 commit 870114d
Show file tree
Hide file tree
Showing 25 changed files with 251 additions and 1,880 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
## 0.3.18+2

* Adds support for StoreKit2's `purchase` and `transactions`

## 0.3.18+1

* Adds support for StoreKit2's `canMakePayments` and `products`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,6 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI {
// This property is optional, as it requires self to exist to be initialized.
public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol?

// This should be an Task, but Task is on available >= iOS 13
private var _updateListenerTask: Any?

@available(iOS 13.0, *)
var updateListenerTask: Task<(), Never> {
return self._updateListenerTask as! Task<(), Never>
}

@available(iOS 13.0, *)
func setListenerTaskAsTask(task: Task<(), Never>) {
self._updateListenerTask = task
}

var transactionCallbackAPI: InAppPurchase2CallbackAPI? = nil

public static func register(with registrar: FlutterPluginRegistrar) {
#if os(iOS)
let messenger = registrar.messenger()
Expand Down Expand Up @@ -108,7 +93,6 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI {
let messenger = registrar.messenger
#endif
setupTransactionObserverChannelIfNeeded(withMessenger: messenger)
self.transactionCallbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: messenger)
}

// MARK: - Pigeon Functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

@available(iOS 15.0, macOS 12.0, *)
extension InAppPurchasePlugin: InAppPurchase2API {

// MARK: - Pigeon Functions

/// Wrapper method around StoreKit2's canMakePayments() method
/// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
// Wrapper method around StoreKit2's canMakePayments() method
// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
func canMakePayments() throws -> Bool {
return AppStore.canMakePayments
}

/// Wrapper method around StoreKit2's products() method
/// https://developer.apple.com/documentation/storekit/product/3851116-products
// Wrapper method around StoreKit2's products() method
// https://developer.apple.com/documentation/storekit/product/3851116-products
func products(
identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void
) {
Expand All @@ -35,146 +34,4 @@ extension InAppPurchasePlugin: InAppPurchase2API {
}
}
}

/// Gets the appropriate product, then calls purchase on it.
/// https://developer.apple.com/documentation/storekit/product/3791971-purchase
func purchase(
id: String, options: SK2ProductPurchaseOptionsMessage?,
completion: @escaping (Result<SK2ProductPurchaseResultMessage, Error>) -> Void
) {
Task { @MainActor in
do {
guard let product = try await Product.products(for: [id]).first else {
let error = PigeonError(
code: "storekit2_failed_to_fetch_product",
message: "Storekit has failed to fetch this product.",
details: "Product ID : \(id)")
return completion(.failure(error))
}

let result = try await product.purchase(options: [])

switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
self.sendTransactionUpdate(transaction: transaction)
completion(.success(result.convertToPigeon()))
case .unverified(_, let error):
completion(.failure(error))
}
case .pending:
completion(
.failure(
PigeonError(
code: "storekit2_purchase_pending",
message:
"This transaction is still pending and but may complete in the future. If it completes, it will be delivered via `purchaseStream`",
details: "Product ID : \(id)")))
case .userCancelled:
completion(
.failure(
PigeonError(
code: "storekit2_purchase_cancelled",
message: "This transaction has been cancelled by the user.",
details: "Product ID : \(id)")))
@unknown default:
fatalError("An unknown StoreKit PurchaseResult has been encountered.")
}
} catch {
completion(.failure(error))
}
}
}

/// Wrapper method around StoreKit2's transactions() method
/// https://developer.apple.com/documentation/storekit/product/3851116-products
func transactions(
completion: @escaping (Result<[SK2TransactionMessage], Error>) -> Void
) {
Task {
@MainActor in
do {
let transactionsMsgs = await rawTransactions().map {
$0.convertToPigeon()
}
completion(.success(transactionsMsgs))
}
}
}

/// Wrapper method around StoreKit2's finish() method https://developer.apple.com/documentation/storekit/transaction/3749694-finish
func finish(id: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
Task {
let transaction = try await fetchTransaction(by: UInt64(id))
if let transaction = transaction {
await transaction.finish()
}
}
}

/// This Task listens to Transation.updates as shown here
/// https://developer.apple.com/documentation/storekit/transaction/3851206-updates
/// This function should be called as soon as the app starts to avoid missing any Transactions done outside of the app.
func startListeningToTransactions() throws {
self.setListenerTaskAsTask(
task: Task { [weak self] in
for await verificationResult in Transaction.updates {
switch verificationResult {
case .verified(let transaction):
self?.sendTransactionUpdate(transaction: transaction)
case .unverified:
break
}
}
})
}

/// Stop subscribing to Transaction.updates
func stopListeningToTransactions() throws {
updateListenerTask.cancel()
}

/// Sends an transaction back to Dart. Access these transactions with `purchaseStream`
func sendTransactionUpdate(transaction: Transaction) {
let transactionMessage = transaction.convertToPigeon()
transactionCallbackAPI?.onTransactionsUpdated(newTransaction: transactionMessage) { result in
switch result {
case .success: break
case .failure(let error):
print("Failed to send transaction updates: \(error)")
}
}
}

// MARK: - Convenience Functions

/// Helper function that fetches and unwraps all verified transactions
private func rawTransactions() async -> [Transaction] {
var transactions: [Transaction] = []
for await verificationResult in Transaction.all {
switch verificationResult {
case .verified(let transaction):
transactions.append(transaction)
case .unverified:
break
}
}
return transactions
}

/// Helper function to fetch specific transaction
private func fetchTransaction(by id: UInt64) async throws -> Transaction? {
for await result in Transaction.all {
switch result {
case .verified(let transaction):
if transaction.id == id {
return transaction
}
case .unverified:
continue
}
}
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,38 +167,3 @@ extension SK2PriceLocaleMessage: Equatable {
return lhs.currencyCode == rhs.currencyCode && lhs.currencySymbol == rhs.currencySymbol
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Product.PurchaseResult {
func convertToPigeon() -> SK2ProductPurchaseResultMessage {
switch self {
case .success(_):
return SK2ProductPurchaseResultMessage.success
case .userCancelled:
return SK2ProductPurchaseResultMessage.userCancelled
case .pending:
return SK2ProductPurchaseResultMessage.pending
@unknown default:
fatalError()
}
}
}

@available(iOS 15.0, macOS 12.0, *)
extension Transaction {
func convertToPigeon(restoring: Bool = false) -> SK2TransactionMessage {

let dateFromatter: DateFormatter = DateFormatter()
dateFromatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

return SK2TransactionMessage(
id: Int64(id),
originalId: Int64(originalID),
productId: productID,
purchaseDate: dateFromatter.string(from: purchaseDate),
purchasedQuantity: Int64(purchasedQuantity),
appAccountToken: appAccountToken?.uuidString,
restoring: restoring
)
}
}
Loading

0 comments on commit 870114d

Please sign in to comment.