Skip to content

Commit c799691

Browse files
authored
refactor: move sku into platform-specific verification options (#54)
Refactor again based on #53 - 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 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Breaking Changes * **verifyPurchase API Refactored**: SKU parameter moved from root-level options into platform-specific options. Apple and Google options now require platform-specific SKU fields. Apple verification updated to use SKU instead of legacy JWS field. Horizon platform option added. ## Documentation * Updated release notes and code examples to reflect new API structure. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ff49040 commit c799691

19 files changed

Lines changed: 179 additions & 163 deletions

File tree

packages/apple/Sources/Models/Types.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,17 +1337,14 @@ public struct SubscriptionProductReplacementParamsAndroid: Codable {
13371337

13381338
/// Apple App Store verification parameters.
13391339
/// Used for server-side receipt validation via App Store Server API.
1340-
///
1341-
/// ⚠️ SECURITY: Contains sensitive token (jws). Do not log or persist this data.
13421340
public struct VerifyPurchaseAppleOptions: Codable {
1343-
/// The JWS (JSON Web Signature) representation of the transaction.
1344-
/// ⚠️ Sensitive: Do not log this value.
1345-
public var jws: String
1341+
/// Product SKU to validate
1342+
public var sku: String
13461343

13471344
public init(
1348-
jws: String
1345+
sku: String
13491346
) {
1350-
self.jws = jws
1347+
self.sku = sku
13511348
}
13521349
}
13531350

@@ -1366,17 +1363,21 @@ public struct VerifyPurchaseGoogleOptions: Codable {
13661363
/// Purchase token from the purchase response.
13671364
/// ⚠️ Sensitive: Do not log this value.
13681365
public var purchaseToken: String
1366+
/// Product SKU to validate
1367+
public var sku: String
13691368

13701369
public init(
13711370
accessToken: String,
13721371
isSub: Bool? = nil,
13731372
packageName: String,
1374-
purchaseToken: String
1373+
purchaseToken: String,
1374+
sku: String
13751375
) {
13761376
self.accessToken = accessToken
13771377
self.isSub = isSub
13781378
self.packageName = packageName
13791379
self.purchaseToken = purchaseToken
1380+
self.sku = sku
13801381
}
13811382
}
13821383

@@ -1417,19 +1418,15 @@ public struct VerifyPurchaseProps: Codable {
14171418
public var google: VerifyPurchaseGoogleOptions?
14181419
/// Meta Horizon (Quest) verification parameters.
14191420
public var horizon: VerifyPurchaseHorizonOptions?
1420-
/// Product SKU to validate
1421-
public var sku: String
14221421

14231422
public init(
14241423
apple: VerifyPurchaseAppleOptions? = nil,
14251424
google: VerifyPurchaseGoogleOptions? = nil,
1426-
horizon: VerifyPurchaseHorizonOptions? = nil,
1427-
sku: String
1425+
horizon: VerifyPurchaseHorizonOptions? = nil
14281426
) {
14291427
self.apple = apple
14301428
self.google = google
14311429
self.horizon = horizon
1432-
self.sku = sku
14331430
}
14341431
}
14351432

packages/apple/Sources/OpenIapModule.swift

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -598,25 +598,24 @@ public final class OpenIapModule: NSObject, OpenIapModuleProtocol {
598598
var jws: String = ""
599599
var isValid = false
600600

601-
// If apple options with JWS are provided, use that directly
602-
// Otherwise, fetch the latest transaction from StoreKit
603-
if let appleOptions = props.apple, !appleOptions.jws.isEmpty {
604-
jws = appleOptions.jws
605-
// When JWS is provided externally, we trust it's valid
606-
// The caller should verify the JWS on their server
607-
isValid = true
608-
} else {
609-
do {
610-
let product = try await storeProduct(for: props.sku)
611-
if let result = await product.latestTransaction {
612-
jws = result.jwsRepresentation
613-
let transaction = try checkVerified(result)
614-
latestPurchase = .purchaseIos(await StoreKitTypesBridge.purchaseIOS(from: transaction, jwsRepresentation: result.jwsRepresentation))
615-
isValid = true
616-
}
617-
} catch {
618-
isValid = false
601+
// Apple options with sku is required
602+
guard let appleOptions = props.apple, !appleOptions.sku.isEmpty else {
603+
throw makePurchaseError(
604+
code: .developerError,
605+
message: "Apple verification requires apple options with sku"
606+
)
607+
}
608+
609+
do {
610+
let product = try await storeProduct(for: appleOptions.sku)
611+
if let result = await product.latestTransaction {
612+
jws = result.jwsRepresentation
613+
let transaction = try checkVerified(result)
614+
latestPurchase = .purchaseIos(await StoreKitTypesBridge.purchaseIOS(from: transaction, jwsRepresentation: result.jwsRepresentation))
615+
isValid = true
619616
}
617+
} catch {
618+
isValid = false
620619
}
621620

622621
return VerifyPurchaseResultIOS(

packages/apple/Sources/OpenIapProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public extension OpenIapModuleProtocol {
9292
throw PurchaseError(
9393
code: .featureNotSupported,
9494
message: "Expected iOS validation result",
95-
productId: props.sku
95+
productId: props.apple?.sku
9696
)
9797
}
9898

packages/apple/Sources/OpenIapStore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ public final class OpenIapStore: ObservableObject {
374374
}
375375

376376
public func verifyPurchase(sku: String) async throws -> VerifyPurchaseResultIOS {
377-
let result = try await module.verifyPurchase(VerifyPurchaseProps(sku: sku))
377+
let result = try await module.verifyPurchase(VerifyPurchaseProps(apple: VerifyPurchaseAppleOptions(sku: sku)))
378378
if case let .verifyPurchaseResultIos(iosResult) = result {
379379
return iosResult
380380
}

packages/apple/Tests/OpenIapTests/VerifyPurchaseTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private final class FakeOpenIapModule: OpenIapModuleProtocol {
101101
func getReceiptDataIOS() async throws -> String? { "receipt" }
102102
func validateReceiptIOS(_ props: VerifyPurchaseProps) async throws -> VerifyPurchaseResultIOS {
103103
guard case let .verifyPurchaseResultIos(ios) = validateResult else {
104-
throw PurchaseError(code: .featureNotSupported, message: "Android validation not supported", productId: props.sku)
104+
throw PurchaseError(code: .featureNotSupported, message: "Android validation not supported", productId: props.apple?.sku)
105105
}
106106
return ios
107107
}

packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private final class FakeVerifyPurchaseModule: OpenIapModuleProtocol {
116116
func getReceiptDataIOS() async throws -> String? { "receipt" }
117117
func validateReceiptIOS(_ props: VerifyPurchaseProps) async throws -> VerifyPurchaseResultIOS {
118118
guard case let .verifyPurchaseResultIos(ios) = validateResult else {
119-
throw PurchaseError(code: .featureNotSupported, message: "Expected iOS validation result", productId: props.sku)
119+
throw PurchaseError(code: .featureNotSupported, message: "Expected iOS validation result", productId: props.apple?.sku)
120120
}
121121
return ios
122122
}

packages/docs/src/pages/docs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ function Docs() {
225225
className={({ isActive }) => (isActive ? 'active' : '')}
226226
onClick={closeSidebar}
227227
>
228-
Notes
228+
Updates
229229
</NavLink>
230230
</li>
231231
<li>

packages/docs/src/pages/docs/types.tsx

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,8 +2426,8 @@ Future<void> handleExternalPurchase(String externalUrl) async {
24262426
verification.
24272427
</p>
24282428

2429-
<AnchorLink id="purchase-verification-props" level="h3">
2430-
PurchaseVerificationProps
2429+
<AnchorLink id="verify-purchase-props" level="h3">
2430+
VerifyPurchaseProps
24312431
</AnchorLink>
24322432
<table className="doc-table">
24332433
<thead>
@@ -2439,35 +2439,49 @@ Future<void> handleExternalPurchase(String externalUrl) async {
24392439
<tbody>
24402440
<tr>
24412441
<td>
2442+
<code>apple</code>
2443+
</td>
2444+
<td>
2445+
Apple App Store verification options. Contains:{' '}
24422446
<code>sku</code>
24432447
</td>
2444-
<td>Product identifier to verify</td>
24452448
</tr>
24462449
<tr>
24472450
<td>
2448-
<code>androidOptions</code>
2451+
<code>google</code>
2452+
</td>
2453+
<td>
2454+
Google Play verification options. Contains:{' '}
2455+
<code>sku</code>, <code>packageName</code>,{' '}
2456+
<code>purchaseToken</code>, <code>accessToken</code>,{' '}
2457+
<code>isSub</code>
2458+
</td>
2459+
</tr>
2460+
<tr>
2461+
<td>
2462+
<code>horizon</code>
24492463
</td>
24502464
<td>
2451-
Android Play Developer API options. Contains:{' '}
2452-
<code>packageName</code>, <code>productToken</code>,{' '}
2453-
<code>accessToken</code>, <code>isSub</code>
2465+
Meta Horizon (Quest) verification options. Contains:{' '}
2466+
<code>sku</code>, <code>userId</code>, <code>accessToken</code>
24542467
</td>
24552468
</tr>
24562469
</tbody>
24572470
</table>
24582471

2459-
<AnchorLink id="purchase-verification-result" level="h3">
2460-
PurchaseVerificationResult
2472+
<AnchorLink id="verify-purchase-result" level="h3">
2473+
VerifyPurchaseResult
24612474
</AnchorLink>
24622475
<p>
2463-
Union of <code>PurchaseVerificationResultIOS</code> and{' '}
2464-
<code>PurchaseVerificationResultAndroid</code>.
2476+
Union of <code>VerifyPurchaseResultIOS</code>,{' '}
2477+
<code>VerifyPurchaseResultAndroid</code>, and{' '}
2478+
<code>VerifyPurchaseResultHorizon</code>.
24652479
</p>
24662480
<PlatformTabs>
24672481
{{
24682482
ios: (
24692483
<>
2470-
<h4>PurchaseVerificationResultIOS</h4>
2484+
<h4>VerifyPurchaseResultIOS</h4>
24712485
<table className="doc-table">
24722486
<thead>
24732487
<tr>
@@ -2506,7 +2520,7 @@ Future<void> handleExternalPurchase(String externalUrl) async {
25062520
),
25072521
android: (
25082522
<>
2509-
<h4>PurchaseVerificationResultAndroid</h4>
2523+
<h4>VerifyPurchaseResultAndroid (Google Play)</h4>
25102524
<table className="doc-table">
25112525
<thead>
25122526
<tr>
@@ -2601,6 +2615,35 @@ Future<void> handleExternalPurchase(String externalUrl) async {
26012615
</tr>
26022616
</tbody>
26032617
</table>
2618+
2619+
<h4 style={{ marginTop: '2rem' }}>
2620+
VerifyPurchaseResultHorizon (Meta Quest)
2621+
</h4>
2622+
<table className="doc-table">
2623+
<thead>
2624+
<tr>
2625+
<th>Name</th>
2626+
<th>Summary</th>
2627+
</tr>
2628+
</thead>
2629+
<tbody>
2630+
<tr>
2631+
<td>
2632+
<code>success</code>
2633+
</td>
2634+
<td>Whether the entitlement verification succeeded</td>
2635+
</tr>
2636+
<tr>
2637+
<td>
2638+
<code>grantTime</code>
2639+
</td>
2640+
<td>
2641+
Unix timestamp when the entitlement was granted (null if
2642+
verification failed)
2643+
</td>
2644+
</tr>
2645+
</tbody>
2646+
</table>
26042647
</>
26052648
),
26062649
}}

packages/docs/src/pages/docs/updates/notes.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ function Notes() {
88
return (
99
<div className="doc-page">
1010
<SEO
11-
title="Updates"
11+
title="Notes"
1212
description="Important changes and deprecations in IAP libraries and platforms - API changes, breaking changes, validateReceipt to verifyPurchase migration, and guides."
1313
path="/docs/updates/notes"
1414
keywords="IAP updates, validateReceipt, verifyPurchase, receipt validation, purchase verification, migration guide"
1515
/>
16-
<h1>Updates</h1>
16+
<h1>Notes</h1>
1717
<p>Important changes and deprecations in IAP libraries and platforms.</p>
1818

1919
<section>
@@ -29,29 +29,31 @@ function Notes() {
2929
}}
3030
>
3131
<h4 style={{ marginTop: 0, color: 'var(--text-primary)' }}>
32-
📅 openiap-gql v1.3.3 / openiap-google v1.3.13 / openiap-apple v1.3.1
32+
📅 openiap-gql v1.3.4 / openiap-google v1.3.14 / openiap-apple v1.3.2
3333
- Platform-Specific Verification Options
3434
</h4>
3535
<p>
36-
<strong>verifyPurchase API Refactored:</strong>
36+
<strong>verifyPurchase API Refactored (Breaking Change):</strong>
3737
</p>
3838
<p>
39-
The <code>verifyPurchase</code> API now supports platform-specific
40-
options for Apple, Google, and Meta Horizon stores.
39+
The <code>verifyPurchase</code> API now requires platform-specific
40+
options for Apple, Google, and Meta Horizon stores. The{' '}
41+
<code>sku</code> field has been moved inside each platform-specific
42+
options object.
4143
</p>
4244
<ul>
4345
<li>
4446
<strong>
4547
<code>VerifyPurchaseAppleOptions</code>
4648
</strong>{' '}
47-
- Apple App Store verification with JWS token
49+
- Apple App Store verification with sku
4850
</li>
4951
<li>
5052
<strong>
5153
<code>VerifyPurchaseGoogleOptions</code>
5254
</strong>{' '}
53-
- Google Play verification with packageName, purchaseToken, and
54-
accessToken
55+
- Google Play verification with sku, packageName, purchaseToken,
56+
and accessToken
5557
</li>
5658
<li>
5759
<strong>
@@ -65,11 +67,11 @@ function Notes() {
6567
<strong>New VerifyPurchaseProps Structure:</strong>
6668
</p>
6769
<CodeBlock language="typescript">
68-
{`// Platform-specific verification (recommended)
70+
{`// Platform-specific verification
6971
verifyPurchase({
70-
sku: 'premium_monthly',
71-
apple: { jws: 'eyJ...' }, // iOS App Store
72+
apple: { sku: 'premium_monthly' }, // iOS App Store
7273
google: { // Google Play
74+
sku: 'premium_monthly',
7375
packageName: 'com.example.app',
7476
purchaseToken: 'token...',
7577
accessToken: 'oauth_token...',
@@ -80,20 +82,18 @@ verifyPurchase({
8082
userId: '123456789',
8183
accessToken: 'OC|app_id|app_secret'
8284
}
83-
})
84-
85-
// Legacy format still supported (deprecated)
86-
verifyPurchase({
87-
sku: 'premium_monthly',
88-
androidOptions: { ... } // @deprecated - use google instead
8985
})`}
9086
</CodeBlock>
9187
<p>
92-
<strong>Deprecations:</strong>
88+
<strong>Breaking Changes:</strong>
9389
</p>
9490
<ul>
9591
<li>
96-
<code>androidOptions</code> in VerifyPurchaseProps → Use{' '}
92+
<code>sku</code> removed from <code>VerifyPurchaseProps</code>{' '}
93+
root level → Now inside each platform options
94+
</li>
95+
<li>
96+
<code>androidOptions</code> completely removed → Use{' '}
9797
<code>google</code> instead
9898
</li>
9999
</ul>

0 commit comments

Comments
 (0)