feat: sync rcat data (#13628)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* RevenueCat support: public webhook endpoint, webhook handler/service,
nightly reconciliation and per-user sync; subscriptions now expose
provider and iapStore; new user-facing error for App Store/Play-managed
subscriptions.
* **Chores**
* Multi-provider subscription schema (Provider, IapStore); Stripe
credentials moved into payment.stripe (top-level apiKey/webhookKey
deprecated); new payment.revenuecat config and defaults added.
* **Tests**
  * Comprehensive RevenueCat integration test suite and snapshots.
* **Documentation**
  * Admin config descriptions updated with deprecation guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-09-23 16:52:23 +08:00
committed by GitHub
parent 75a6c79b2c
commit 762b702e46
31 changed files with 2059 additions and 33 deletions
@@ -39,15 +39,20 @@ export class StripeFactory {
}
setup() {
// TODO@(@forehalo): use per-requests api key injection
this.#stripe = new Stripe(
this.config.payment.apiKey ||
// NOTE(@forehalo):
// we always fake a key if not set because `new Stripe` will complain if it's empty string
// this will make code cleaner than providing `Stripe` instance as optional one.
'stripe-api-key',
this.config.payment.stripe
);
// Prefer new keys under payment.stripe.*, fallback to legacy root keys for backward compatibility
const {
apiKey: nestedApiKey,
webhookKey: _,
...config
} = this.config.payment.stripe || {};
// NOTE:
// we always fake a key if not set because `new Stripe` will complain if it's empty string
// this will make code cleaner than providing `Stripe` instance as optional one.
const apiKey =
nestedApiKey || this.config.payment.apiKey || 'stripe-api-key';
// TODO@(@darkskygit): use per-requests api key injection
this.#stripe = new Stripe(apiKey, config);
if (this.config.payment.enabled) {
this.server.enableFeature(ServerFeature.Payment);
} else {