Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## NEXT

* Fixes StoreKit 2 purchase flow to send cancelled/pending/unverified results to `purchaseStream`.
* Fixes StoreKit 2 consumable purchases being reported as restored, which left transactions unfinished and blocked repeat buys.

## 0.4.7+1

* Fixes Xcode 26.2 analyzer warnings in example app tests.

## 0.4.7
Expand All @@ -18,7 +23,7 @@
## 0.4.6

* Adds a new case `.unverified` to enum `SK2ProductPurchaseResult`
* Fixes the StoreKit2 implementation throwing `PlatformException`s instead of returning the corresponding
* Fixes the StoreKit2 implementation throwing `PlatformException`s instead of returning the corresponding
`SK2ProductPurchaseResult` when a purchase is cancelled / unverified / pending.

## 0.4.5
Expand Down Expand Up @@ -47,7 +52,7 @@

* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
* Adds **Win Back Offers** support for StoreKit2:
- Includes new `isWinBackOfferEligible` function for eligibility verification
* Includes new `isWinBackOfferEligible` function for eligibility verification
* Adds **Promotional Offers** support in StoreKit2 purchases
* Fixes introductory pricing handling in promotional offers list in StoreKit2
* Ensures proper `appAccountToken` handling for StoreKit2 purchases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ extension InAppPurchasePlugin: InAppPurchase2API {
switch completedPurchase {
case .verified(let purchase):
self.sendTransactionUpdate(
transaction: purchase, receipt: "\(completedPurchase.jwsRepresentation)")
transaction: purchase, receipt: "\(completedPurchase.jwsRepresentation)",
isRestoring: true)
case .unverified(let failedPurchase, let error):
unverifiedPurchases[failedPurchase.id] = (
receipt: completedPurchase.jwsRepresentation, error: error
Expand Down Expand Up @@ -354,8 +355,10 @@ extension InAppPurchasePlugin: InAppPurchase2API {
}

/// Sends an transaction back to Dart. Access these transactions with `purchaseStream`
private func sendTransactionUpdate(transaction: Transaction, receipt: String? = nil) {
let transactionMessage = transaction.convertToPigeon(receipt: receipt)
private func sendTransactionUpdate(
transaction: Transaction, receipt: String? = nil, isRestoring: Bool = false
) {
let transactionMessage = transaction.convertToPigeon(receipt: receipt, isRestoring: isRestoring)
Task { @MainActor in
self.transactionCallbackAPI?.onTransactionsUpdated(newTransactions: [transactionMessage]) {
result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ extension Product.PurchaseResult {

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

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
Expand All @@ -205,7 +205,7 @@ extension Transaction {
expirationDate: expirationDate.map { dateFormatter.string(from: $0) },
purchasedQuantity: Int64(purchasedQuantity),
appAccountToken: appAccountToken?.uuidString,
restoring: receipt != nil,
restoring: isRestoring,
receiptData: receipt,
jsonRepresentation: String(decoding: jsonRepresentation, as: UTF8.self)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,47 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform {
);
}

await SK2Product.purchase(
final SK2ProductPurchaseResult result = await SK2Product.purchase(
purchaseParam.productDetails.id,
options: options,
);

// For non-success results, manually send update to the stream
// since native side only sends transaction for success case
if (result != SK2ProductPurchaseResult.success) {
final PurchaseStatus status = switch (result) {
SK2ProductPurchaseResult.userCancelled => PurchaseStatus.canceled,
SK2ProductPurchaseResult.pending => PurchaseStatus.pending,
SK2ProductPurchaseResult.unverified => PurchaseStatus.error,
SK2ProductPurchaseResult.success =>
PurchaseStatus.purchased, // won't reach here
};

final details = SK2PurchaseDetails(
productID: purchaseParam.productDetails.id,
purchaseID: null,
verificationData: PurchaseVerificationData(
localVerificationData: '',
serverVerificationData: '',
source: kIAPSource,
),
transactionDate: null,
status: status,
);

if (status == PurchaseStatus.error) {
details.error = IAPError(
source: kIAPSource,
code: kPurchaseErrorCode,
message: 'Purchase verification failed',
);
}

_sk2transactionObserver.transactionsCreatedController.add(
<PurchaseDetails>[details],
);
}

return true;
}
await _skPaymentQueueWrapper.addPayment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: in_app_purchase_storekit
description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
version: 0.4.7
version: 0.4.7+2

environment:
sdk: ^3.9.0
Expand Down