release: promote development → main (2026-06-19T05:13:28Z) - #97
Conversation
When PayCraft.initialize(apiKey, Cloud) runs, it sets PayCraft.apiKey and PayCraft.backend synchronously. However, PayCraft.config is only populated AFTER the async cloud SuiteConfig fetch completes. The PayCraftModule Koin singleton for SupabaseClient was calling requireConfig() at materialization time, assuming config was populated. In reality, Koin materializes the singleton (e.g. when resolving the PayCraftPaywallViewModel) before the cloud fetch finishes, throwing 'PayCraft.initialize(apiKey) must be called before use'. Fix: Read PayCraft.backend (known synchronously) and PayCraft.apiKey (set synchronously) instead of requiring config. Both are available immediately after initialize() returns. This unblocks Cloud-mode initialization and allows the BillingManager to start its cache+sync flow without blocking on the config fetch. Verified: app launches on Android, PayCraft logs 'Backend=cloud', no Koin DI crashes, BillingManager initializes cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… placeholder config Three related fixes for the v2 Cloud SDK initialization path: 1. Placeholder config in initialize() — Cloud/SelfHosted backends now populate PayCraft.config synchronously with an empty-list PayCraftConfig before kicking off the async cloud fetch. This means requireConfig() callers (PayCraftBanner.kt:138, PaywallScreen consumers, etc.) never throw IllegalStateException between init and the first SuiteConfig arrival. 2. Auto-fetch SuiteConfig on init — fetchAndApplySuiteConfig() runs in sdkScope at the tail of initialize() for non-Mock backends. Issues GET $backend.configUrl?apiKey=…, decodes the SuiteConfig response, and publishes via applySuiteConfig(). On HTTP non-success / network failure, logs and leaves the placeholder in place so UI shows empty state instead of crashing. 3. Koin DI race fix — PayCraftModule's SupabaseClient singleton now reads PayCraft.backend (statically known for Cloud) and PayCraft.apiKey (set synchronously by initialize()) instead of going through requireConfig() which depends on the async fetch. Plus: deploy.sh Phase 3.5 FUNCTIONS DEPLOY — wires up the Edge Function deploy step using framework-supabase-access-token from the vault. Verified on Android (CPH2423): app launches, PayCraft logs initialize + GET to /functions/v1/config, no Koin or requireConfig crashes, BillingManager initializes cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…l anon key
Two related fixes for Cloud-mode SuiteConfig fetch:
1. Add Authorization + apikey headers to GET /functions/v1/config.
Supabase Edge Functions require Authorization: Bearer <anon-key>
by default (verify_jwt=true platform-level). Without these headers
the function returns 401 UNAUTHORIZED_NO_AUTH_HEADER and the SDK
never loads its plans/products.
2. Replace PUBLIC_PAYCRAFT_ANON_KEY_PLACEHOLDER in PayCraftBackend.Cloud
with the real framework-supabase anon key. Anon key is publishable
by design (same security model as pk_live_*) — RLS prevents data
exposure. This unblocks every Cloud-mode SDK consumer.
Verified end-to-end on Android (CPH2423):
Backend = cloud
GET …/functions/v1/config?apiKey=pk_live_…
══ PayCraft SuiteConfig applied ═══════════════
Source = Cloud, Products = 4, Locale = US
[loadConfig] cloud fetch ok — 4 products
No crashes. BillingManager + paywall render with real cloud data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refines the SDK paywall composables so the rendered Free state matches the
dashboard's Paywall designer reference mockup, while preserving all 6 state
paths (Loading / Free / Premium / Error / DeviceConflict / OwnershipVerified)
the SDK already handled.
Changes:
1. PayCraftPaywallHeader — hero icon container is now a 64dp rounded-square
tile (was 72dp circle), default icon switched from Star to PlayArrow,
accepts an optional subtitle string rendered below the title in
bodyMedium/onSurfaceVariant.
2. PayCraftPlanCard — drops the leading radio button (border alone signals
selection), moves the POPULAR badge to a center-top pill that overlaps
the card's top edge (matches the designer's 'MOST POPULAR' pill).
3. PaywallLegalFooter (new) — minimal 'PRIVACY · TERMS · RESTORE' row
rendered at the foot of the Free state. Privacy/Terms open URLs from
paywall.themeJsonb (privacy_url / terms_url) with paycraft.mobilebytesensei.com
defaults; Restore dispatches PayCraftPaywallAction.RestoreSubscription.
4. PayCraftPaywall.kt Free state — replaces the 'What you get' benefits
list + 'Choose your plan' header + 'Get Premium' CTA + BrandingFooter
with the designer's layout: hero header (with subtitle from
paywall.themeJsonb['headline_subtitle']), plan cards, 'Continue' CTA,
PaywallLegalFooter. The other 5 state paths are unchanged.
5. Strings — adds paycraft_most_popular_badge, paycraft_continue_cta,
paycraft_legal_{privacy,terms,restore}.
Builds clean: assembleDemoDebug ok against reels-downloader composite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two regressions from the v2.0.5 designer-UI refactor + an existing dead-end: 1. BrandingFooter (Powered by PayCraft by MobileByteSensei) was inadvertently dropped from the Free state when the legacy benefits list was removed. Add it back below PaywallLegalFooter — renders on every paywall state per the dashboard's attribution toggle (auto-hides for Pro+ tier). 2. The legal-footer RESTORE link silently no-op'd for not-logged-in users: onRestoreClick dispatched RestoreSubscription(email = userEmail.orEmpty()) which the ViewModel bails on (if email.isBlank() return). Fix: when state.userEmail is null/blank, dispatch the new OpenRestoreSheet action instead, which shows the SDK's PayCraftRestore modal bottom sheet (email input + OAuth). 3. New OpenRestoreSheet + CloseRestoreSheet actions + isRestoreSheetVisible state field. PayCraftPaywallContent renders PayCraftRestore(visible, …) at the same dialog layer as ProviderBottomSheet. Verified on Android (CPH2423) with cmp-paycraft local composite: - Branding 'Powered by PayCraft by MobileByteSensei' visible at foot - PRIVACY/TERMS open URLs via PayCraftPlatform.openUrl as before - RESTORE now opens the SDK restore sheet instead of doing nothing Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates BrandingFooter from 'Powered by PayCraft by MobileByteSensei' to
the dashboard's reference wordmark style:
Powered by PAYCRAFT
Uses AnnotatedString to render 'Powered by' in regular weight at the
default muted onSurfaceVariant tint, then 'PAYCRAFT' in ExtraBold uppercase
with 1.5sp letter-spacing as the brand wordmark. Matches the Premium-state
mockup in the dashboard's Paywall designer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-only consumer init Architectural fix: consumer apps should never have to call PayCraftPlatform.init(applicationContext) from their androidMain. Context handoff is now done inside the SDK via an androidx-startup Initializer: - New PayCraftInitializer (androidMain) — calls PayCraftPlatform.init at app start, before Application.onCreate runs. - New AndroidManifest.xml (androidMain) — registers the Initializer via the androidx.startup InitializationProvider merge node. - Added androidx.startup:startup-runtime:1.2.0 dependency. Consumer impact: drop PayCraftPlatform.init(applicationContext) from your Application.onCreate. Call PayCraft.initialize(apiKey) from commonMain only — the Android Context handoff is handled by the SDK. Opt-out (tools:node='remove') documented in PayCraftInitializer KDoc. Also reverts the v2.0.7-WIP InitOptions.showTestProducts client flag + ProductDto.isTestOnly filter (test-only product visibility is being moved to a dashboard-driven server-side filter using a test_devices registry — see follow-up release). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stry
Production-grade visibility control for internal QA products. The
dashboard marks a product is_test_only=true; the server only includes
it in /config responses when the requesting device's fingerprint is
in the tenant's test_devices allow-list. Signed prod APKs whose
fingerprint isn't registered physically cannot see the product.
SDK (PayCraft 2.0.9):
- expect/actual DeviceFingerprint (Android SHA-256(SSAID+package),
iOS FNV1a(identifierForVendor+bundle), JVM persisted UUID, macOS
NSUserDefaults UUID, wasmJs localStorage UUID).
- PayCraft.deviceId lazy property + logged at initialize().
- /config GET now carries ?device_id=<fingerprint>.
Server (migration 067):
- tenant_products.is_test_only column.
- test_devices table + tenant-admin RLS.
- RPCs: test_devices_{list,register,revoke,is_registered}.
- /config Edge Function calls is_registered, filters is_test_only
products for unregistered devices.
End-to-end verified:
- registered device (CPH2423) → 5 products incl. Testing Trial
- unregistered device → 4 products
- no device_id → 4 products
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dashboard UI for cmp-paycraft 2.0.9's server-enforced test product
visibility:
- /settings/test-devices: list + register + revoke device fingerprints
for the current tenant. Form validates the 16-hex-char device ID
shape; helper text explains how to grab it from adb logcat / Xcode
console at app start.
- Product editor (new + edit) grows a 'Test devices only' checkbox
alongside the existing 'Active' toggle. Visual treatment + helper
text spell out the server-enforcement guarantee + link to the
Testing Devices page.
- New API routes:
GET /api/test-devices — list rows
POST /api/test-devices — register (validates hex shape)
DELETE /api/test-devices/{id} — revoke
All call SECURITY DEFINER RPCs from migration 067; both register +
revoke emit audit_log entries.
- Sidebar: new 'Testing devices' link under the Account group, between
API keys and Settings.
- Migration 068 extends tenant_products_upsert to thread the
is_test_only flag through INSERT and UPDATE so the toggle actually
persists. Applied to framework-supabase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Auto-opened by /paycraft-deploy Phase 4 PROMOTE.
Source: origin/development @ 483a5ec
Target: origin/main @ ab2fb8c
Diff: 19 commits
This PR is fast-forward-only — main is kept as an exact replica of development at promote time. No manual edits should land on main.