Offer Code Redemption
Offer codes (also known as promo codes or redemption codes) allow users to redeem special offers for in-app purchases and subscriptions.
Current API boundary: Call the cross-platform openRedeemOfferCode on both platforms. It returns the verified Purchase produced by Apple's new StoreKit API on iOS 27, Mac Catalyst 27, and visionOS 27 or later (Xcode 27+ builds). Every other flow resolves null: earlier iOS and visionOS runtimes present Apple's system sheet, Google Play opens the Play Store redeem page, and Meta Horizon and Amazon Appstore launch nothing. Observe the redeemed purchase through purchaseUpdatedListener or an explicit available-purchases refresh. Mac Catalyst 16–26 instead throws StoreKitError.unknown, and the Catalyst 15 StoreKit 1 call has no effect. The suffixed presentCodeRedemptionSheetIOS and openRedeemOfferCodeAndroid are deprecated and scheduled for removal in OpenIAP 4.0.
Platform Differences#
| Platform | Method | Description |
|---|---|---|
| iOS | openRedeemOfferCode | Present Apple's system redemption sheet in the app |
| Android one-time-use code | openRedeemOfferCode | Open the Google Play redemption page |
| Android subscription custom code | Normal requestPurchase subscription flow | Let the user enter the custom code in the Google Play purchase sheet; the redemption URL does not support this code type |
Platform Implementation#
iOS#
Initialize the store connection before presenting the sheet, and register a purchase listener before the user redeems a code. Builds made with Xcode 27+ return the verified redeemed purchase directly on Apple 27+ runtimes. Earlier iOS and visionOS paths return null after presenting the system sheet, and the redeemed transaction arrives through the listener or a subsequent refresh. Mac Catalyst 16–26 rejects the StoreKit 2 scene API with StoreKitError.unknown; its StoreKit 1 call on Catalyst 15 has no effect. iOS 15 keeps the functional StoreKit 1 fallback.
import OpenIAP
@MainActor
final class RedemptionManager {
enum RedemptionError: Error { case storeConnectionFailed }
private let iapStore = OpenIapStore()
func start() async throws {
iapStore.onPurchaseSuccess = { purchase in
print("Redeemed product: (purchase.productId)")
}
try await iapStore.initConnection()
guard iapStore.isConnected else {
throw RedemptionError.storeConnectionFailed
}
}
func redeemCode() async throws -> PurchaseIOS? {
try await iapStore.openRedeemOfferCode()
}
func stop() async {
do {
try await iapStore.endConnection()
} catch {
print("Store teardown failed: (error.localizedDescription)")
}
}
}Testing#
- Use Xcode 27 and StoreKit Testing to compile and exercise the new result path in a simulator
- The pre-built Godot GDExtension must also have been compiled with Xcode 27; an Xcode 26-built framework uses the older API path even on Apple 27. On iOS and visionOS it returns
null; on Mac Catalyst it follows the platform-specific error or no-op behavior above. Build from source or confirm the release artifact toolchain. - Use a physical device for an actual App Store sandbox or TestFlight redemption
- Generate test codes in App Store Connect
- Use a sandbox account or TestFlight build