# Implementation Guidelines

## Commit Message Convention

Follow the monorepo
[Git Commit Message Format](../../knowledge/internal/06-git-deployment.md#git-commit-message-format)
with the `expo` scope, e.g. `fix(expo): resolve purchase crash`.

## Tooling & Package Management

- **Use Bun exclusively.** Run installs with `bun install`, scripts with `bun run <script>`, add deps via `bun add` / `bun add -d`.
- Do **not** suggest or create `package-lock.json` or `yarn.lock`; `bun.lock` is the single source of truth.

## Expo-Specific Guidelines

### iOS Pod Configuration

**CRITICAL WARNING**: Never modify the iOS platform version in `ios/ExpoIap.podspec`

- iOS platform version MUST remain at `13.4` even though the code requires iOS 15.0+
- Changing iOS to `15.0` can cause expo prebuild to exclude the module in certain Expo SDKs (known bug)
- See issue: [#168](https://github.com/hyochan/expo-iap/issues/168)
- This is kept at `13.4` for compatibility with affected Expo SDKs
- The actual iOS 15.0+ requirement is enforced at build time via @available annotations
- Users must follow the deployment target required by their Expo SDK. The validated Expo SDK 57 example uses iOS 16.4; older supported SDKs must never go below the module's iOS 15.0 runtime requirement.

**tvOS Exception**: tvOS platform version MUST be `16.0` (not `13.4`) because:

- The `openiap` CocoaPods dependency requires tvOS 16.0 minimum
- Setting tvOS to `13.4` causes build failure due to dependency mismatch
- This is a hard requirement from the dependency, not subject to the iOS workaround

### Pre-Commit Checks

Before committing any changes:

1. Run `bun run lint` to ensure code quality
2. Run `bun run lint:tsc` to verify TypeScript types
3. Run `bun run test` to verify all tests pass (Note: Use `bun run test`, not `bun test`)
4. **IMPORTANT**: Run tests in the example directory as well:
   - `cd example && bun run test`
   - Ensure all tests pass with 100% success rate
   - Fix any failing tests before committing
5. Only commit if all checks succeed

### Platform-Specific Naming Conventions

#### Function Naming

- Functions that only operate on one platform must carry the suffix: `nameIOS` or `nameAndroid` (e.g. `getReceiptDataIOS`, `deepLinkToSubscriptionsAndroid`).
- Cross-platform helpers should expose a single name and branch internally via `Platform.select` or equivalent.

#### Field Naming

- **iOS-related fields**: Use `IOS` suffix (e.g., `displayNameIOS`, `subscriptionGroupIdIOS`, `introductoryPriceIOS`)
  - **Exception**: When an acronym appears at the end of a field name, use uppercase (e.g., `quantityIOS`, `appBundleIdIOS`, not `quantityIos`)
  - Platform-specific fields: `currencyCodeIOS`, `currencySymbolIOS`, `countryCodeIOS`
  - Product fields: `isFamilyShareableIOS`, `jsonRepresentationIOS`, `subscriptionGroupIdIOS`
- **Android-related fields**: Use `Android` suffix (e.g., `nameAndroid`)
  - Platform-specific fields: `productStatusAndroid`, `basePlanIdAndroid`
  - In **cross-platform types** (e.g., `SubscriptionOffer`, `DiscountOffer`), use `pricingPhasesAndroid` suffix to distinguish from common fields
- **Common fields**: Fields shared across platforms go in Common types (e.g., `ids`, `platform`, `debugDescription`)
  - Use these for data that exists on both platforms without platform-specific variations

#### Type Naming

- **iOS types**: Use `IOS` suffix (e.g., `PurchaseIOS`, `ProductIOS`)
- **Android types**: Use descriptive prefixes to identify subtypes:
  - ✅ Good: `BillingProgramAvailabilityResultAndroid`, `SubscriptionPeriodIOS`, `PurchaseAndroidState`
  - ❌ Avoid: `OneTimePurchaseOfferDetails`, `SubscriptionOfferAndroid`, `PurchaseStateAndroid`
- **General IAP types**: Use `Iap` prefix (e.g., `IapPurchase`, not `IAPPurchase`)

#### General Rules

- **ID fields**: Use `Id` instead of `ID` (e.g., `productId`, `transactionId`, not `productID`, `transactionID`)
- **Consistent naming**: This applies to functions, types, and file names
- **Deprecation**: Fields without platform suffixes are legacy and should only be removed in a planned major release.

### Type System

For complete type definitions and documentation, see: <https://openiap.dev/docs/types>

The library follows the OpenIAP type specifications with platform-specific extensions using iOS/Android suffixes.

> **Important:** `src/types.ts` is generated from the OpenIAP schema. Never edit this file manually or commit hand-written changes. In this monorepo, run `cd specs/client && bun run generate` from the repository root so every platform copy stays synchronized. The Expo-local `bun run generate:types` command is only for a standalone checkout: it atomically refreshes the platform-ready file from the raw `openiap-client-protocol-${clientProtocol}` tag pinned by `openiap-versions.json` (or the version passed with `--tag`).

- Whenever you need Request/Params/Result types in the JS API surface (`src/index.ts`, hooks, modules, examples), import them directly from the generated `src/types.ts` (e.g., `MutationRequestPurchaseArgs`, `QueryFetchProductsArgs`). Bind exported functions with the generated `QueryField` / `MutationField` helpers so their signatures stay in lockstep with `types.ts` instead of redefining ad-hoc unions like `ProductTypeInput`.

### React/JSX Conventions

- **Conditional Rendering**: Use ternary operator with null instead of logical AND
  - ✅ Good: `{condition ? <Component /> : null}`
  - ❌ Avoid: `{condition && <Component />}`

### Hook API Semantics (useIAP)

- Inside the `useIAP` hook, most methods return `Promise<void>` and update internal state. Do not design examples or implementations that expect data from these methods.
  - Examples: `fetchProducts`, `requestPurchase`, `getAvailablePurchases`, `getActiveSubscriptions`.
  - After calling, consume state from the hook: `products`, `subscriptions`, `availablePurchases`, `activeSubscriptions`, etc.
- Defined exceptions that DO return values in the hook:
  - `hasActiveSubscriptions(subscriptionIds?) => Promise<boolean>`
- The root (index) API is value-returning and can be awaited to receive data directly. Use root API when not using React state.

### API Method Naming

- Functions that depend on event results should use `request` prefix (e.g., `requestPurchase`)
- Follow OpenIAP terminology: <https://openiap.dev/docs/apis#terminology>
- Do not use generic prefixes like `get`, `find` - refer to the official terminology

## IAP-Specific Guidelines

### OpenIAP Specification

All implementations must follow the OpenIAP specification:

- **APIs**: <https://openiap.dev/docs/apis>
- **Types**: <https://openiap.dev/docs/types>
- **Events**: <https://openiap.dev/docs/events>
- **Errors**: <https://openiap.dev/docs/errors>

### Feature Development Process

For new feature proposals:

1. Before implementing, discuss at: <https://github.com/hyodotdev/openiap/discussions>
2. Get community feedback and consensus
3. Ensure alignment with OpenIAP standards
4. Implement following the agreed specification

### State Management with OpenIapStore

#### IapStatus Structure

The `IapStatus` struct provides standardized state management for OpenIAP operations:

```swift
public struct IapStatus {
    public var loadings: LoadingStates = LoadingStates()
    public var lastPurchaseResult: PurchaseResultData?
    public var lastError: ErrorData?
    // ...
}

public struct LoadingStates {
    public var initConnection: Bool = false
    public var fetchProducts: Bool = false
    public var restorePurchases: Bool = false
    public var purchasing: Set<String> = []  // Product IDs currently being purchased
}
```

#### Usage Guidelines

- **OpenIapStore** manages data state only, not UI state
- UI components should manage their own display state (alerts, sheets, etc.)
- Loading states are automatically managed within OpenIapStore
- Use `status.loadings.purchasing.contains(productId)` to check if a specific product is being purchased
- Use `status.isLoading` computed property to check if any operation is in progress

### Error Handling

#### Error Code Format

All error codes in expo-iap follow the OpenIAP specification and use **kebab-case** format:

```typescript
export enum ErrorCode {
  UserCancelled = 'user-cancelled', // NOT 'E_USER_CANCELLED'
  NetworkError = 'network-error',
  ItemUnavailable = 'item-unavailable',
  AlreadyOwned = 'already-owned',
  // ...
}
```

**Important**:

- ✅ Use `ErrorCode.UserCancelled` enum in TypeScript code
- ✅ Error codes are kebab-case strings: `'user-cancelled'`
- ❌ Never use deprecated `E_` prefix: `'E_USER_CANCELLED'`
- ❌ Never use UPPERCASE format: `'USER_CANCELLED'`

#### Error Object Structure

All errors returned from native modules have the following structure:

```typescript
interface PurchaseError {
  code: ErrorCode; // Standardized error code enum
  message: string; // Human-readable error message
  responseCode?: number; // Platform-specific response code
  debugMessage?: string; // Additional debug information
  productId?: string; // Product ID if applicable
}
```

Example error object:

```json
{
  "code": "user-cancelled",
  "message": "User cancelled the purchase",
  "responseCode": 1,
  "debugMessage": "User pressed cancel button",
  "productId": "com.example.premium"
}
```

#### Using Error Codes

Always use the `ErrorCode` enum for type-safe error handling:

```typescript
import {useIAP, ErrorCode} from 'expo-iap';

const {requestPurchase} = useIAP({
  onPurchaseError: (error) => {
    // ✅ Correct - use ErrorCode enum
    if (error.code === ErrorCode.UserCancelled) {
      console.log('User cancelled');
      return;
    }

    // ❌ Wrong - don't use string literals
    if (error.code === 'E_USER_CANCELLED') {
      /* ... */
    }
    if (error.code === 'USER_CANCELLED') {
      /* ... */
    }

    // ✅ Correct - use switch with enum
    switch (error.code) {
      case ErrorCode.NetworkError:
        showRetryDialog();
        break;
      case ErrorCode.ItemUnavailable:
        showUnavailableMessage();
        break;
      default:
        console.error(error.message);
    }
  },
});
```

For complete error handling documentation, see:

- [Error Codes Reference](https://openiap.dev/docs/errors)
- [Error Handling Guide](https://docs.expo-iap.dev/docs/guides/error-handling)

## Documentation Guidelines

### Blog Post Conventions

- **Title format**: Use version number only (e.g., `v2.8.7 - Feature Description`, not `Expo IAP v2.8.7 - Feature Description`)
- **Heading format**: Use version number only in headings (e.g., `# v2.8.7 Release Notes`, not `# Expo IAP v2.8.7 Release Notes`)
