From 590b3218977ce282f2b9b55822cc7e9884a87644 Mon Sep 17 00:00:00 2001
From: Hyo
Date: Sat, 13 Dec 2025 04:15:44 +0900
Subject: [PATCH 1/4] refactor: move sku into platform-specific verification
options
- Remove sku from VerifyPurchaseProps root level
- Add sku to VerifyPurchaseAppleOptions (replaces jws)
- Add sku to VerifyPurchaseGoogleOptions
- Update Kotlin code to use googleOptions.sku
- Update tests for new schema structure
- Update release notes for v1.3.4/v1.3.14/v1.3.2
---
packages/apple/Sources/Models/Types.swift | 23 ++++++------
.../docs/src/pages/docs/updates/notes.tsx | 36 +++++++++----------
.../src/main/java/dev/hyo/openiap/Types.kt | 27 +++++++-------
.../utils/PurchaseVerificationValidator.kt | 3 +-
.../PurchaseVerificationValidatorTest.kt | 23 ++++++------
packages/gql/src/generated/Types.kt | 27 +++++++-------
packages/gql/src/generated/Types.swift | 23 ++++++------
packages/gql/src/generated/types.dart | 34 +++++++-----------
packages/gql/src/generated/types.ts | 13 +++----
packages/gql/src/type-android.graphql | 4 +++
packages/gql/src/type-ios.graphql | 7 ++--
packages/gql/src/type.graphql | 4 ---
12 files changed, 99 insertions(+), 125 deletions(-)
diff --git a/packages/apple/Sources/Models/Types.swift b/packages/apple/Sources/Models/Types.swift
index 42365f34..a56f0602 100644
--- a/packages/apple/Sources/Models/Types.swift
+++ b/packages/apple/Sources/Models/Types.swift
@@ -1337,17 +1337,14 @@ public struct SubscriptionProductReplacementParamsAndroid: Codable {
/// Apple App Store verification parameters.
/// Used for server-side receipt validation via App Store Server API.
-///
-/// ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
public struct VerifyPurchaseAppleOptions: Codable {
- /// The JWS (JSON Web Signature) representation of the transaction.
- /// ⚠️ Sensitive: Do not log this value.
- public var jws: String
+ /// Product SKU to validate
+ public var sku: String
public init(
- jws: String
+ sku: String
) {
- self.jws = jws
+ self.sku = sku
}
}
@@ -1366,17 +1363,21 @@ public struct VerifyPurchaseGoogleOptions: Codable {
/// Purchase token from the purchase response.
/// ⚠️ Sensitive: Do not log this value.
public var purchaseToken: String
+ /// Product SKU to validate
+ public var sku: String
public init(
accessToken: String,
isSub: Bool? = nil,
packageName: String,
- purchaseToken: String
+ purchaseToken: String,
+ sku: String
) {
self.accessToken = accessToken
self.isSub = isSub
self.packageName = packageName
self.purchaseToken = purchaseToken
+ self.sku = sku
}
}
@@ -1417,19 +1418,15 @@ public struct VerifyPurchaseProps: Codable {
public var google: VerifyPurchaseGoogleOptions?
/// Meta Horizon (Quest) verification parameters.
public var horizon: VerifyPurchaseHorizonOptions?
- /// Product SKU to validate
- public var sku: String
public init(
apple: VerifyPurchaseAppleOptions? = nil,
google: VerifyPurchaseGoogleOptions? = nil,
- horizon: VerifyPurchaseHorizonOptions? = nil,
- sku: String
+ horizon: VerifyPurchaseHorizonOptions? = nil
) {
self.apple = apple
self.google = google
self.horizon = horizon
- self.sku = sku
}
}
diff --git a/packages/docs/src/pages/docs/updates/notes.tsx b/packages/docs/src/pages/docs/updates/notes.tsx
index fa7c0f9e..d5eb384f 100644
--- a/packages/docs/src/pages/docs/updates/notes.tsx
+++ b/packages/docs/src/pages/docs/updates/notes.tsx
@@ -29,29 +29,31 @@ function Notes() {
}}
>
- 📅 openiap-gql v1.3.3 / openiap-google v1.3.13 / openiap-apple v1.3.1
+ 📅 openiap-gql v1.3.4 / openiap-google v1.3.14 / openiap-apple v1.3.2
- Platform-Specific Verification Options
- verifyPurchase API Refactored:
+ verifyPurchase API Refactored (Breaking Change):
- The verifyPurchase API now supports platform-specific
- options for Apple, Google, and Meta Horizon stores.
+ The verifyPurchase API now requires platform-specific
+ options for Apple, Google, and Meta Horizon stores. The{' '}
+ sku field has been moved inside each platform-specific
+ options object.
-
VerifyPurchaseAppleOptions
{' '}
- - Apple App Store verification with JWS token
+ - Apple App Store verification with sku
-
VerifyPurchaseGoogleOptions
{' '}
- - Google Play verification with packageName, purchaseToken, and
- accessToken
+ - Google Play verification with sku, packageName, purchaseToken,
+ and accessToken
-
@@ -65,11 +67,11 @@ function Notes() {
New VerifyPurchaseProps Structure:
- {`// Platform-specific verification (recommended)
+ {`// Platform-specific verification
verifyPurchase({
- sku: 'premium_monthly',
- apple: { jws: 'eyJ...' }, // iOS App Store
+ apple: { sku: 'premium_monthly' }, // iOS App Store
google: { // Google Play
+ sku: 'premium_monthly',
packageName: 'com.example.app',
purchaseToken: 'token...',
accessToken: 'oauth_token...',
@@ -80,20 +82,18 @@ verifyPurchase({
userId: '123456789',
accessToken: 'OC|app_id|app_secret'
}
-})
-
-// Legacy format still supported (deprecated)
-verifyPurchase({
- sku: 'premium_monthly',
- androidOptions: { ... } // @deprecated - use google instead
})`}
- Deprecations:
+ Breaking Changes:
-
-
androidOptions in VerifyPurchaseProps → Use{' '}
+ sku removed from VerifyPurchaseProps{' '}
+ root level → Now inside each platform options
+
+ -
+
androidOptions completely removed → Use{' '}
google instead
diff --git a/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt b/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
index b4ae4233..4aefb97f 100644
--- a/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
+++ b/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
@@ -3087,26 +3087,23 @@ public data class SubscriptionProductReplacementParamsAndroid(
/**
* Apple App Store verification parameters.
* Used for server-side receipt validation via App Store Server API.
- *
- * ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
*/
public data class VerifyPurchaseAppleOptions(
/**
- * The JWS (JSON Web Signature) representation of the transaction.
- * ⚠️ Sensitive: Do not log this value.
+ * Product SKU to validate
*/
- val jws: String
+ val sku: String
) {
companion object {
fun fromJson(json: Map): VerifyPurchaseAppleOptions {
return VerifyPurchaseAppleOptions(
- jws = json["jws"] as String,
+ sku = json["sku"] as String,
)
}
}
fun toJson(): Map = mapOf(
- "jws" to jws,
+ "sku" to sku,
)
}
@@ -3134,7 +3131,11 @@ public data class VerifyPurchaseGoogleOptions(
* Purchase token from the purchase response.
* ⚠️ Sensitive: Do not log this value.
*/
- val purchaseToken: String
+ val purchaseToken: String,
+ /**
+ * Product SKU to validate
+ */
+ val sku: String
) {
companion object {
fun fromJson(json: Map): VerifyPurchaseGoogleOptions {
@@ -3143,6 +3144,7 @@ public data class VerifyPurchaseGoogleOptions(
isSub = json["isSub"] as Boolean?,
packageName = json["packageName"] as String,
purchaseToken = json["purchaseToken"] as String,
+ sku = json["sku"] as String,
)
}
}
@@ -3152,6 +3154,7 @@ public data class VerifyPurchaseGoogleOptions(
"isSub" to isSub,
"packageName" to packageName,
"purchaseToken" to purchaseToken,
+ "sku" to sku,
)
}
@@ -3213,11 +3216,7 @@ public data class VerifyPurchaseProps(
/**
* Meta Horizon (Quest) verification parameters.
*/
- val horizon: VerifyPurchaseHorizonOptions? = null,
- /**
- * Product SKU to validate
- */
- val sku: String
+ val horizon: VerifyPurchaseHorizonOptions? = null
) {
companion object {
fun fromJson(json: Map): VerifyPurchaseProps {
@@ -3225,7 +3224,6 @@ public data class VerifyPurchaseProps(
apple = (json["apple"] as Map?)?.let { VerifyPurchaseAppleOptions.fromJson(it) },
google = (json["google"] as Map?)?.let { VerifyPurchaseGoogleOptions.fromJson(it) },
horizon = (json["horizon"] as Map?)?.let { VerifyPurchaseHorizonOptions.fromJson(it) },
- sku = json["sku"] as String,
)
}
}
@@ -3234,7 +3232,6 @@ public data class VerifyPurchaseProps(
"apple" to apple?.toJson(),
"google" to google?.toJson(),
"horizon" to horizon?.toJson(),
- "sku" to sku,
)
}
diff --git a/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt b/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt
index b3e97875..118b798c 100644
--- a/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt
+++ b/packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt
@@ -49,10 +49,11 @@ suspend fun verifyPurchaseWithGooglePlay(
)
}
+ val sku = googleOptions.sku
val typeSegment = if (isSub == true) "subscriptions" else "products"
val baseUrl = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications"
val url = "$baseUrl/${encodePathSegment(packageName)}/purchases/$typeSegment/" +
- "${encodePathSegment(props.sku)}/tokens/${encodePathSegment(purchaseToken)}"
+ "${encodePathSegment(sku)}/tokens/${encodePathSegment(purchaseToken)}"
val connection = connectionFactory(url).apply {
requestMethod = "GET"
diff --git a/packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt b/packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt
index 940265d4..549edd02 100644
--- a/packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt
+++ b/packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt
@@ -26,7 +26,7 @@ class PurchaseVerificationValidatorTest {
@Test
fun `verifyPurchaseWithGooglePlay throws without google options`() = runTest {
- val props = VerifyPurchaseProps(sku = "product.sku")
+ val props = VerifyPurchaseProps()
try {
verifyPurchaseWithGooglePlay(props, "TEST_TAG") { _ ->
@@ -44,9 +44,10 @@ class PurchaseVerificationValidatorTest {
accessToken = "token",
isSub = true,
packageName = "dev.hyo.app",
- purchaseToken = "purchaseToken"
+ purchaseToken = "purchaseToken",
+ sku = "premium_monthly"
)
- val props = VerifyPurchaseProps(google = googleOptions, sku = "premium_monthly")
+ val props = VerifyPurchaseProps(google = googleOptions)
val body = """
{
"autoRenewing": true,
@@ -86,9 +87,10 @@ class PurchaseVerificationValidatorTest {
accessToken = "token",
isSub = true,
packageName = "dev.hyo.app",
- purchaseToken = "purchaseToken"
+ purchaseToken = "purchaseToken",
+ sku = "premium_monthly"
)
- val props = VerifyPurchaseProps(google = googleOptions, sku = "premium_monthly")
+ val props = VerifyPurchaseProps(google = googleOptions)
val body = """
{
"autoRenewing": true,
@@ -130,9 +132,10 @@ class PurchaseVerificationValidatorTest {
accessToken = "token",
isSub = false,
packageName = "dev.hyo.app",
- purchaseToken = "purchaseToken"
+ purchaseToken = "purchaseToken",
+ sku = "premium_monthly"
)
- val props = VerifyPurchaseProps(google = googleOptions, sku = "premium_monthly")
+ val props = VerifyPurchaseProps(google = googleOptions)
try {
verifyPurchaseWithGooglePlay(
@@ -268,7 +271,7 @@ class PurchaseVerificationValidatorTest {
@Test
fun `verifyPurchaseWithHorizon throws without horizon options`() = runTest {
- val props = VerifyPurchaseProps(sku = "50_gems")
+ val props = VerifyPurchaseProps()
try {
verifyPurchaseWithHorizon(props, "test-app-id", "TEST") { _ ->
@@ -287,7 +290,7 @@ class PurchaseVerificationValidatorTest {
userId = "123456789",
accessToken = "OC|app_id|app_secret"
)
- val props = VerifyPurchaseProps(horizon = horizonOptions, sku = "50_gems")
+ val props = VerifyPurchaseProps(horizon = horizonOptions)
val result = verifyPurchaseWithHorizon(
props,
@@ -306,7 +309,7 @@ class PurchaseVerificationValidatorTest {
userId = "123456789",
accessToken = "OC|app_id|app_secret"
)
- val props = VerifyPurchaseProps(horizon = horizonOptions, sku = "50_gems")
+ val props = VerifyPurchaseProps(horizon = horizonOptions)
try {
verifyPurchaseWithHorizon(
diff --git a/packages/gql/src/generated/Types.kt b/packages/gql/src/generated/Types.kt
index a060f318..304fe050 100644
--- a/packages/gql/src/generated/Types.kt
+++ b/packages/gql/src/generated/Types.kt
@@ -3159,26 +3159,23 @@ public data class SubscriptionProductReplacementParamsAndroid(
/**
* Apple App Store verification parameters.
* Used for server-side receipt validation via App Store Server API.
- *
- * ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
*/
public data class VerifyPurchaseAppleOptions(
/**
- * The JWS (JSON Web Signature) representation of the transaction.
- * ⚠️ Sensitive: Do not log this value.
+ * Product SKU to validate
*/
- val jws: String
+ val sku: String
) {
companion object {
fun fromJson(json: Map): VerifyPurchaseAppleOptions {
return VerifyPurchaseAppleOptions(
- jws = json["jws"] as String,
+ sku = json["sku"] as String,
)
}
}
fun toJson(): Map = mapOf(
- "jws" to jws,
+ "sku" to sku,
)
}
@@ -3206,7 +3203,11 @@ public data class VerifyPurchaseGoogleOptions(
* Purchase token from the purchase response.
* ⚠️ Sensitive: Do not log this value.
*/
- val purchaseToken: String
+ val purchaseToken: String,
+ /**
+ * Product SKU to validate
+ */
+ val sku: String
) {
companion object {
fun fromJson(json: Map): VerifyPurchaseGoogleOptions {
@@ -3215,6 +3216,7 @@ public data class VerifyPurchaseGoogleOptions(
isSub = json["isSub"] as Boolean?,
packageName = json["packageName"] as String,
purchaseToken = json["purchaseToken"] as String,
+ sku = json["sku"] as String,
)
}
}
@@ -3224,6 +3226,7 @@ public data class VerifyPurchaseGoogleOptions(
"isSub" to isSub,
"packageName" to packageName,
"purchaseToken" to purchaseToken,
+ "sku" to sku,
)
}
@@ -3285,11 +3288,7 @@ public data class VerifyPurchaseProps(
/**
* Meta Horizon (Quest) verification parameters.
*/
- val horizon: VerifyPurchaseHorizonOptions? = null,
- /**
- * Product SKU to validate
- */
- val sku: String
+ val horizon: VerifyPurchaseHorizonOptions? = null
) {
companion object {
fun fromJson(json: Map): VerifyPurchaseProps {
@@ -3297,7 +3296,6 @@ public data class VerifyPurchaseProps(
apple = (json["apple"] as Map?)?.let { VerifyPurchaseAppleOptions.fromJson(it) },
google = (json["google"] as Map?)?.let { VerifyPurchaseGoogleOptions.fromJson(it) },
horizon = (json["horizon"] as Map?)?.let { VerifyPurchaseHorizonOptions.fromJson(it) },
- sku = json["sku"] as String,
)
}
}
@@ -3306,7 +3304,6 @@ public data class VerifyPurchaseProps(
"apple" to apple?.toJson(),
"google" to google?.toJson(),
"horizon" to horizon?.toJson(),
- "sku" to sku,
)
}
diff --git a/packages/gql/src/generated/Types.swift b/packages/gql/src/generated/Types.swift
index 42365f34..a56f0602 100644
--- a/packages/gql/src/generated/Types.swift
+++ b/packages/gql/src/generated/Types.swift
@@ -1337,17 +1337,14 @@ public struct SubscriptionProductReplacementParamsAndroid: Codable {
/// Apple App Store verification parameters.
/// Used for server-side receipt validation via App Store Server API.
-///
-/// ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
public struct VerifyPurchaseAppleOptions: Codable {
- /// The JWS (JSON Web Signature) representation of the transaction.
- /// ⚠️ Sensitive: Do not log this value.
- public var jws: String
+ /// Product SKU to validate
+ public var sku: String
public init(
- jws: String
+ sku: String
) {
- self.jws = jws
+ self.sku = sku
}
}
@@ -1366,17 +1363,21 @@ public struct VerifyPurchaseGoogleOptions: Codable {
/// Purchase token from the purchase response.
/// ⚠️ Sensitive: Do not log this value.
public var purchaseToken: String
+ /// Product SKU to validate
+ public var sku: String
public init(
accessToken: String,
isSub: Bool? = nil,
packageName: String,
- purchaseToken: String
+ purchaseToken: String,
+ sku: String
) {
self.accessToken = accessToken
self.isSub = isSub
self.packageName = packageName
self.purchaseToken = purchaseToken
+ self.sku = sku
}
}
@@ -1417,19 +1418,15 @@ public struct VerifyPurchaseProps: Codable {
public var google: VerifyPurchaseGoogleOptions?
/// Meta Horizon (Quest) verification parameters.
public var horizon: VerifyPurchaseHorizonOptions?
- /// Product SKU to validate
- public var sku: String
public init(
apple: VerifyPurchaseAppleOptions? = nil,
google: VerifyPurchaseGoogleOptions? = nil,
- horizon: VerifyPurchaseHorizonOptions? = nil,
- sku: String
+ horizon: VerifyPurchaseHorizonOptions? = nil
) {
self.apple = apple
self.google = google
self.horizon = horizon
- self.sku = sku
}
}
diff --git a/packages/gql/src/generated/types.dart b/packages/gql/src/generated/types.dart
index ecde6bfe..6e9c8c2f 100644
--- a/packages/gql/src/generated/types.dart
+++ b/packages/gql/src/generated/types.dart
@@ -3624,28 +3624,24 @@ class SubscriptionProductReplacementParamsAndroid {
/// Apple App Store verification parameters.
/// Used for server-side receipt validation via App Store Server API.
-///
-/// ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
class VerifyPurchaseAppleOptions {
const VerifyPurchaseAppleOptions({
- /// The JWS (JSON Web Signature) representation of the transaction.
- /// ⚠️ Sensitive: Do not log this value.
- required this.jws,
+ /// Product SKU to validate
+ required this.sku,
});
- /// The JWS (JSON Web Signature) representation of the transaction.
- /// ⚠️ Sensitive: Do not log this value.
- final String jws;
+ /// Product SKU to validate
+ final String sku;
factory VerifyPurchaseAppleOptions.fromJson(Map json) {
return VerifyPurchaseAppleOptions(
- jws: json['jws'] as String,
+ sku: json['sku'] as String,
);
}
Map toJson() {
return {
- 'jws': jws,
+ 'sku': sku,
};
}
}
@@ -3666,6 +3662,8 @@ class VerifyPurchaseGoogleOptions {
/// Purchase token from the purchase response.
/// ⚠️ Sensitive: Do not log this value.
required this.purchaseToken,
+ /// Product SKU to validate
+ required this.sku,
});
/// Google OAuth2 access token for API authentication.
@@ -3678,6 +3676,8 @@ class VerifyPurchaseGoogleOptions {
/// Purchase token from the purchase response.
/// ⚠️ Sensitive: Do not log this value.
final String purchaseToken;
+ /// Product SKU to validate
+ final String sku;
factory VerifyPurchaseGoogleOptions.fromJson(Map json) {
return VerifyPurchaseGoogleOptions(
@@ -3685,6 +3685,7 @@ class VerifyPurchaseGoogleOptions {
isSub: json['isSub'] as bool?,
packageName: json['packageName'] as String,
purchaseToken: json['purchaseToken'] as String,
+ sku: json['sku'] as String,
);
}
@@ -3694,6 +3695,7 @@ class VerifyPurchaseGoogleOptions {
'isSub': isSub,
'packageName': packageName,
'purchaseToken': purchaseToken,
+ 'sku': sku,
};
}
}
@@ -3752,8 +3754,6 @@ class VerifyPurchaseProps {
this.google,
/// Meta Horizon (Quest) verification parameters.
this.horizon,
- /// Product SKU to validate
- required this.sku,
});
/// Apple App Store verification parameters.
@@ -3762,15 +3762,12 @@ class VerifyPurchaseProps {
final VerifyPurchaseGoogleOptions? google;
/// Meta Horizon (Quest) verification parameters.
final VerifyPurchaseHorizonOptions? horizon;
- /// Product SKU to validate
- final String sku;
factory VerifyPurchaseProps.fromJson(Map json) {
return VerifyPurchaseProps(
apple: json['apple'] != null ? VerifyPurchaseAppleOptions.fromJson(json['apple'] as Map) : null,
google: json['google'] != null ? VerifyPurchaseGoogleOptions.fromJson(json['google'] as Map) : null,
horizon: json['horizon'] != null ? VerifyPurchaseHorizonOptions.fromJson(json['horizon'] as Map) : null,
- sku: json['sku'] as String,
);
}
@@ -3779,7 +3776,6 @@ class VerifyPurchaseProps {
'apple': apple?.toJson(),
'google': google?.toJson(),
'horizon': horizon?.toJson(),
- 'sku': sku,
};
}
}
@@ -4058,14 +4054,12 @@ abstract class MutationResolver {
VerifyPurchaseAppleOptions? apple,
VerifyPurchaseGoogleOptions? google,
VerifyPurchaseHorizonOptions? horizon,
- required String sku,
});
/// Verify purchases with the configured providers
Future verifyPurchase({
VerifyPurchaseAppleOptions? apple,
VerifyPurchaseGoogleOptions? google,
VerifyPurchaseHorizonOptions? horizon,
- required String sku,
});
/// Verify purchases with a specific provider (e.g., IAPKit)
Future verifyPurchaseWithProvider({
@@ -4121,7 +4115,6 @@ abstract class QueryResolver {
VerifyPurchaseAppleOptions? apple,
VerifyPurchaseGoogleOptions? google,
VerifyPurchaseHorizonOptions? horizon,
- required String sku,
});
}
@@ -4173,13 +4166,11 @@ typedef MutationValidateReceiptHandler = Future Function({
VerifyPurchaseAppleOptions? apple,
VerifyPurchaseGoogleOptions? google,
VerifyPurchaseHorizonOptions? horizon,
- required String sku,
});
typedef MutationVerifyPurchaseHandler = Future Function({
VerifyPurchaseAppleOptions? apple,
VerifyPurchaseGoogleOptions? google,
VerifyPurchaseHorizonOptions? horizon,
- required String sku,
});
typedef MutationVerifyPurchaseWithProviderHandler = Future Function({
RequestVerifyPurchaseWithIapkitProps? iapkit,
@@ -4265,7 +4256,6 @@ typedef QueryValidateReceiptIOSHandler = Future Functio
VerifyPurchaseAppleOptions? apple,
VerifyPurchaseGoogleOptions? google,
VerifyPurchaseHorizonOptions? horizon,
- required String sku,
});
class QueryHandlers {
diff --git a/packages/gql/src/generated/types.ts b/packages/gql/src/generated/types.ts
index 2f7ebf1a..65eb2f93 100644
--- a/packages/gql/src/generated/types.ts
+++ b/packages/gql/src/generated/types.ts
@@ -1113,15 +1113,10 @@ export interface ValidTimeWindowAndroid {
/**
* Apple App Store verification parameters.
* Used for server-side receipt validation via App Store Server API.
- *
- * ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
*/
export interface VerifyPurchaseAppleOptions {
- /**
- * The JWS (JSON Web Signature) representation of the transaction.
- * ⚠️ Sensitive: Do not log this value.
- */
- jws: string;
+ /** Product SKU to validate */
+ sku: string;
}
/**
@@ -1145,6 +1140,8 @@ export interface VerifyPurchaseGoogleOptions {
* ⚠️ Sensitive: Do not log this value.
*/
purchaseToken: string;
+ /** Product SKU to validate */
+ sku: string;
}
/**
@@ -1180,8 +1177,6 @@ export interface VerifyPurchaseProps {
google?: (VerifyPurchaseGoogleOptions | null);
/** Meta Horizon (Quest) verification parameters. */
horizon?: (VerifyPurchaseHorizonOptions | null);
- /** Product SKU to validate */
- sku: string;
}
export type VerifyPurchaseResult = VerifyPurchaseResultAndroid | VerifyPurchaseResultHorizon | VerifyPurchaseResultIOS;
diff --git a/packages/gql/src/type-android.graphql b/packages/gql/src/type-android.graphql
index 57a9175b..eac0b2f0 100644
--- a/packages/gql/src/type-android.graphql
+++ b/packages/gql/src/type-android.graphql
@@ -377,6 +377,10 @@ Used for server-side receipt validation via Google Play Developer API.
⚠️ SECURITY: Contains sensitive tokens (accessToken, purchaseToken). Do not log or persist this data.
"""
input VerifyPurchaseGoogleOptions {
+ """
+ Product SKU to validate
+ """
+ sku: String!
"""
Android package name (e.g., com.example.app)
"""
diff --git a/packages/gql/src/type-ios.graphql b/packages/gql/src/type-ios.graphql
index d4b036a1..a67bfefd 100644
--- a/packages/gql/src/type-ios.graphql
+++ b/packages/gql/src/type-ios.graphql
@@ -230,15 +230,12 @@ input DiscountOfferInputIOS {
"""
Apple App Store verification parameters.
Used for server-side receipt validation via App Store Server API.
-
-⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
"""
input VerifyPurchaseAppleOptions {
"""
- The JWS (JSON Web Signature) representation of the transaction.
- ⚠️ Sensitive: Do not log this value.
+ Product SKU to validate
"""
- jws: String!
+ sku: String!
}
type VerifyPurchaseResultIOS {
diff --git a/packages/gql/src/type.graphql b/packages/gql/src/type.graphql
index 08e57323..80f29194 100644
--- a/packages/gql/src/type.graphql
+++ b/packages/gql/src/type.graphql
@@ -250,10 +250,6 @@ Platform-specific purchase verification parameters.
- horizon: Verifies via Meta's S2S API (verify_entitlement endpoint)
"""
input VerifyPurchaseProps {
- """
- Product SKU to validate
- """
- sku: String!
"""
Apple App Store verification parameters.
"""
From 93187e98fabd399415075555e9dec2142c0718a2 Mon Sep 17 00:00:00 2001
From: Hyo
Date: Sat, 13 Dec 2025 04:21:33 +0900
Subject: [PATCH 2/4] fix(apple): update verifyPurchase to use apple.sku
instead of props.sku
- Update performVerifyPurchaseIOS to require apple options with sku
- Update OpenIapStore.verifyPurchase to use VerifyPurchaseAppleOptions
- Update test mocks to use props.apple?.sku
---
packages/apple/Sources/OpenIapModule.swift | 35 +++++++++----------
packages/apple/Sources/OpenIapProtocol.swift | 2 +-
packages/apple/Sources/OpenIapStore.swift | 2 +-
.../OpenIapTests/VerifyPurchaseTests.swift | 2 +-
.../VerifyPurchaseWithProviderTests.swift | 2 +-
5 files changed, 21 insertions(+), 22 deletions(-)
diff --git a/packages/apple/Sources/OpenIapModule.swift b/packages/apple/Sources/OpenIapModule.swift
index 281696e5..7d12c463 100644
--- a/packages/apple/Sources/OpenIapModule.swift
+++ b/packages/apple/Sources/OpenIapModule.swift
@@ -598,25 +598,24 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
var jws: String = ""
var isValid = false
- // If apple options with JWS are provided, use that directly
- // Otherwise, fetch the latest transaction from StoreKit
- if let appleOptions = props.apple, !appleOptions.jws.isEmpty {
- jws = appleOptions.jws
- // When JWS is provided externally, we trust it's valid
- // The caller should verify the JWS on their server
- isValid = true
- } else {
- do {
- let product = try await storeProduct(for: props.sku)
- if let result = await product.latestTransaction {
- jws = result.jwsRepresentation
- let transaction = try checkVerified(result)
- latestPurchase = .purchaseIos(await StoreKitTypesBridge.purchaseIOS(from: transaction, jwsRepresentation: result.jwsRepresentation))
- isValid = true
- }
- } catch {
- isValid = false
+ // Apple options with sku is required
+ guard let appleOptions = props.apple, !appleOptions.sku.isEmpty else {
+ throw makePurchaseError(
+ code: .developerError,
+ message: "Apple verification requires apple options with sku"
+ )
+ }
+
+ do {
+ let product = try await storeProduct(for: appleOptions.sku)
+ if let result = await product.latestTransaction {
+ jws = result.jwsRepresentation
+ let transaction = try checkVerified(result)
+ latestPurchase = .purchaseIos(await StoreKitTypesBridge.purchaseIOS(from: transaction, jwsRepresentation: result.jwsRepresentation))
+ isValid = true
}
+ } catch {
+ isValid = false
}
return VerifyPurchaseResultIOS(
diff --git a/packages/apple/Sources/OpenIapProtocol.swift b/packages/apple/Sources/OpenIapProtocol.swift
index 083b799e..29546920 100644
--- a/packages/apple/Sources/OpenIapProtocol.swift
+++ b/packages/apple/Sources/OpenIapProtocol.swift
@@ -92,7 +92,7 @@ public extension OpenIapModuleProtocol {
throw PurchaseError(
code: .featureNotSupported,
message: "Expected iOS validation result",
- productId: props.sku
+ productId: props.apple?.sku
)
}
diff --git a/packages/apple/Sources/OpenIapStore.swift b/packages/apple/Sources/OpenIapStore.swift
index 85516e8d..5a767187 100644
--- a/packages/apple/Sources/OpenIapStore.swift
+++ b/packages/apple/Sources/OpenIapStore.swift
@@ -374,7 +374,7 @@ public final class OpenIapStore: ObservableObject {
}
public func verifyPurchase(sku: String) async throws -> VerifyPurchaseResultIOS {
- let result = try await module.verifyPurchase(VerifyPurchaseProps(sku: sku))
+ let result = try await module.verifyPurchase(VerifyPurchaseProps(apple: VerifyPurchaseAppleOptions(sku: sku)))
if case let .verifyPurchaseResultIos(iosResult) = result {
return iosResult
}
diff --git a/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift b/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift
index 63c73fbf..ebfe50f4 100644
--- a/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift
+++ b/packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift
@@ -101,7 +101,7 @@ private final class FakeOpenIapModule: OpenIapModuleProtocol {
func getReceiptDataIOS() async throws -> String? { "receipt" }
func validateReceiptIOS(_ props: VerifyPurchaseProps) async throws -> VerifyPurchaseResultIOS {
guard case let .verifyPurchaseResultIos(ios) = validateResult else {
- throw PurchaseError(code: .featureNotSupported, message: "Android validation not supported", productId: props.sku)
+ throw PurchaseError(code: .featureNotSupported, message: "Android validation not supported", productId: props.apple?.sku)
}
return ios
}
diff --git a/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift b/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift
index deeb3f74..11cf9515 100644
--- a/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift
+++ b/packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift
@@ -116,7 +116,7 @@ private final class FakeVerifyPurchaseModule: OpenIapModuleProtocol {
func getReceiptDataIOS() async throws -> String? { "receipt" }
func validateReceiptIOS(_ props: VerifyPurchaseProps) async throws -> VerifyPurchaseResultIOS {
guard case let .verifyPurchaseResultIos(ios) = validateResult else {
- throw PurchaseError(code: .featureNotSupported, message: "Expected iOS validation result", productId: props.sku)
+ throw PurchaseError(code: .featureNotSupported, message: "Expected iOS validation result", productId: props.apple?.sku)
}
return ios
}
From b3fc717812ba022bfbbc51a580fabecc55d7c4f7 Mon Sep 17 00:00:00 2001
From: Hyo
Date: Sat, 13 Dec 2025 04:22:49 +0900
Subject: [PATCH 3/4] docs: swap Updates and Notes labels in sidebar and page
title
---
packages/docs/src/pages/docs.tsx | 2 +-
packages/docs/src/pages/docs/updates/notes.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/docs/src/pages/docs.tsx b/packages/docs/src/pages/docs.tsx
index 2b740cc8..4164bc53 100644
--- a/packages/docs/src/pages/docs.tsx
+++ b/packages/docs/src/pages/docs.tsx
@@ -225,7 +225,7 @@ function Docs() {
className={({ isActive }) => (isActive ? 'active' : '')}
onClick={closeSidebar}
>
- Notes
+ Updates
diff --git a/packages/docs/src/pages/docs/updates/notes.tsx b/packages/docs/src/pages/docs/updates/notes.tsx
index d5eb384f..cf171e52 100644
--- a/packages/docs/src/pages/docs/updates/notes.tsx
+++ b/packages/docs/src/pages/docs/updates/notes.tsx
@@ -8,12 +8,12 @@ function Notes() {
return (
-
Updates
+
Notes
Important changes and deprecations in IAP libraries and platforms.
From e7223da86188b2eff066a63b6dbc50b769b2df77 Mon Sep 17 00:00:00 2001
From: Hyo
Date: Sat, 13 Dec 2025 04:32:03 +0900
Subject: [PATCH 4/4] docs(types): update VerifyPurchaseProps and add
VerifyPurchaseResultHorizon
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Rename PurchaseVerificationProps to VerifyPurchaseProps
- Update fields: sku/androidOptions → apple/google/horizon options
- Add VerifyPurchaseResultHorizon documentation in Android tab
- Rename result types to VerifyPurchaseResult*
---
packages/docs/src/pages/docs/types.tsx | 69 +++++++++++++++++++++-----
1 file changed, 56 insertions(+), 13 deletions(-)
diff --git a/packages/docs/src/pages/docs/types.tsx b/packages/docs/src/pages/docs/types.tsx
index 6e1f99ad..f5c479d7 100644
--- a/packages/docs/src/pages/docs/types.tsx
+++ b/packages/docs/src/pages/docs/types.tsx
@@ -2426,8 +2426,8 @@ Future handleExternalPurchase(String externalUrl) async {
verification.
-
- PurchaseVerificationProps
+
+ VerifyPurchaseProps
@@ -2439,35 +2439,49 @@ Future handleExternalPurchase(String externalUrl) async {
+ apple
+ |
+
+ Apple App Store verification options. Contains:{' '}
sku
|
- Product identifier to verify |
- androidOptions
+ google
+ |
+
+ Google Play verification options. Contains:{' '}
+ sku, packageName,{' '}
+ purchaseToken, accessToken,{' '}
+ isSub
+ |
+
+
+
+ horizon
|
- Android Play Developer API options. Contains:{' '}
- packageName, productToken,{' '}
- accessToken, isSub
+ Meta Horizon (Quest) verification options. Contains:{' '}
+ sku, userId, accessToken
|
-
- PurchaseVerificationResult
+
+ VerifyPurchaseResult
- Union of PurchaseVerificationResultIOS and{' '}
- PurchaseVerificationResultAndroid.
+ Union of VerifyPurchaseResultIOS,{' '}
+ VerifyPurchaseResultAndroid, and{' '}
+ VerifyPurchaseResultHorizon.
{{
ios: (
<>
- PurchaseVerificationResultIOS
+ VerifyPurchaseResultIOS
@@ -2506,7 +2520,7 @@ Future handleExternalPurchase(String externalUrl) async {
),
android: (
<>
- PurchaseVerificationResultAndroid
+ VerifyPurchaseResultAndroid (Google Play)
@@ -2601,6 +2615,35 @@ Future handleExternalPurchase(String externalUrl) async {
+
+
+ VerifyPurchaseResultHorizon (Meta Quest)
+
+
+
+
+ | Name |
+ Summary |
+
+
+
+
+
+ success
+ |
+ Whether the entitlement verification succeeded |
+
+
+
+ grantTime
+ |
+
+ Unix timestamp when the entitlement was granted (null if
+ verification failed)
+ |
+
+
+
>
),
}}