Safety commit so the 94 uncommitted files in the working tree (in-flight Account/Mac/Tip work plus the feature-preview recording tooling) survive the move onto cursor/cloud-agent-1787824905651-vxuwc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.0 KiB
StoreKit — managed credit packs
OSGKeyboard ships three Consumable in-app purchases and no subscriptions.
All three add credits to the optional OSG managed account. The authoritative
local catalog is OSGKeyboard.storekit; App Store
Connect must match it exactly.
| Product ID | Type | Reference name | Display name (en) | Price (CN) | Grants |
|---|---|---|---|---|---|
500tks |
Consumable | 500 OSG 积分 | 500 OSG Credits | ¥8 | 500 account credits |
1500tks |
Consumable | 1,500 OSG 积分 | 1,500 OSG Credits | ¥18 | 1,500 account credits |
3000tks |
Consumable | 3,000 OSG 积分 | 3,000 OSG Credits | ¥28 | 3,000 account credits |
Rules that apply to all three:
- Consumables cannot be restored (Apple policy). The UI states this.
- Nothing here gates a feature. Local dictation, BYOK providers, typing, clipboard skills, and iCloud sync stay free with or without a purchase.
- Credits only add balance to the optional OSG account. They are useless without Sign in with Apple, and the app never requires an account.
- Credit packs are iOS / iPadOS only. The macOS build ships via Developer ID outside the Mac App Store, so it loads no StoreKit products at all.
Where the packs appear
iOS Home → credit card → Account Center, and Settings → Account Center. Both require Sign in with Apple.
Purchase flow (server-verified)
Credit packs are not client-trusted. AccountCreditPurchaseManager:
- Buys with StoreKit 2, binding
appAccountTokento the signed-in account ID. - Rejects any transaction whose
appAccountTokenor product ID does not match the configured set. - Submits the signed transaction to the account service
(
submitCreditTransaction) and checks the returned transaction and product IDs against the local ones. - Calls
transaction.finish()only after the server confirms and the ledger is updated.
A purchase that fails server verification stays unfinished and is retried from the transaction-updates stream, so a crashed or backgrounded app does not lose credits.
App Store Connect setup
- Open App Store Connect → OSGKeyboard → In-App Purchases.
- Create each of the three products as Consumable with the exact Product IDs above. Do not rename an existing ID — Apple treats it as a new product.
- Add en + zh-Hans localizations. Copy comes from
OSGKeyboard.storekit(localizations) anddocs/APPSTORE_METADATA.md. - Set pricing to the nearest App Store tier for China (¥8 / ¥18 / ¥28) and let Apple map the equivalent tiers elsewhere.
- Submit the IAPs for review together with the app version that exposes them. A first IAP submitted without a build is rejected as incomplete.
Review screenshot (required — otherwise「元数据丢失」)
Each product needs a screenshot under 审核信息 → 截屏: run the app signed in, open Home → credit card → Account Center, and capture the pack list showing localized prices.
Optional 审核备注:
Consumable credits for the optional OSG managed speech/AI service.
Requires Sign in with Apple. Local dictation and user-supplied API keys
remain free and never require credits. Consumable — cannot restore.
Save — status should become 准备提交.
Local testing (StoreKit Test — no Connect, no sandbox account)
The OSGKeyboard scheme already references OSGKeyboard.storekit in
project.yml (storeKitConfiguration).
./Scripts/generate-xcodeproj.sh && open OSGKeyboard.xcodeproj- Product → Scheme → Edit Scheme → Run → Options — confirm
StoreKit Configuration =
OSGKeyboard.storekit - Run OSGKeyboard on a simulator (e.g. iPhone 17) or a device.
- Sign in with Apple, then open Home → credit card → Account Center.
- Buy
500tks→ balance increases by 500 after the account service confirms; the purchase-history row appears. - Cancel a purchase → returns to idle with no error spam.
- Repeat a purchase → allowed (Consumable).
Debug menu: Xcode → Debug → StoreKit → Manage Transactions to inspect or delete test purchases.
Automated coverage: AccountCreditPurchaseManagerTests (in the hermetic
manifest — see TESTING.md) and AccountCenterUITests, which runs
separately via xcodebuild -scheme OSGKeyboardUITests against the IAPReview
stub harness — see
ACCOUNT_MANAGED_GATEWAY_TESTING.md.
Sandbox testing (after Connect IAPs reach 准备提交)
- App Store Connect → 用户和访问 → 沙盒 → create a Sandbox Tester.
- On device: 设置 → App Store → 沙盒账户 → sign in (not your real Apple ID).
- Install via TestFlight, or Debug-run with StoreKit Configuration = None so the app hits Connect products.
- Buy each pack once and confirm the server-side ledger, not just the UI.
- Sandbox charges are free; the receipt path is the real one.
The full server-side matrix (replayed transactions, interrupted network, wrong-account rejection) lives in ACCOUNT_MANAGED_GATEWAY_TESTING.md.
Release checklist
- All three products load and show localized prices
- Each pack adds the correct balance after server verification
- Purchase history lists the new transaction without duplicates
- A failed/interrupted verification is retried and not lost
- User cancel returns to idle (no error spam)
- Repeat purchase works (Consumable allows multiple)
- No Restore button for any of these products
- Local dictation, BYOK, typing, and clipboard skills unchanged after purchase
Files
| File | Role |
|---|---|
OSGKeyboard/Views/Account/AccountCreditPurchaseManager.swift |
Purchase + server verification |
OSGKeyboard/Views/Account/AccountCreditStore.swift |
StoreKit 2 product/purchase abstraction |
OSGKeyboard/Views/Account/AccountCenterView.swift |
Credit pack UI, purchase history |
OSGKeyboard.storekit |
Local StoreKit Test catalog |