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
20 changes: 20 additions & 0 deletions libraries/expo-iap/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1579,11 +1579,29 @@ export interface RequestVerifyPurchaseWithIapkitGoogleProps {
purchaseToken: string;
}

/**
* Meta Horizon verification parameters for IAPKit.
*
* The App Secret used to call Meta's Graph API lives on the IAPKit server
* (per project), so the client only needs to identify the entitlement by
* (userId, sku). Authentication with IAPKit is the Bearer API key shared
* with apple / google.
*/
export interface RequestVerifyPurchaseWithIapkitHorizonProps {
/** The SKU for the add-on item, defined in the Meta Developer Dashboard. */
sku: string;
/** The user ID of the user whose purchase you want to verify. */
userId: string;
}

/**
* Platform-specific verification parameters for IAPKit.
*
* - apple: Verifies via App Store (JWS token)
* - google: Verifies via Play Store (purchase token)
* - horizon: Verifies via Meta's S2S verify_entitlement endpoint. The
* IAPKit server holds the Horizon App Secret, so the client only sends
* (userId, sku) — no Meta access token required here.
*/
export interface RequestVerifyPurchaseWithIapkitProps {
/** API key used for the Authorization header (Bearer {apiKey}). */
Expand All @@ -1592,6 +1610,8 @@ export interface RequestVerifyPurchaseWithIapkitProps {
apple?: (RequestVerifyPurchaseWithIapkitAppleProps | null);
/** Google Play Store verification parameters. */
google?: (RequestVerifyPurchaseWithIapkitGoogleProps | null);
/** Meta Horizon (Quest) verification parameters. */
horizon?: (RequestVerifyPurchaseWithIapkitHorizonProps | null);
}

export interface RequestVerifyPurchaseWithIapkitResult {
Expand Down
40 changes: 40 additions & 0 deletions libraries/flutter_inapp_purchase/lib/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4406,15 +4406,51 @@ class RequestVerifyPurchaseWithIapkitGoogleProps {
}
}

/// Meta Horizon verification parameters for IAPKit.
///
/// The App Secret used to call Meta's Graph API lives on the IAPKit server
/// (per project), so the client only needs to identify the entitlement by
/// (userId, sku). Authentication with IAPKit is the Bearer API key shared
/// with apple / google.
class RequestVerifyPurchaseWithIapkitHorizonProps {
const RequestVerifyPurchaseWithIapkitHorizonProps({
required this.sku,
required this.userId,
});

/// The SKU for the add-on item, defined in the Meta Developer Dashboard.
final String sku;
/// The user ID of the user whose purchase you want to verify.
final String userId;

factory RequestVerifyPurchaseWithIapkitHorizonProps.fromJson(Map<String, dynamic> json) {
return RequestVerifyPurchaseWithIapkitHorizonProps(
sku: json['sku'] as String,
userId: json['userId'] as String,
);
}

Map<String, dynamic> toJson() {
return {
'sku': sku,
'userId': userId,
};
}
}

/// Platform-specific verification parameters for IAPKit.
///
/// - apple: Verifies via App Store (JWS token)
/// - google: Verifies via Play Store (purchase token)
/// - horizon: Verifies via Meta's S2S verify_entitlement endpoint. The
/// IAPKit server holds the Horizon App Secret, so the client only sends
/// (userId, sku) — no Meta access token required here.
class RequestVerifyPurchaseWithIapkitProps {
const RequestVerifyPurchaseWithIapkitProps({
this.apiKey,
this.apple,
this.google,
this.horizon,
});

/// API key used for the Authorization header (Bearer {apiKey}).
Expand All @@ -4423,12 +4459,15 @@ class RequestVerifyPurchaseWithIapkitProps {
final RequestVerifyPurchaseWithIapkitAppleProps? apple;
/// Google Play Store verification parameters.
final RequestVerifyPurchaseWithIapkitGoogleProps? google;
/// Meta Horizon (Quest) verification parameters.
final RequestVerifyPurchaseWithIapkitHorizonProps? horizon;

factory RequestVerifyPurchaseWithIapkitProps.fromJson(Map<String, dynamic> json) {
return RequestVerifyPurchaseWithIapkitProps(
apiKey: json['apiKey'] as String?,
apple: json['apple'] != null ? RequestVerifyPurchaseWithIapkitAppleProps.fromJson(json['apple'] as Map<String, dynamic>) : null,
google: json['google'] != null ? RequestVerifyPurchaseWithIapkitGoogleProps.fromJson(json['google'] as Map<String, dynamic>) : null,
horizon: json['horizon'] != null ? RequestVerifyPurchaseWithIapkitHorizonProps.fromJson(json['horizon'] as Map<String, dynamic>) : null,
);
}

Expand All @@ -4437,6 +4476,7 @@ class RequestVerifyPurchaseWithIapkitProps {
'apiKey': apiKey,
'apple': apple?.toJson(),
'google': google?.toJson(),
'horizon': horizon?.toJson(),
};
}
}
Expand Down
37 changes: 36 additions & 1 deletion libraries/godot-iap/addons/godot-iap/types.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4096,14 +4096,39 @@ class RequestVerifyPurchaseWithIapkitGoogleProps:
dict["purchaseToken"] = purchase_token
return dict

## Platform-specific verification parameters for IAPKit. - apple: Verifies via App Store (JWS token) - google: Verifies via Play Store (purchase token)
## Meta Horizon verification parameters for IAPKit. The App Secret used to call Meta's Graph API lives on the IAPKit server (per project), so the client only needs to identify the entitlement by (userId, sku). Authentication with IAPKit is the Bearer API key shared with apple / google.
class RequestVerifyPurchaseWithIapkitHorizonProps:
## The user ID of the user whose purchase you want to verify.
var user_id: String = ""
## The SKU for the add-on item, defined in the Meta Developer Dashboard.
var sku: String = ""

static func from_dict(data: Dictionary) -> RequestVerifyPurchaseWithIapkitHorizonProps:
var obj = RequestVerifyPurchaseWithIapkitHorizonProps.new()
if data.has("userId") and data["userId"] != null:
obj.user_id = data["userId"]
if data.has("sku") and data["sku"] != null:
obj.sku = data["sku"]
return obj

func to_dict() -> Dictionary:
var dict = {}
if user_id != null:
dict["userId"] = user_id
if sku != null:
dict["sku"] = sku
return dict

## Platform-specific verification parameters for IAPKit. - apple: Verifies via App Store (JWS token) - google: Verifies via Play Store (purchase token) - horizon: Verifies via Meta's S2S verify_entitlement endpoint. The IAPKit server holds the Horizon App Secret, so the client only sends (userId, sku) — no Meta access token required here.
class RequestVerifyPurchaseWithIapkitProps:
## API key used for the Authorization header (Bearer {apiKey}).
var api_key: Variant = null
## Apple App Store verification parameters.
var apple: RequestVerifyPurchaseWithIapkitAppleProps
## Google Play Store verification parameters.
var google: RequestVerifyPurchaseWithIapkitGoogleProps
## Meta Horizon (Quest) verification parameters.
var horizon: RequestVerifyPurchaseWithIapkitHorizonProps

static func from_dict(data: Dictionary) -> RequestVerifyPurchaseWithIapkitProps:
var obj = RequestVerifyPurchaseWithIapkitProps.new()
Expand All @@ -4119,6 +4144,11 @@ class RequestVerifyPurchaseWithIapkitProps:
obj.google = RequestVerifyPurchaseWithIapkitGoogleProps.from_dict(data["google"])
else:
obj.google = data["google"]
if data.has("horizon") and data["horizon"] != null:
if data["horizon"] is Dictionary:
obj.horizon = RequestVerifyPurchaseWithIapkitHorizonProps.from_dict(data["horizon"])
else:
obj.horizon = data["horizon"]
return obj

func to_dict() -> Dictionary:
Expand All @@ -4135,6 +4165,11 @@ class RequestVerifyPurchaseWithIapkitProps:
dict["google"] = google.to_dict()
else:
dict["google"] = google
if horizon != null:
if horizon.has_method("to_dict"):
dict["horizon"] = horizon.to_dict()
else:
dict["horizon"] = horizon
return dict

## Product-level subscription replacement parameters (Android) Used with setSubscriptionProductReplacementParams in BillingFlowParams.ProductDetailsParams Available in Google Play Billing Library 8.1.0+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4464,11 +4464,50 @@ public data class RequestVerifyPurchaseWithIapkitGoogleProps(
)
}

/**
* Meta Horizon verification parameters for IAPKit.
*
* The App Secret used to call Meta's Graph API lives on the IAPKit server
* (per project), so the client only needs to identify the entitlement by
* (userId, sku). Authentication with IAPKit is the Bearer API key shared
* with apple / google.
*/
public data class RequestVerifyPurchaseWithIapkitHorizonProps(
/**
* The SKU for the add-on item, defined in the Meta Developer Dashboard.
*/
val sku: String,
/**
* The user ID of the user whose purchase you want to verify.
*/
val userId: String
) {
companion object {
fun fromJson(json: Map<String, Any?>): RequestVerifyPurchaseWithIapkitHorizonProps? {
val sku = json["sku"] as? String
val userId = json["userId"] as? String
if (sku == null || userId == null) return null
return RequestVerifyPurchaseWithIapkitHorizonProps(
sku = sku,
userId = userId,
)
}
}

fun toJson(): Map<String, Any?> = mapOf(
"sku" to sku,
"userId" to userId,
)
}

/**
* Platform-specific verification parameters for IAPKit.
*
* - apple: Verifies via App Store (JWS token)
* - google: Verifies via Play Store (purchase token)
* - horizon: Verifies via Meta's S2S verify_entitlement endpoint. The
* IAPKit server holds the Horizon App Secret, so the client only sends
* (userId, sku) — no Meta access token required here.
*/
public data class RequestVerifyPurchaseWithIapkitProps(
/**
Expand All @@ -4482,14 +4521,19 @@ public data class RequestVerifyPurchaseWithIapkitProps(
/**
* Google Play Store verification parameters.
*/
val google: RequestVerifyPurchaseWithIapkitGoogleProps? = null
val google: RequestVerifyPurchaseWithIapkitGoogleProps? = null,
/**
* Meta Horizon (Quest) verification parameters.
*/
val horizon: RequestVerifyPurchaseWithIapkitHorizonProps? = null
) {
companion object {
fun fromJson(json: Map<String, Any?>): RequestVerifyPurchaseWithIapkitProps {
return RequestVerifyPurchaseWithIapkitProps(
apiKey = json["apiKey"] as? String,
apple = (json["apple"] as? Map<String, Any?>)?.let { RequestVerifyPurchaseWithIapkitAppleProps.fromJson(it) },
google = (json["google"] as? Map<String, Any?>)?.let { RequestVerifyPurchaseWithIapkitGoogleProps.fromJson(it) },
horizon = (json["horizon"] as? Map<String, Any?>)?.let { RequestVerifyPurchaseWithIapkitHorizonProps.fromJson(it) },
)
}
}
Expand All @@ -4498,6 +4542,7 @@ public data class RequestVerifyPurchaseWithIapkitProps(
"apiKey" to apiKey,
"apple" to apple?.toJson(),
"google" to google?.toJson(),
"horizon" to horizon?.toJson(),
)
}

Expand Down
20 changes: 20 additions & 0 deletions libraries/react-native-iap/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1579,11 +1579,29 @@ export interface RequestVerifyPurchaseWithIapkitGoogleProps {
purchaseToken: string;
}

/**
* Meta Horizon verification parameters for IAPKit.
*
* The App Secret used to call Meta's Graph API lives on the IAPKit server
* (per project), so the client only needs to identify the entitlement by
* (userId, sku). Authentication with IAPKit is the Bearer API key shared
* with apple / google.
*/
export interface RequestVerifyPurchaseWithIapkitHorizonProps {
/** The SKU for the add-on item, defined in the Meta Developer Dashboard. */
sku: string;
/** The user ID of the user whose purchase you want to verify. */
userId: string;
}

/**
* Platform-specific verification parameters for IAPKit.
*
* - apple: Verifies via App Store (JWS token)
* - google: Verifies via Play Store (purchase token)
* - horizon: Verifies via Meta's S2S verify_entitlement endpoint. The
* IAPKit server holds the Horizon App Secret, so the client only sends
* (userId, sku) — no Meta access token required here.
*/
export interface RequestVerifyPurchaseWithIapkitProps {
/** API key used for the Authorization header (Bearer {apiKey}). */
Expand All @@ -1592,6 +1610,8 @@ export interface RequestVerifyPurchaseWithIapkitProps {
apple?: (RequestVerifyPurchaseWithIapkitAppleProps | null);
/** Google Play Store verification parameters. */
google?: (RequestVerifyPurchaseWithIapkitGoogleProps | null);
/** Meta Horizon (Quest) verification parameters. */
horizon?: (RequestVerifyPurchaseWithIapkitHorizonProps | null);
}

export interface RequestVerifyPurchaseWithIapkitResult {
Expand Down
30 changes: 29 additions & 1 deletion packages/apple/Sources/Models/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1853,26 +1853,54 @@ public struct RequestVerifyPurchaseWithIapkitGoogleProps: Codable {
}
}

/// Meta Horizon verification parameters for IAPKit.
///
/// The App Secret used to call Meta's Graph API lives on the IAPKit server
/// (per project), so the client only needs to identify the entitlement by
/// (userId, sku). Authentication with IAPKit is the Bearer API key shared
/// with apple / google.
public struct RequestVerifyPurchaseWithIapkitHorizonProps: Codable {
/// The SKU for the add-on item, defined in the Meta Developer Dashboard.
public var sku: String
/// The user ID of the user whose purchase you want to verify.
public var userId: String

public init(
sku: String,
userId: String
) {
self.sku = sku
self.userId = userId
}
}

/// Platform-specific verification parameters for IAPKit.
///
/// - apple: Verifies via App Store (JWS token)
/// - google: Verifies via Play Store (purchase token)
/// - horizon: Verifies via Meta's S2S verify_entitlement endpoint. The
/// IAPKit server holds the Horizon App Secret, so the client only sends
/// (userId, sku) — no Meta access token required here.
public struct RequestVerifyPurchaseWithIapkitProps: Codable {
/// API key used for the Authorization header (Bearer {apiKey}).
public var apiKey: String?
/// Apple App Store verification parameters.
public var apple: RequestVerifyPurchaseWithIapkitAppleProps?
/// Google Play Store verification parameters.
public var google: RequestVerifyPurchaseWithIapkitGoogleProps?
/// Meta Horizon (Quest) verification parameters.
public var horizon: RequestVerifyPurchaseWithIapkitHorizonProps?

public init(
apiKey: String? = nil,
apple: RequestVerifyPurchaseWithIapkitAppleProps? = nil,
google: RequestVerifyPurchaseWithIapkitGoogleProps? = nil
google: RequestVerifyPurchaseWithIapkitGoogleProps? = nil,
horizon: RequestVerifyPurchaseWithIapkitHorizonProps? = nil
) {
self.apiKey = apiKey
self.apple = apple
self.google = google
self.horizon = horizon
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import dev.hyo.openiap.MutationValidateReceiptHandler
import dev.hyo.openiap.MutationVerifyPurchaseWithProviderHandler
import dev.hyo.openiap.PurchaseVerificationProvider
import dev.hyo.openiap.utils.verifyPurchaseWithIapkit
import dev.hyo.openiap.utils.verifyPurchaseWithIapkitHorizon
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand Down Expand Up @@ -722,8 +723,19 @@ class OpenIapModule(
val options = props.iapkit ?: throw OpenIapError.DeveloperError(
"Missing IAPKit verification parameters"
)
// Horizon-flavor-only routing: if the caller supplied a
// `horizon` payload, use the flavor-local helper that knows
// how to assemble the Meta-specific IAPKit request. Otherwise
// fall through to the shared Google path in main — a Quest
// app that happens to hold a Play purchase token still gets
// verified that way.
val iapkitResult = if (options.horizon != null) {
verifyPurchaseWithIapkitHorizon(options, TAG)
} else {
verifyPurchaseWithIapkit(options, TAG)
}
VerifyPurchaseWithProviderResult(
iapkit = verifyPurchaseWithIapkit(options, TAG),
iapkit = iapkitResult,
provider = props.provider
)
}
Expand Down
Loading
Loading