Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.
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
38 changes: 29 additions & 9 deletions android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ class HybridRnIap : HybridRnIapSpec() {
transactionDate = purchase.transactionDate,
purchaseToken = purchase.purchaseToken,
platform = IapPlatform.ANDROID,
store = mapIapStore(purchase.store),
quantity = purchase.quantity.toDouble(),
purchaseState = mapPurchaseState(purchase.purchaseState),
isAutoRenewing = purchase.isAutoRenewing,
Expand Down Expand Up @@ -970,7 +971,16 @@ class HybridRnIap : HybridRnIapSpec() {
dev.hyo.openiap.PurchaseState.Unknown -> PurchaseState.UNKNOWN
}
}


private fun mapIapStore(store: dev.hyo.openiap.IapStore): IapStore {
return when (store) {
dev.hyo.openiap.IapStore.Apple -> IapStore.APPLE
dev.hyo.openiap.IapStore.Google -> IapStore.GOOGLE
dev.hyo.openiap.IapStore.Horizon -> IapStore.HORIZON
dev.hyo.openiap.IapStore.Unknown -> IapStore.UNKNOWN
}
}

// Billing error messages handled by OpenIAP

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

RnIapLog.result("verifyPurchaseWithProvider", mapOf("provider" to result.provider, "iapkitCount" to result.iapkit.size))
RnIapLog.result("verifyPurchaseWithProvider", mapOf("provider" to result.provider, "hasIapkit" to (result.iapkit != null)))

// Convert result to Nitro types
val nitroIapkitResults = result.iapkit.map { item ->
val nitroIapkitResult = result.iapkit?.let { item ->
NitroVerifyPurchaseWithIapkitResult(
isValid = item.isValid,
state = mapIapkitPurchaseState(item.state.name),
store = mapIapkitStore(item.store.name)
)
}.toTypedArray()
}

// Convert errors if present
val nitroErrors = result.errors?.map { error ->
NitroVerifyPurchaseWithProviderError(
code = error.code,
message = error.message
)
}?.toTypedArray()

NitroVerifyPurchaseWithProviderResult(
iapkit = nitroIapkitResults,
iapkit = nitroIapkitResult,
errors = nitroErrors,
provider = mapPurchaseVerificationProvider(result.provider.name)
)
} catch (e: Exception) {
Expand Down Expand Up @@ -1419,11 +1438,12 @@ class HybridRnIap : HybridRnIapSpec() {
}
}

private fun mapIapkitStore(storeName: String): IapkitStore {
private fun mapIapkitStore(storeName: String): IapStore {
return when (storeName.uppercase()) {
"APPLE" -> IapkitStore.APPLE
"GOOGLE" -> IapkitStore.GOOGLE
else -> IapkitStore.GOOGLE // Default to Google on Android
"APPLE" -> IapStore.APPLE
"GOOGLE" -> IapStore.GOOGLE
"HORIZON" -> IapStore.HORIZON
else -> IapStore.UNKNOWN
}
}

Expand Down
14 changes: 11 additions & 3 deletions docs/docs/api/methods/core-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,11 +547,17 @@ const verifyWithIAPKit = async (purchase: Purchase) => {
```typescript
interface VerifyPurchaseWithProviderResult {
provider: 'iapkit';
iapkit: Array<{
iapkit?: {
isValid: boolean;
state: IapkitPurchaseState;
store: 'apple' | 'google';
}>;
store: IapStore;
} | null;
errors?: VerifyPurchaseWithProviderError[] | null;
}

interface VerifyPurchaseWithProviderError {
code?: string | null;
message: string;
}

type IapkitPurchaseState =
Expand All @@ -564,6 +570,8 @@ type IapkitPurchaseState =
| 'ready-to-consume'
| 'consumed'
| 'inauthentic';

type IapStore = 'unknown' | 'apple' | 'google' | 'horizon';
```

**Platform Behavior:**
Expand Down
16 changes: 14 additions & 2 deletions docs/docs/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,25 @@ The request types have been harmonised to match the schema definitions.

```ts
export interface RequestPurchasePropsByPlatforms {
android?: RequestPurchaseAndroidProps | null;
/** Apple-specific purchase parameters */
apple?: RequestPurchaseIosProps | null;
/** Google-specific purchase parameters */
google?: RequestPurchaseAndroidProps | null;
/** @deprecated Use apple instead */
ios?: RequestPurchaseIosProps | null;
/** @deprecated Use google instead */
android?: RequestPurchaseAndroidProps | null;
}

export interface RequestSubscriptionPropsByPlatforms {
android?: RequestSubscriptionAndroidProps | null;
/** Apple-specific subscription parameters */
apple?: RequestSubscriptionIosProps | null;
/** Google-specific subscription parameters */
google?: RequestSubscriptionAndroidProps | null;
/** @deprecated Use apple instead */
ios?: RequestSubscriptionIosProps | null;
/** @deprecated Use google instead */
android?: RequestSubscriptionAndroidProps | null;
}

export type MutationRequestPurchaseArgs =
Expand Down
6 changes: 4 additions & 2 deletions docs/docs/examples/purchase-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ const {finishTransaction} = useIAP({
IAPKit returns a standardized response:

```typescript
interface IapkitVerificationResult {
interface RequestVerifyPurchaseWithIapkitResult {
isValid: boolean;
state: IapkitPurchaseState;
store: 'apple' | 'google';
store: IapStore;
}

type IapkitPurchaseState =
Expand All @@ -132,6 +132,8 @@ type IapkitPurchaseState =
| 'consumed'
| 'unknown'
| 'inauthentic';

type IapStore = 'unknown' | 'apple' | 'google' | 'horizon';
```

### Verification Methods
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ useEffect(() => {
// Purchase (platform-specific)
await requestPurchase({
request: {
ios: {sku: 'product_id'},
android: {skus: ['product_id']},
apple: {sku: 'product_id'},
google: {skus: ['product_id']},
},
type: 'in-app',
});
Expand Down
6 changes: 5 additions & 1 deletion docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ const config: Config = {
exclude: ['**/node_modules/**'],
versions: {
current: {
label: '14.4 (Current)',
label: '14.5 (Current)',
path: '',
},
'14.4': {
label: '14.4',
path: '14.4',
},
'14.3': {
label: '14.3',
path: '14.3',
Expand Down
215 changes: 215 additions & 0 deletions docs/versioned_docs/version-14.4/api/error-codes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
---
sidebar_position: 2
---

import GreatFrontEndTopFixed from "@site/src/uis/GreatFrontEndTopFixed";

# Error Codes

<GreatFrontEndTopFixed />

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.

## Error Code Enum

### ErrorCode

The `ErrorCode` enum provides standardized error codes that map to platform-specific errors:

```tsx
import {ErrorCode} from 'react-native-iap';

export enum ErrorCode {
Unknown = 'unknown',
UserCancelled = 'user-cancelled',
UserError = 'user-error',
ItemUnavailable = 'item-unavailable',
RemoteError = 'remote-error',
NetworkError = 'network-error',
ServiceError = 'service-error',
ReceiptFailed = 'receipt-failed',
ReceiptFinished = 'receipt-finished',
ReceiptFinishedFailed = 'receipt-finished-failed',
NotPrepared = 'not-prepared',
NotEnded = 'not-ended',
AlreadyOwned = 'already-owned',
DeveloperError = 'developer-error',
BillingResponseJsonParseError = 'billing-response-json-parse-error',
DeferredPayment = 'deferred-payment',
Interrupted = 'interrupted',
IapNotAvailable = 'iap-not-available',
PurchaseError = 'purchase-error',
SyncError = 'sync-error',
TransactionValidationFailed = 'transaction-validation-failed',
ActivityUnavailable = 'activity-unavailable',
AlreadyPrepared = 'already-prepared',
Pending = 'pending',
ConnectionClosed = 'connection-closed',
InitConnection = 'init-connection',
ServiceDisconnected = 'service-disconnected',
QueryProduct = 'query-product',
SkuNotFound = 'sku-not-found',
SkuOfferMismatch = 'sku-offer-mismatch',
ItemNotOwned = 'item-not-owned',
BillingUnavailable = 'billing-unavailable',
FeatureNotSupported = 'feature-not-supported',
EmptySkuList = 'empty-sku-list',
}
```

## PurchaseError

A custom error class for purchase-related errors.

```tsx
export interface PurchaseErrorProps {
message: string;
responseCode?: number;
debugMessage?: string;
code?: ErrorCode;
productId?: string;
platform?: IapPlatform;
}

export type PurchaseError = Error & PurchaseErrorProps;
```

### createPurchaseError

Creates a `PurchaseError` instance.

```tsx
export const createPurchaseError = (
props: PurchaseErrorProps,
): PurchaseError => {
// ...
};
```

### createPurchaseErrorFromPlatform

Creates a `PurchaseError` from platform-specific error data.

```tsx
export const createPurchaseErrorFromPlatform = (
errorData: PurchaseErrorProps,
platform: IapPlatform,
): PurchaseError => {
// ...
};
```

## ErrorCodeUtils

Utility functions for error code mapping and validation.

### getNativeErrorCode

Gets the native error code for the current platform:

```tsx
ErrorCodeUtils.getNativeErrorCode(errorCode: ErrorCode): string
```

### fromPlatformCode

Maps platform-specific error code to standardized ErrorCode:

```tsx
ErrorCodeUtils.fromPlatformCode(
platformCode: string | number,
platform?: IapPlatform,
): ErrorCode
```

### toPlatformCode

Maps ErrorCode to platform-specific code:

```tsx
ErrorCodeUtils.toPlatformCode(
errorCode: ErrorCode,
platform?: IapPlatform,
): string | number
```

### isValidForPlatform

Checks if error code is valid for the specified platform:

```tsx
ErrorCodeUtils.isValidForPlatform(
errorCode: ErrorCode,
platform: IapPlatform,
): boolean
```

## Error Helper Functions

These functions help interpret error objects.

### isUserCancelledError

Returns `true` if the error is a user cancellation error.

```tsx
export function isUserCancelledError(error: unknown): boolean;
```

### isNetworkError

Returns `true` if the error is a network-related error.

```tsx
export function isNetworkError(error: unknown): boolean;
```

### isRecoverableError

Returns `true` if the error is a recoverable error.

```tsx
export function isRecoverableError(error: unknown): boolean;
```

### getUserFriendlyErrorMessage

Returns a user-friendly error message for a given error.

```tsx
export function getUserFriendlyErrorMessage(error: ErrorLike): string;
```

## User-Friendly Error Messages

The `getUserFriendlyErrorMessage` function provides localized and user-friendly messages for common errors.

| ErrorCode | Message |
| --- | --- |
| `UserCancelled` | 'Purchase cancelled' |
| `NetworkError` | 'Network connection error. Please check your internet connection and try again.' |
| `ReceiptFinished` | 'Receipt already finished' |
| `ServiceDisconnected` | 'Billing service disconnected. Please try again.' |
| `BillingUnavailable` | 'Billing is unavailable on this device or account.' |
| `ItemUnavailable` | 'This item is not available for purchase' |
| `ItemNotOwned` | "You don't own this item" |
| `AlreadyOwned` | 'You already own this item' |
| `SkuNotFound` | 'Requested product could not be found' |
| `SkuOfferMismatch` | 'Selected offer does not match the SKU' |
| `DeferredPayment` | 'Payment is pending approval' |
| `NotPrepared` | 'In-app purchase is not ready. Please try again later.' |
| `ServiceError` | 'Store service error. Please try again later.' |
| `FeatureNotSupported` | 'This feature is not supported on this device.' |
| `TransactionValidationFailed` | 'Transaction could not be verified' |
| `ReceiptFailed` | 'Receipt processing failed' |
| `EmptySkuList` | 'No product IDs provided' |
| `InitConnection` | 'Failed to initialize billing connection' |
| `QueryProduct` | 'Failed to query products. Please try again later.' |
| _default_ | 'An unexpected error occurred' |

## See Also

- [Error Handling Guide](../guides/error-handling)
- [useIAP Hook](./use-iap)
- [Types Reference](./types)
- [Troubleshooting](../guides/troubleshooting)
Loading