Skip to content
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
143 changes: 134 additions & 9 deletions knowledge/_claude-context/context.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# OpenIAP Project Context

> **Auto-generated for Claude Code**
> Last updated: 2026-01-18T13:00:35.017Z
> Last updated: 2026-01-20T01:17:35.950Z
>
> Usage: `claude --context knowledge/_claude-context/context.md`

Expand Down Expand Up @@ -72,6 +72,64 @@ fun buildModuleAndroid()

**Exception**: Only use `Android` suffix for types that are part of a cross-platform API (e.g., `ProductAndroid`, `PurchaseAndroid` that contrast with iOS types).

## Platform-Specific Field Naming (CRITICAL)

> **This is the most commonly violated rule. Pay extra attention.**

### GraphQL Input Types (API Fields)

Fields inside platform-specific input types do NOT need platform suffix (the type name already indicates the platform):

```graphql
# CORRECT - Fields inside AndroidProps don't need Android suffix
input RequestPurchaseAndroidProps {
skus: [String!]! # Cross-platform, no suffix
offerToken: String # No suffix - already in Android type
isOfferPersonalized: Boolean # No suffix - already in Android type
obfuscatedAccountId: String # No suffix - already in Android type
obfuscatedProfileId: String # No suffix - already in Android type
developerBillingOption: DeveloperBillingOptionParamsAndroid # Type has suffix (cross-platform type)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# INCORRECT - Redundant Android suffix inside Android-specific type
input RequestPurchaseAndroidProps {
offerTokenAndroid: String # ❌ Redundant - type already indicates Android
isOfferPersonalizedAndroid: Boolean # ❌ Redundant - type already indicates Android
}
```

### Why This Matters

1. **Parent type context**: `RequestPurchaseAndroidProps` already indicates Android
2. **Cleaner API**: `google: { offerToken: "..." }` is cleaner than `google: { offerTokenAndroid: "..." }`
3. **Type names still use suffix**: Cross-platform types like `DeveloperBillingOptionParamsAndroid` keep the suffix

### Field Suffix Rules

| Field Location | Suffix Required? | Example |
|----------------|------------------|---------|
| Inside Android-only input type | NO | `offerToken` in `RequestPurchaseAndroidProps` |
| Inside iOS-only input type | NO | `appAccountToken` in `RequestPurchaseIOSProps` |
| Cross-platform type | YES for platform-specific | `nameAndroid` in `ProductAndroid` |
| Cross-platform type reference | YES | `developerBillingOption: DeveloperBillingOptionParamsAndroid` |
| Internal implementation | NO (not API) | `val offerToken` in Kotlin data class |

### Internal vs API Fields

- **API fields** (GraphQL schema): ALWAYS use platform suffix
- **Internal fields** (Kotlin/Swift data classes not exposed): No suffix needed

```kotlin
// Internal helper data class - no suffix needed
internal data class AndroidPurchaseArgs(
val offerToken: String?, // Internal, no suffix OK
val isOfferPersonalized: Boolean? // Internal, no suffix OK
)

// But when reading from API props, use the suffixed names:
val offerToken = params.offerTokenAndroid // ✓ API uses suffix
```

### Cross-Platform Functions

Functions available on BOTH platforms have **NO** platform suffix:
Expand Down Expand Up @@ -705,6 +763,60 @@ swift test # Run tests
swift build # Build package
```

### Objective-C Bridge (CRITICAL for kmp-iap)

**IMPORTANT**: When updating iOS functions in `OpenIapModule.swift`, you **MUST** also update `OpenIapModule+ObjC.swift`.

The Objective-C bridge (`OpenIapModule+ObjC.swift`) exposes Swift async functions to Objective-C/Kotlin for:
- **kmp-iap** (Kotlin Multiplatform via cinterop)
- Any other platform that requires Objective-C interoperability

#### When to Update ObjC Bridge

Update `OpenIapModule+ObjC.swift` when:
- [ ] Adding new public functions to `OpenIapModule.swift`
- [ ] Changing function signatures (parameters, return types)
- [ ] Adding new input options or parameters
- [ ] Changing existing function behavior

#### Bridge Pattern

Every Swift async function needs an Objective-C completion handler wrapper:

```swift
// In OpenIapModule.swift (Swift async)
public func newFeatureIOS(param: String) async throws -> ResultType {
// implementation
}

// In OpenIapModule+ObjC.swift (ObjC bridge - MUST ADD)
@objc func newFeatureIOSWithParam(
_ param: String,
completion: @escaping (Any?, Error?) -> Void
) {
Task {
do {
let result = try await newFeatureIOS(param: param)
let dictionary = OpenIapSerialization.encode(result)
completion(dictionary, nil)
} catch {
completion(nil, error)
}
}
}
```

#### Files to Update Together

| Swift Function Changed | ObjC Bridge Required |
|------------------------|----------------------|
| `OpenIapModule.swift` | `OpenIapModule+ObjC.swift` |

**Verification**: After updating, run:
```bash
swift build # Verifies ObjC bridge compiles
```
Comment thread
hyochan marked this conversation as resolved.

---

## Google Package (packages/google)
Expand Down Expand Up @@ -1094,6 +1206,21 @@ This will:
- **Separate versioning**: Apple and Google packages have independent versions
- **Swift Package Manager**: Automatically works via Git tags, no separate deployment step

---

## Version File Management

### openiap-versions.json

**CRITICAL: NEVER manually edit `openiap-versions.json`**

This file is automatically managed by CI/CD workflows during releases:
- Apple releases update `apple` version
- Google releases update `google` version
- GQL releases update `gql` and `docs` versions

Manual edits will cause version conflicts and deployment issues. Always use the GitHub Actions workflows to update versions.


---

Expand Down Expand Up @@ -1490,15 +1617,15 @@ await endConnection();

Google Play Billing Library enables in-app purchases and subscriptions on Android devices.

## Google Play Billing Version History
## Version History

| Version | Release Date | Key Features |
|---------|--------------|--------------|
| 8.0 | 2025-06-30 | One-time product improvements, multiple purchase options/offers for one-time products, product-level status for unfetched products |
| 8.1 | 2025-11-06 | Minor release with bug fixes and improvements |
| 8.0 | 2025-06-30 | Auto-reconnect, product-level status codes, one-time products with multiple offers, sub-response codes |
| 8.1 | 2025-11-06 | Suspended subscriptions (`isSuspended`), `includeSuspended` parameter, pre-order details, product-level subscription replacement, `KEEP_EXISTING` mode |
| 8.2 | 2025-12-09 | Billing Programs API (external content links, external offers), deprecates old External Offers API |
| 8.2.1 | 2025-12-15 | Bug fix for `isBillingProgramAvailableAsync()` and `createBillingProgramReportingDetailsAsync()` |
| 8.3 | 2025-12-23 | External Payments program, developer billing options |
| 8.3 | 2025-12-23 | External Payments program (Japan only), developer billing options |

**Current Version**: 8.3.0 (as of January 2026)

Expand Down Expand Up @@ -2571,7 +2698,7 @@ This document provides external API reference for Apple's StoreKit 2 framework.
| UI context for purchases | iOS 18.2 | Required for proper payment sheet display |
| External purchase notice | iOS 18.2 | `presentExternalPurchaseNoticeSheetIOS` |
| `appTransactionID` | iOS 18.4 | Globally unique app transaction identifier (back-deployed to iOS 15) |
| `originalPlatform` | iOS 18.4 | Original purchase platform |
| `originalPlatform` | iOS 18.4 | Original purchase platform (back-deployed to iOS 15) |
| `Offer.Period` | iOS 18.4 | Offer period information |
| `advancedCommerceInfo` | iOS 18.4 | Advanced Commerce API data |
| Expanded offer codes | iOS 18.4 | For consumables/non-consumables |
Expand Down Expand Up @@ -2762,10 +2889,8 @@ let result = try await product.purchase(confirmIn: window)
```swift
let appTransaction = try await AppTransaction.shared

// appTransactionID: New in iOS 18.4 (back-deployed to iOS 15)
// New in iOS 18.4 (back-deployed to iOS 15)
let appTransactionID = appTransaction.appTransactionID // Globally unique per Apple Account

// originalPlatform: New in iOS 18.4 (iOS 18.4+ only, NOT back-deployed)
let originalPlatform = appTransaction.originalPlatform // Original purchase platform
```

Expand Down
59 changes: 59 additions & 0 deletions knowledge/internal/01-naming-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,65 @@ fun buildModuleAndroid()

**Exception**: Only use `Android` suffix for types that are part of a cross-platform API (e.g., `ProductAndroid`, `PurchaseAndroid` that contrast with iOS types).

## Platform-Specific Field Naming (CRITICAL)

> **This is the most commonly violated rule. Pay extra attention.**

### GraphQL Input Types (API Fields)

Fields inside platform-specific input types do NOT need platform suffix (the type name already indicates the platform):

```graphql
# CORRECT - Fields inside AndroidProps don't need Android suffix
input RequestPurchaseAndroidProps {
skus: [String!]! # Cross-platform, no suffix
offerToken: String # No suffix - already in Android type
isOfferPersonalized: Boolean # No suffix - already in Android type
obfuscatedAccountId: String # No suffix - already in Android type
obfuscatedProfileId: String # No suffix - already in Android type
developerBillingOption: DeveloperBillingOptionParamsAndroid # Type has suffix (cross-platform type)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# INCORRECT - Redundant Android suffix inside Android-specific type
input RequestPurchaseAndroidProps {
offerTokenAndroid: String # ❌ Redundant - type already indicates Android
isOfferPersonalizedAndroid: Boolean # ❌ Redundant - type already indicates Android
}
```

### Why This Matters

1. **Parent type context**: `RequestPurchaseAndroidProps` already indicates Android
2. **Cleaner API**: `google: { offerToken: "..." }` is cleaner than `google: { offerTokenAndroid: "..." }`
3. **Type names still use suffix**: Cross-platform types like `DeveloperBillingOptionParamsAndroid` keep the suffix

### Field Suffix Rules

| Field Location | Suffix Required? | Example |
|----------------|------------------|---------|
| Inside Android-only input type | NO | `offerToken` in `RequestPurchaseAndroidProps` |
| Inside iOS-only input type | NO | `appAccountToken` in `RequestPurchaseIOSProps` |
| Cross-platform type | YES for platform-specific | `nameAndroid` in `ProductAndroid` |
| Cross-platform type reference | YES | `developerBillingOption: DeveloperBillingOptionParamsAndroid` |
| Internal implementation | NO (not API) | `val offerToken` in Kotlin data class |

### Type vs Field Suffix

- **Type names**: Cross-platform types ALWAYS use platform suffix (`DeveloperBillingOptionParamsAndroid`)
- **Fields in platform-specific inputs**: NO suffix needed (parent type indicates platform)
- **Fields in cross-platform types**: Use suffix for platform-specific fields

```kotlin
// Cross-platform SDK usage
requestPurchase {
google {
skus = listOf("product_id")
offerToken = "discount_offer_token" // ✓ Clean - no redundant suffix
isOfferPersonalized = false
}
}
```

### Cross-Platform Functions

Functions available on BOTH platforms have **NO** platform suffix:
Expand Down
Loading
Loading