Skip to content

Commit 4e01eb3

Browse files
hyochanclaude
andcommitted
feat: add getAllTransactionsIOS wrappers and docs improvements
Framework Libraries: - Add getAllTransactionsIOS wrapper to react-native-iap, expo-iap, flutter_inapp_purchase, and godot-iap Docs: - Fix button border clipping and dark mode text (.btn exclusion from doc-page link styles, including dark mode overrides) - Fix SPM badge tag filter (apple-v* -> 2.*) - Add Claude Code and Codex to operational costs, domain -> ~$3 - Expand "The Problem" with Horizon OS, Vega OS, HarmonyOS, Fire OS, Galaxy Store, Huawei AppGallery, Onside (with official links) - Add Foundation draft notice to all foundation pages - Add framework library patch releases to release notes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cdee78d commit 4e01eb3

18 files changed

Lines changed: 367 additions & 28 deletions

File tree

libraries/expo-iap/ios/ExpoIapModule.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ public final class ExpoIapModule: Module {
148148
return sanitized
149149
}
150150

151+
AsyncFunction("getAllTransactionsIOS") { () async throws -> [[String: Any]] in
152+
ExpoIapLog.payload("getAllTransactionsIOS", payload: nil)
153+
let all = try await OpenIapModule.shared.getAllTransactionsIOS()
154+
let sanitized = all.map { ExpoIapHelper.sanitizeDictionary(OpenIapSerialization.encode($0)) }
155+
ExpoIapLog.result("getAllTransactionsIOS", value: sanitized)
156+
return sanitized
157+
}
158+
151159
AsyncFunction("clearTransactionIOS") { () async throws -> Bool in
152160
ExpoIapLog.payload("clearTransactionIOS", payload: nil)
153161
let success = try await OpenIapModule.shared.clearTransactionIOS()

libraries/expo-iap/src/modules/ios.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,13 @@ export const getPendingTransactionsIOS: QueryField<
357357
return (transactions ?? []) as PurchaseIOS[];
358358
};
359359

360+
export const getAllTransactionsIOS: QueryField<
361+
'getAllTransactionsIOS'
362+
> = async () => {
363+
const transactions = await ExpoIapModule.getAllTransactionsIOS();
364+
return (transactions ?? []) as PurchaseIOS[];
365+
};
366+
360367
/**
361368
* Clear a specific transaction (iOS only).
362369
*

libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ public class FlutterInappPurchasePlugin: NSObject, FlutterPlugin {
142142
case "getPendingTransactionsIOS":
143143
getPendingTransactionsIOS(result: result)
144144

145+
case "getAllTransactionsIOS":
146+
getAllTransactionsIOS(result: result)
147+
145148
case "requestPurchaseOnPromotedProductIOS":
146149
requestPurchaseOnPromotedProductIOS(result: result)
147150

@@ -663,6 +666,23 @@ public class FlutterInappPurchasePlugin: NSObject, FlutterPlugin {
663666
}
664667
}
665668

669+
private func getAllTransactionsIOS(result: @escaping FlutterResult) {
670+
Task { @MainActor in
671+
do {
672+
let all = try await OpenIapModule.shared.getAllTransactionsIOS()
673+
let purchases = all.map { Purchase.purchaseIos($0) }
674+
let serialized = FlutterIapHelper.sanitizeArray(OpenIapSerialization.purchases(purchases))
675+
FlutterIapLog.result("getAllTransactionsIOS", value: serialized)
676+
result(serialized)
677+
} catch {
678+
await MainActor.run {
679+
let code: ErrorCode = .serviceError
680+
result(FlutterError(code: code.rawValue, message: defaultMessage(for: code), details: nil))
681+
}
682+
}
683+
}
684+
}
685+
666686
private func clearTransactionIOS(result: @escaping FlutterResult) {
667687
FlutterIapLog.debug("clearTransactionIOS called")
668688
Task { @MainActor in

libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,26 @@ class FlutterInappPurchase with RequestPurchaseBuilderApi {
964964
return const <gentype.PurchaseIOS>[];
965965
};
966966

967+
gentype.QueryGetAllTransactionsIOSHandler get getAllTransactionsIOS =>
968+
() async {
969+
if (_platform.isIOS || _platform.isMacOS) {
970+
final dynamic result = await _channel.invokeMethod(
971+
'getAllTransactionsIOS',
972+
);
973+
final purchases = extractPurchases(
974+
result,
975+
platformIsAndroid: _platform.isAndroid,
976+
platformIsIOS: _platform.isIOS || _platform.isMacOS,
977+
acknowledgedAndroidPurchaseTokens:
978+
_acknowledgedAndroidPurchaseTokens,
979+
);
980+
return purchases.whereType<gentype.PurchaseIOS>().toList(
981+
growable: false,
982+
);
983+
}
984+
return const <gentype.PurchaseIOS>[];
985+
};
986+
967987
gentype.MutationAcknowledgePurchaseAndroidHandler
968988
get acknowledgePurchaseAndroid => (purchaseToken) async {
969989
if (!_platform.isAndroid) {
@@ -2254,6 +2274,7 @@ class FlutterInappPurchase with RequestPurchaseBuilderApi {
22542274
getAvailablePurchases: getAvailablePurchases,
22552275
getExternalPurchaseCustomLinkTokenIOS:
22562276
getExternalPurchaseCustomLinkTokenIOS,
2277+
getAllTransactionsIOS: getAllTransactionsIOS,
22572278
getPendingTransactionsIOS: getPendingTransactionsIOS,
22582279
getPromotedProductIOS: getPromotedProductIOS,
22592280
getStorefront: getStorefront,

libraries/godot-iap/addons/godot-iap/godot_iap.gd

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,23 @@ func get_pending_transactions_ios() -> Array:
641641
purchases.append(Types.PurchaseIOS.from_dict(tx))
642642
return purchases
643643

644+
## Get all transactions including finished consumables (iOS only).
645+
## Requires SK2ConsumableTransactionHistory Info.plist key for finished consumables (iOS 18+).
646+
## @return Array of Types.PurchaseIOS
647+
func get_all_transactions_ios() -> Array:
648+
var purchases: Array = []
649+
if _native_plugin and _platform == "iOS":
650+
var result_json = _native_plugin.call("getAllTransactionsIOS")
651+
var result = JSON.parse_string(result_json)
652+
if result is Dictionary and result.get("success", false):
653+
var transactions_json = result.get("transactionsJson", "[]")
654+
var transactions = JSON.parse_string(transactions_json)
655+
if transactions is Array:
656+
for tx in transactions:
657+
if tx is Dictionary:
658+
purchases.append(Types.PurchaseIOS.from_dict(tx))
659+
return purchases
660+
644661
## Present code redemption sheet (iOS only).
645662
## @return Types.VoidResult
646663
func present_code_redemption_sheet_ios() -> Variant:

libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,12 @@ class HybridRnIap : HybridRnIapSpec() {
15461546
}
15471547
}
15481548

1549+
override fun getAllTransactionsIOS(): Promise<Array<NitroPurchase>> {
1550+
return Promise.async {
1551+
throw OpenIapException(toErrorJson(OpenIAPError.FeatureNotSupported()))
1552+
}
1553+
}
1554+
15491555
override fun syncIOS(): Promise<Boolean> {
15501556
return Promise.async {
15511557
throw OpenIapException(toErrorJson(OpenIAPError.FeatureNotSupported()))

libraries/react-native-iap/ios/HybridRnIap.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,32 @@ class HybridRnIap: HybridRnIapSpec {
717717
}
718718
}
719719

720+
func getAllTransactionsIOS() throws -> Promise<[NitroPurchase]> {
721+
return Promise.async {
722+
do {
723+
RnIapLog.payload("getAllTransactionsIOS", nil)
724+
let all = try await OpenIapModule.shared.getAllTransactionsIOS()
725+
var unionPurchases: [OpenIAP.Purchase] = []
726+
for purchase in all {
727+
let union = OpenIAP.Purchase.purchaseIos(purchase)
728+
unionPurchases.append(union)
729+
let raw = OpenIapSerialization.purchase(union)
730+
if let identifier = raw["id"] as? String {
731+
await MainActor.run {
732+
self.purchasePayloadById[identifier] = raw
733+
}
734+
}
735+
}
736+
let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(unionPurchases))
737+
RnIapLog.result("getAllTransactionsIOS", payloads)
738+
return payloads.map { RnIapHelper.convertPurchaseDictionary($0) }
739+
} catch {
740+
RnIapLog.failure("getAllTransactionsIOS", error: error)
741+
return []
742+
}
743+
}
744+
}
745+
720746
func syncIOS() throws -> Promise<Bool> {
721747
return Promise.async {
722748
do {

libraries/react-native-iap/src/__tests__/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const mockIap: any = {
3838
requestPromotedProductIOS: jest.fn(async () => null),
3939
buyPromotedProductIOS: jest.fn(async () => undefined),
4040
presentCodeRedemptionSheetIOS: jest.fn(async () => true),
41+
getAllTransactionsIOS: jest.fn(async () => []),
4142

4243
// Unified storefront
4344
getStorefront: jest.fn(async () => 'USA'),

libraries/react-native-iap/src/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,32 @@ export const getPendingTransactionsIOS: QueryField<
10801080
}
10811081
};
10821082

1083+
export const getAllTransactionsIOS: QueryField<
1084+
'getAllTransactionsIOS'
1085+
> = async () => {
1086+
if (Platform.OS !== 'ios') {
1087+
return [];
1088+
}
1089+
1090+
try {
1091+
const nitroPurchases = await IAP.instance.getAllTransactionsIOS();
1092+
return nitroPurchases
1093+
.map(convertNitroPurchaseToPurchase)
1094+
.filter(
1095+
(purchase): purchase is PurchaseIOS => purchase.platform === 'ios',
1096+
);
1097+
} catch (error) {
1098+
RnIapConsole.error('[getAllTransactionsIOS] Failed:', error);
1099+
const parsedError = parseErrorStringToJsonObj(error);
1100+
throw createPurchaseError({
1101+
code: parsedError.code,
1102+
message: parsedError.message,
1103+
responseCode: parsedError.responseCode,
1104+
debugMessage: parsedError.debugMessage,
1105+
});
1106+
}
1107+
};
1108+
10831109
export const showManageSubscriptionsIOS: MutationField<
10841110
'showManageSubscriptionsIOS'
10851111
> = async () => {

libraries/react-native-iap/src/specs/RnIap.nitro.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,14 @@ export interface RnIap extends HybridObject<{ios: 'swift'; android: 'kotlin'}> {
857857
*/
858858
getPendingTransactionsIOS(): Promise<NitroPurchase[]>;
859859

860+
/**
861+
* Get the full StoreKit 2 transaction history as PurchaseIOS values.
862+
* Requires SK2ConsumableTransactionHistory Info.plist key for finished consumables (iOS 18+).
863+
* @returns Promise<NitroPurchase[]> - Array of all transactions
864+
* @platform iOS
865+
*/
866+
getAllTransactionsIOS(): Promise<NitroPurchase[]>;
867+
860868
/**
861869
* Sync with the App Store (iOS only)
862870
* @returns Promise<boolean> - Success flag

0 commit comments

Comments
 (0)