Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/apple/Sources/Helpers/StoreKitTypesBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ enum StoreKitTypesBridge {
renewalInfoIOS: renewalInfoIOS,
revocationDateIOS: revocationDate,
revocationReasonIOS: transaction.revocationReason?.rawValue.description,
store: .apple,
storefrontCountryCodeIOS: {
if #available(iOS 17.0, tvOS 17.0, watchOS 10.0, *) {
transaction.storefront.countryCode
Expand Down
58 changes: 48 additions & 10 deletions packages/apple/Sources/Models/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,18 @@ public enum IapkitPurchaseState: String, Codable, CaseIterable {
case inauthentic = "inauthentic"
}

public enum IapkitStore: String, Codable, CaseIterable {
case apple = "apple"
case google = "google"
}

public enum IapPlatform: String, Codable, CaseIterable {
case ios = "ios"
case android = "android"
}

public enum IapStore: String, Codable, CaseIterable {
case unknown = "unknown"
case apple = "apple"
case google = "google"
case horizon = "horizon"
}

public enum PaymentModeIOS: String, Codable, CaseIterable {
case empty = "empty"
case freeTrial = "free-trial"
Expand Down Expand Up @@ -266,12 +268,15 @@ public protocol PurchaseCommon: Codable {
var id: String { get }
var ids: [String]? { get }
var isAutoRenewing: Bool { get }
/// @deprecated Use store instead
var platform: IapPlatform { get }
var productId: String { get }
var purchaseState: PurchaseState { get }
/// Unified purchase token (iOS JWS, Android purchaseToken)
var purchaseToken: String? { get }
var quantity: Int { get }
/// Store where purchase was made
var store: IapStore { get }
var transactionDate: Double { get }
}

Expand Down Expand Up @@ -487,12 +492,15 @@ public struct PurchaseAndroid: Codable, PurchaseCommon {
public var obfuscatedAccountIdAndroid: String?
public var obfuscatedProfileIdAndroid: String?
public var packageNameAndroid: String?
/// @deprecated Use store instead
public var platform: IapPlatform
public var productId: String
public var purchaseState: PurchaseState
public var purchaseToken: String?
public var quantity: Int
public var signatureAndroid: String?
/// Store where purchase was made
public var store: IapStore
public var transactionDate: Double
public var transactionId: String?
}
Expand Down Expand Up @@ -520,6 +528,7 @@ public struct PurchaseIOS: Codable, PurchaseCommon {
public var originalTransactionDateIOS: Double?
public var originalTransactionIdentifierIOS: String?
public var ownershipTypeIOS: String?
/// @deprecated Use store instead
public var platform: IapPlatform
public var productId: String
public var purchaseState: PurchaseState
Expand All @@ -531,6 +540,8 @@ public struct PurchaseIOS: Codable, PurchaseCommon {
public var renewalInfoIOS: RenewalInfoIOS?
public var revocationDateIOS: Double?
public var revocationReasonIOS: String?
/// Store where purchase was made
public var store: IapStore
public var storefrontCountryCodeIOS: String?
public var subscriptionGroupIdIOS: String?
public var transactionDate: Double
Expand Down Expand Up @@ -591,7 +602,7 @@ public struct RequestVerifyPurchaseWithIapkitResult: Codable {
public var isValid: Bool
/// The current state of the purchase.
public var state: IapkitPurchaseState
public var store: IapkitStore
public var store: IapStore
}

public struct SubscriptionInfoIOS: Codable {
Expand Down Expand Up @@ -924,16 +935,24 @@ public struct RequestPurchaseProps: Codable {
}

public struct RequestPurchasePropsByPlatforms: Codable {
/// Android-specific purchase parameters
/// @deprecated Use google instead
public var android: RequestPurchaseAndroidProps?
/// iOS-specific purchase parameters
/// Apple-specific purchase parameters
public var apple: RequestPurchaseIosProps?
/// Google-specific purchase parameters
public var google: RequestPurchaseAndroidProps?
/// @deprecated Use apple instead
public var ios: RequestPurchaseIosProps?

public init(
android: RequestPurchaseAndroidProps? = nil,
apple: RequestPurchaseIosProps? = nil,
google: RequestPurchaseAndroidProps? = nil,
ios: RequestPurchaseIosProps? = nil
) {
self.android = android
self.apple = apple
self.google = google
self.ios = ios
}
}
Expand Down Expand Up @@ -996,16 +1015,24 @@ public struct RequestSubscriptionIosProps: Codable {
}

public struct RequestSubscriptionPropsByPlatforms: Codable {
/// Android-specific subscription parameters
/// @deprecated Use google instead
public var android: RequestSubscriptionAndroidProps?
/// iOS-specific subscription parameters
/// Apple-specific subscription parameters
public var apple: RequestSubscriptionIosProps?
/// Google-specific subscription parameters
public var google: RequestSubscriptionAndroidProps?
/// @deprecated Use apple instead
public var ios: RequestSubscriptionIosProps?

public init(
android: RequestSubscriptionAndroidProps? = nil,
apple: RequestSubscriptionIosProps? = nil,
google: RequestSubscriptionAndroidProps? = nil,
ios: RequestSubscriptionIosProps? = nil
) {
self.android = android
self.apple = apple
self.google = google
self.ios = ios
}
}
Expand Down Expand Up @@ -1339,6 +1366,7 @@ public enum Purchase: Codable, PurchaseCommon {
}
}

/// @deprecated Use store instead
public var platform: IapPlatform {
switch self {
case let .purchaseAndroid(value):
Expand Down Expand Up @@ -1385,6 +1413,16 @@ public enum Purchase: Codable, PurchaseCommon {
}
}

/// Store where purchase was made
public var store: IapStore {
switch self {
case let .purchaseAndroid(value):
return value.store
case let .purchaseIos(value):
return value.store
}
}

public var transactionDate: Double {
switch self {
case let .purchaseAndroid(value):
Expand Down
1 change: 1 addition & 0 deletions packages/apple/Sources/OpenIapModule+ObjC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ import StoreKit
reasonStringRepresentationIOS: nil,
revocationDateIOS: nil,
revocationReasonIOS: nil,
store: .apple,
storefrontCountryCodeIOS: nil,
subscriptionGroupIdIOS: nil,
transactionDate: Date().timeIntervalSince1970,
Expand Down
14 changes: 8 additions & 6 deletions packages/apple/Sources/OpenIapModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
guard props.apple != nil else {
throw makePurchaseError(code: .developerError, message: "IAPKit verification on Apple requires an apple payload")
}
let store: IapkitStore = .apple
let store: IapStore = .apple
let body = try buildIapkitPayload(props: props, store: store)

var request = URLRequest(url: url)
Expand Down Expand Up @@ -716,22 +716,22 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
let normalizedState = stateString.lowercased().replacingOccurrences(of: "_", with: "-")
let parsedState = IapkitPurchaseState(rawValue: normalizedState) ?? .unknown
let storeString = json["store"] as? String
let parsedStore = storeString.flatMap { IapkitStore(rawValue: $0) } ?? store
let parsedStore = storeString.flatMap { IapStore(rawValue: $0) } ?? store
OpenIapLog.info("IAPKit verification result: store=\(parsedStore.rawValue), isValid=\(isValid), state=\(parsedState.rawValue)")
return RequestVerifyPurchaseWithIapkitResult(isValid: isValid, state: parsedState, store: parsedStore)
}

private struct IapkitApplePayload: Codable {
let store: IapkitStore
let store: IapStore
let jws: String
}

private struct IapkitGooglePayload: Codable {
let store: IapkitStore
let store: IapStore
let purchaseToken: String
}

private func buildIapkitPayload(props: RequestVerifyPurchaseWithIapkitProps, store: IapkitStore) throws -> Data {
private func buildIapkitPayload(props: RequestVerifyPurchaseWithIapkitProps, store: IapStore) throws -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = [.withoutEscapingSlashes]
switch store {
Expand All @@ -747,7 +747,7 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
jws: apple.jws
)
return try encoder.encode(payload)
case .google:
case .google, .horizon:
guard let google = props.google else {
throw makePurchaseError(code: .developerError, message: "Google verification parameters are required")
}
Expand All @@ -759,6 +759,8 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
purchaseToken: google.purchaseToken
)
return try encoder.encode(payload)
case .unknown:
throw makePurchaseError(code: .developerError, message: "Unknown store type")
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/apple/Tests/OpenIapTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ final class OpenIapTests: XCTestCase {
renewalInfoIOS: renewalInfo,
revocationDateIOS: nil,
revocationReasonIOS: nil,
store: .apple,
storefrontCountryCodeIOS: "US",
subscriptionGroupIdIOS: "21686373",
transactionDate: 1729083955000,
Expand Down Expand Up @@ -385,6 +386,7 @@ final class OpenIapTests: XCTestCase {
renewalInfoIOS: nil,
revocationDateIOS: nil,
revocationReasonIOS: nil,
store: .apple,
storefrontCountryCodeIOS: "US",
subscriptionGroupIdIOS: "group",
transactionDate: 2,
Expand Down Expand Up @@ -422,6 +424,7 @@ final class OpenIapTests: XCTestCase {
renewalInfoIOS: renewalInfo,
revocationDateIOS: nil,
revocationReasonIOS: nil,
store: .apple,
storefrontCountryCodeIOS: "US",
subscriptionGroupIdIOS: "21686373",
transactionDate: 1729083955000,
Expand Down
2 changes: 2 additions & 0 deletions packages/apple/Tests/OpenIapTests/RenewalInfoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ final class RenewalInfoTests: XCTestCase {
renewalInfoIOS: renewalInfo,
revocationDateIOS: nil,
revocationReasonIOS: nil,
store: .apple,
storefrontCountryCodeIOS: "US",
subscriptionGroupIdIOS: "21686373",
transactionDate: 1729083955000,
Expand Down Expand Up @@ -271,6 +272,7 @@ final class RenewalInfoTests: XCTestCase {
renewalInfoIOS: renewalInfo,
revocationDateIOS: nil,
revocationReasonIOS: nil,
store: .apple,
storefrontCountryCodeIOS: "US",
subscriptionGroupIdIOS: "21686373",
transactionDate: 1729083955000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class VerifyPurchaseWithProviderTests: XCTestCase {
let result = try await store.verifyPurchaseWithProvider(props)

XCTAssertNotNil(result)
XCTAssertEqual(IapkitStore.apple, result?.store)
XCTAssertEqual(IapStore.apple, result?.store)
XCTAssertEqual(true, result?.isValid)
XCTAssertEqual(.entitled, result?.state)
}
Expand Down
14 changes: 7 additions & 7 deletions packages/docs/src/pages/docs/apis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ if (subscription?.renewalInfoIOS?.willAutoRenew === false) {

try {
await requestPurchase({
request: { ios: { sku: 'premium_monthly' } },
request: { apple: { sku: 'premium_monthly' } },
type: 'subs'
});
} catch (error) {
Expand All @@ -621,7 +621,7 @@ if let subscription = subscriptions.first {
try await OpenIapModule.shared.requestPurchase(
RequestPurchaseProps(
request: RequestPurchasePropsByPlatforms(
ios: RequestPurchaseIosProps(sku: "premium_monthly")
apple: RequestPurchaseIosProps(sku: "premium_monthly")
),
type: .subs
)
Expand All @@ -645,7 +645,7 @@ subscriptions.firstOrNull()?.let { subscription ->
openIapStore.requestPurchase(
RequestPurchaseProps(
request = RequestPurchasePropsByPlatforms(
android = RequestPurchaseAndroidProps(
google = RequestPurchaseAndroidProps(
skus = listOf("premium_monthly")
)
),
Expand All @@ -671,8 +671,8 @@ if (subscription?.renewalInfoIOS?.willAutoRenew == false) {
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps(
request: RequestPurchasePropsByPlatforms(
ios: RequestPurchaseIosProps(sku: 'premium_monthly'),
android: RequestPurchaseAndroidProps(skus: ['premium_monthly']),
apple: RequestPurchaseIosProps(sku: 'premium_monthly'),
google: RequestPurchaseAndroidProps(skus: ['premium_monthly']),
),
type: ProductType.subs,
),
Expand Down Expand Up @@ -2128,8 +2128,8 @@ const handlePurchase = async (basePlanId: string) => {
purchasedBasePlanId = basePlanId;

await requestPurchase({
request: {
android: {
requestSubscription: {
google: {
skus: [subscriptionGroupId],
subscriptionOffers: [
{ sku: subscriptionGroupId, offerToken: offer.offerToken },
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/pages/docs/features/external-purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ const purchaseSubscription = purchaseUpdatedListener(
async function handleUserChoicePurchase(productId: string) {
try {
await requestPurchase({
android: { skus: [productId] },
google: { skus: [productId] },
});

// If user selects Google Play → purchaseUpdatedListener callback
Expand Down Expand Up @@ -910,7 +910,7 @@ suspend fun handleUserChoicePurchase(productId: String) {
val props = RequestPurchaseProps(
request = RequestPurchaseProps.Request.Purchase(
RequestPurchasePropsByPlatforms(
android = RequestPurchaseAndroidProps(
google = RequestPurchaseAndroidProps(
skus = listOf(productId)
)
)
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/pages/docs/features/purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ const purchaseProduct = async (productId: string) => {
try {
await requestPurchase({
request: {
ios: { sku: productId },
android: { skus: [productId] },
apple: { sku: productId },
google: { skus: [productId] },
},
type: 'inapp', // 'inapp' for consumables/non-consumables
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ if (currentSub != null) {
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps(
request: RequestPurchasePropsByPlatforms(
android: RequestPurchaseAndroidProps(
google: RequestPurchaseAndroidProps(
skus: ['premium_monthly'],
purchaseToken: currentSub.purchaseToken,
replacementMode: 1, // WITH_TIME_PRORATION
Expand Down Expand Up @@ -1249,7 +1249,7 @@ if (premiumPurchase != null) {
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps(
request: RequestPurchasePropsByPlatforms(
android: RequestPurchaseAndroidProps(
google: RequestPurchaseAndroidProps(
skus: ['basic_monthly'],
purchaseToken: premiumPurchase.purchaseToken,
replacementMode: 6, // DEFERRED - Change at renewal
Expand Down Expand Up @@ -1536,7 +1536,7 @@ Future<void> changeSubscription(
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps(
request: RequestPurchasePropsByPlatforms(
android: RequestPurchaseAndroidProps(
google: RequestPurchaseAndroidProps(
skus: [newSku],
purchaseToken: currentSub.purchaseToken,
replacementMode: replacementMode,
Expand Down
Loading
Loading