Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.

Commit 58b7bca

Browse files
committed
feat: migrate to openiap 1.3.0 with IapStore and store field
- Update openiap versions: apple 1.3.0, google 1.3.10, gql 1.3.0 - Add IapStore type ('unknown' | 'apple' | 'google' | 'horizon') - Add store field to Purchase (platform field deprecated) - Add apple/google fields to RequestPurchasePropsByPlatforms (ios/android deprecated) - Change verifyPurchaseWithProvider iapkit from array to object - Add errors field to VerifyPurchaseWithProviderResult - Create versioned docs for 14.4, update current to 14.5 - Update documentation with correct types and examples
1 parent 51af058 commit 58b7bca

54 files changed

Lines changed: 8251 additions & 171 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,7 @@ class HybridRnIap : HybridRnIapSpec() {
922922
transactionDate = purchase.transactionDate,
923923
purchaseToken = purchase.purchaseToken,
924924
platform = IapPlatform.ANDROID,
925+
store = mapIapStore(purchase.store),
925926
quantity = purchase.quantity.toDouble(),
926927
purchaseState = mapPurchaseState(purchase.purchaseState),
927928
isAutoRenewing = purchase.isAutoRenewing,
@@ -970,7 +971,16 @@ class HybridRnIap : HybridRnIapSpec() {
970971
dev.hyo.openiap.PurchaseState.Unknown -> PurchaseState.UNKNOWN
971972
}
972973
}
973-
974+
975+
private fun mapIapStore(store: dev.hyo.openiap.IapStore): IapStore {
976+
return when (store) {
977+
dev.hyo.openiap.IapStore.Apple -> IapStore.APPLE
978+
dev.hyo.openiap.IapStore.Google -> IapStore.GOOGLE
979+
dev.hyo.openiap.IapStore.Horizon -> IapStore.HORIZON
980+
dev.hyo.openiap.IapStore.Unknown -> IapStore.UNKNOWN
981+
}
982+
}
983+
974984
// Billing error messages handled by OpenIAP
975985

976986
// iOS-specific method - not supported on Android
@@ -1144,19 +1154,28 @@ class HybridRnIap : HybridRnIapSpec() {
11441154
val props = dev.hyo.openiap.VerifyPurchaseWithProviderProps.fromJson(propsMap)
11451155
val result = openIap.verifyPurchaseWithProvider(props)
11461156

1147-
RnIapLog.result("verifyPurchaseWithProvider", mapOf("provider" to result.provider, "iapkitCount" to result.iapkit.size))
1157+
RnIapLog.result("verifyPurchaseWithProvider", mapOf("provider" to result.provider, "hasIapkit" to (result.iapkit != null)))
11481158

11491159
// Convert result to Nitro types
1150-
val nitroIapkitResults = result.iapkit.map { item ->
1160+
val nitroIapkitResult = result.iapkit?.let { item ->
11511161
NitroVerifyPurchaseWithIapkitResult(
11521162
isValid = item.isValid,
11531163
state = mapIapkitPurchaseState(item.state.name),
11541164
store = mapIapkitStore(item.store.name)
11551165
)
1156-
}.toTypedArray()
1166+
}
1167+
1168+
// Convert errors if present
1169+
val nitroErrors = result.errors?.map { error ->
1170+
NitroVerifyPurchaseWithProviderError(
1171+
code = error.code,
1172+
message = error.message
1173+
)
1174+
}?.toTypedArray()
11571175

11581176
NitroVerifyPurchaseWithProviderResult(
1159-
iapkit = nitroIapkitResults,
1177+
iapkit = nitroIapkitResult,
1178+
errors = nitroErrors,
11601179
provider = mapPurchaseVerificationProvider(result.provider.name)
11611180
)
11621181
} catch (e: Exception) {

docs/docs/api/methods/core-methods.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,11 +547,17 @@ const verifyWithIAPKit = async (purchase: Purchase) => {
547547
```typescript
548548
interface VerifyPurchaseWithProviderResult {
549549
provider: 'iapkit';
550-
iapkit: Array<{
550+
iapkit?: {
551551
isValid: boolean;
552552
state: IapkitPurchaseState;
553-
store: 'apple' | 'google';
554-
}>;
553+
store: IapStore;
554+
} | null;
555+
errors?: VerifyPurchaseWithProviderError[] | null;
556+
}
557+
558+
interface VerifyPurchaseWithProviderError {
559+
code?: string | null;
560+
message: string;
555561
}
556562

557563
type IapkitPurchaseState =
@@ -564,6 +570,8 @@ type IapkitPurchaseState =
564570
| 'ready-to-consume'
565571
| 'consumed'
566572
| 'inauthentic';
573+
574+
type IapStore = 'unknown' | 'apple' | 'google' | 'horizon';
567575
```
568576

569577
**Platform Behavior:**

docs/docs/api/types.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,25 @@ The request types have been harmonised to match the schema definitions.
133133

134134
```ts
135135
export interface RequestPurchasePropsByPlatforms {
136-
android?: RequestPurchaseAndroidProps | null;
136+
/** Apple-specific purchase parameters */
137+
apple?: RequestPurchaseIosProps | null;
138+
/** Google-specific purchase parameters */
139+
google?: RequestPurchaseAndroidProps | null;
140+
/** @deprecated Use apple instead */
137141
ios?: RequestPurchaseIosProps | null;
142+
/** @deprecated Use google instead */
143+
android?: RequestPurchaseAndroidProps | null;
138144
}
139145

140146
export interface RequestSubscriptionPropsByPlatforms {
141-
android?: RequestSubscriptionAndroidProps | null;
147+
/** Apple-specific subscription parameters */
148+
apple?: RequestSubscriptionIosProps | null;
149+
/** Google-specific subscription parameters */
150+
google?: RequestSubscriptionAndroidProps | null;
151+
/** @deprecated Use apple instead */
142152
ios?: RequestSubscriptionIosProps | null;
153+
/** @deprecated Use google instead */
154+
android?: RequestSubscriptionAndroidProps | null;
143155
}
144156

145157
export type MutationRequestPurchaseArgs =

docs/docs/examples/purchase-flow.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ const {finishTransaction} = useIAP({
116116
IAPKit returns a standardized response:
117117

118118
```typescript
119-
interface IapkitVerificationResult {
119+
interface RequestVerifyPurchaseWithIapkitResult {
120120
isValid: boolean;
121121
state: IapkitPurchaseState;
122-
store: 'apple' | 'google';
122+
store: IapStore;
123123
}
124124

125125
type IapkitPurchaseState =
@@ -132,6 +132,8 @@ type IapkitPurchaseState =
132132
| 'consumed'
133133
| 'unknown'
134134
| 'inauthentic';
135+
136+
type IapStore = 'unknown' | 'apple' | 'google' | 'horizon';
135137
```
136138

137139
### Verification Methods

docs/docs/intro.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ useEffect(() => {
8787
// Purchase (platform-specific)
8888
await requestPurchase({
8989
request: {
90-
ios: {sku: 'product_id'},
91-
android: {skus: ['product_id']},
90+
apple: {sku: 'product_id'},
91+
google: {skus: ['product_id']},
9292
},
9393
type: 'in-app',
9494
});

docs/docusaurus.config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ const config: Config = {
5151
exclude: ['**/node_modules/**'],
5252
versions: {
5353
current: {
54-
label: '14.4 (Current)',
54+
label: '14.5 (Current)',
5555
path: '',
5656
},
57+
'14.4': {
58+
label: '14.4',
59+
path: '14.4',
60+
},
5761
'14.3': {
5862
label: '14.3',
5963
path: '14.3',
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
---
2+
sidebar_position: 2
3+
---
4+
5+
import GreatFrontEndTopFixed from "@site/src/uis/GreatFrontEndTopFixed";
6+
7+
# Error Codes
8+
9+
<GreatFrontEndTopFixed />
10+
11+
React Native IAP provides a centralized error handling system with platform-specific error code mapping. This ensures consistent error handling across iOS and Android platforms.
12+
13+
## Error Code Enum
14+
15+
### ErrorCode
16+
17+
The `ErrorCode` enum provides standardized error codes that map to platform-specific errors:
18+
19+
```tsx
20+
import {ErrorCode} from 'react-native-iap';
21+
22+
export enum ErrorCode {
23+
Unknown = 'unknown',
24+
UserCancelled = 'user-cancelled',
25+
UserError = 'user-error',
26+
ItemUnavailable = 'item-unavailable',
27+
RemoteError = 'remote-error',
28+
NetworkError = 'network-error',
29+
ServiceError = 'service-error',
30+
ReceiptFailed = 'receipt-failed',
31+
ReceiptFinished = 'receipt-finished',
32+
ReceiptFinishedFailed = 'receipt-finished-failed',
33+
NotPrepared = 'not-prepared',
34+
NotEnded = 'not-ended',
35+
AlreadyOwned = 'already-owned',
36+
DeveloperError = 'developer-error',
37+
BillingResponseJsonParseError = 'billing-response-json-parse-error',
38+
DeferredPayment = 'deferred-payment',
39+
Interrupted = 'interrupted',
40+
IapNotAvailable = 'iap-not-available',
41+
PurchaseError = 'purchase-error',
42+
SyncError = 'sync-error',
43+
TransactionValidationFailed = 'transaction-validation-failed',
44+
ActivityUnavailable = 'activity-unavailable',
45+
AlreadyPrepared = 'already-prepared',
46+
Pending = 'pending',
47+
ConnectionClosed = 'connection-closed',
48+
InitConnection = 'init-connection',
49+
ServiceDisconnected = 'service-disconnected',
50+
QueryProduct = 'query-product',
51+
SkuNotFound = 'sku-not-found',
52+
SkuOfferMismatch = 'sku-offer-mismatch',
53+
ItemNotOwned = 'item-not-owned',
54+
BillingUnavailable = 'billing-unavailable',
55+
FeatureNotSupported = 'feature-not-supported',
56+
EmptySkuList = 'empty-sku-list',
57+
}
58+
```
59+
60+
## PurchaseError
61+
62+
A custom error class for purchase-related errors.
63+
64+
```tsx
65+
export interface PurchaseErrorProps {
66+
message: string;
67+
responseCode?: number;
68+
debugMessage?: string;
69+
code?: ErrorCode;
70+
productId?: string;
71+
platform?: IapPlatform;
72+
}
73+
74+
export type PurchaseError = Error & PurchaseErrorProps;
75+
```
76+
77+
### createPurchaseError
78+
79+
Creates a `PurchaseError` instance.
80+
81+
```tsx
82+
export const createPurchaseError = (
83+
props: PurchaseErrorProps,
84+
): PurchaseError => {
85+
// ...
86+
};
87+
```
88+
89+
### createPurchaseErrorFromPlatform
90+
91+
Creates a `PurchaseError` from platform-specific error data.
92+
93+
```tsx
94+
export const createPurchaseErrorFromPlatform = (
95+
errorData: PurchaseErrorProps,
96+
platform: IapPlatform,
97+
): PurchaseError => {
98+
// ...
99+
};
100+
```
101+
102+
## ErrorCodeUtils
103+
104+
Utility functions for error code mapping and validation.
105+
106+
### getNativeErrorCode
107+
108+
Gets the native error code for the current platform:
109+
110+
```tsx
111+
ErrorCodeUtils.getNativeErrorCode(errorCode: ErrorCode): string
112+
```
113+
114+
### fromPlatformCode
115+
116+
Maps platform-specific error code to standardized ErrorCode:
117+
118+
```tsx
119+
ErrorCodeUtils.fromPlatformCode(
120+
platformCode: string | number,
121+
platform?: IapPlatform,
122+
): ErrorCode
123+
```
124+
125+
### toPlatformCode
126+
127+
Maps ErrorCode to platform-specific code:
128+
129+
```tsx
130+
ErrorCodeUtils.toPlatformCode(
131+
errorCode: ErrorCode,
132+
platform?: IapPlatform,
133+
): string | number
134+
```
135+
136+
### isValidForPlatform
137+
138+
Checks if error code is valid for the specified platform:
139+
140+
```tsx
141+
ErrorCodeUtils.isValidForPlatform(
142+
errorCode: ErrorCode,
143+
platform: IapPlatform,
144+
): boolean
145+
```
146+
147+
## Error Helper Functions
148+
149+
These functions help interpret error objects.
150+
151+
### isUserCancelledError
152+
153+
Returns `true` if the error is a user cancellation error.
154+
155+
```tsx
156+
export function isUserCancelledError(error: unknown): boolean;
157+
```
158+
159+
### isNetworkError
160+
161+
Returns `true` if the error is a network-related error.
162+
163+
```tsx
164+
export function isNetworkError(error: unknown): boolean;
165+
```
166+
167+
### isRecoverableError
168+
169+
Returns `true` if the error is a recoverable error.
170+
171+
```tsx
172+
export function isRecoverableError(error: unknown): boolean;
173+
```
174+
175+
### getUserFriendlyErrorMessage
176+
177+
Returns a user-friendly error message for a given error.
178+
179+
```tsx
180+
export function getUserFriendlyErrorMessage(error: ErrorLike): string;
181+
```
182+
183+
## User-Friendly Error Messages
184+
185+
The `getUserFriendlyErrorMessage` function provides localized and user-friendly messages for common errors.
186+
187+
| ErrorCode | Message |
188+
| --- | --- |
189+
| `UserCancelled` | 'Purchase cancelled' |
190+
| `NetworkError` | 'Network connection error. Please check your internet connection and try again.' |
191+
| `ReceiptFinished` | 'Receipt already finished' |
192+
| `ServiceDisconnected` | 'Billing service disconnected. Please try again.' |
193+
| `BillingUnavailable` | 'Billing is unavailable on this device or account.' |
194+
| `ItemUnavailable` | 'This item is not available for purchase' |
195+
| `ItemNotOwned` | "You don't own this item" |
196+
| `AlreadyOwned` | 'You already own this item' |
197+
| `SkuNotFound` | 'Requested product could not be found' |
198+
| `SkuOfferMismatch` | 'Selected offer does not match the SKU' |
199+
| `DeferredPayment` | 'Payment is pending approval' |
200+
| `NotPrepared` | 'In-app purchase is not ready. Please try again later.' |
201+
| `ServiceError` | 'Store service error. Please try again later.' |
202+
| `FeatureNotSupported` | 'This feature is not supported on this device.' |
203+
| `TransactionValidationFailed` | 'Transaction could not be verified' |
204+
| `ReceiptFailed` | 'Receipt processing failed' |
205+
| `EmptySkuList` | 'No product IDs provided' |
206+
| `InitConnection` | 'Failed to initialize billing connection' |
207+
| `QueryProduct` | 'Failed to query products. Please try again later.' |
208+
| _default_ | 'An unexpected error occurred' |
209+
210+
## See Also
211+
212+
- [Error Handling Guide](../guides/error-handling)
213+
- [useIAP Hook](./use-iap)
214+
- [Types Reference](./types)
215+
- [Troubleshooting](../guides/troubleshooting)

0 commit comments

Comments
 (0)