reveclicat 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/README.md +3 -3
- package/dist/cli.js +91 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -8
- package/dist/index.js +94 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scenarios/play-trial-converts.yaml +16 -0
- package/scenarios/stripe-billing-issue-recovers.yaml +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to this project are documented here. Format based on [Keep a
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.3.0] — 2026-08-29
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Stripe in the generator: `--store stripe` / `subscriber.store: stripe` — constant `si_…` transaction id, `prod_…` product id default, `renewal_number` on every event, `BILLING_ISSUE` with the period pre-extended and `grace_period_expiration_at_ms: null`, recovery `RENEWAL` that does not re-extend, Stripe money fields (commission 0, takehome 1), `country_code: null`. `UNCANCELLATION` and `TEST` are rejected for Stripe with a store-specific message (official compatibility table). All rules derived from a real captured lifecycle (`test/fixtures/events/real/*.stripe.json`). New example `scenarios/stripe-billing-issue-recovers.yaml`. (T-080–T-083)
|
|
11
|
+
- State machine: `legalEvents(state, store)` / `transition(..., { store })` — legality now has a store dimension. (T-082)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `RENEWAL` is now legal from `cancelled_pending_expiration` (recovery after a `BILLING_ERROR` cancellation, as observed in real Stripe traffic and described for App Store in the official flows). (T-082)
|
|
15
|
+
|
|
16
|
+
## [0.2.0] — 2026-08-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Google Play in the generator: `--store play_store` / `subscriber.store: play_store` emit `store: PLAY_STORE`, `GPA.dddd-dddd-dddd-ddddd` order ids with `..N` renewal suffixes (Google's `Purchase.getOrderId` convention), a new order on resubscription, and the `<subscription_id>:<base_plan_id>` default product id. New example `scenarios/play-trial-converts.yaml` (`rcc init` now copies 7 scenarios). (T-071, T-072)
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `product_id` no longer has a default in the scenario schema / `--product`; the per-store default is applied by the generator. Explicit values are unchanged. (T-071)
|
|
23
|
+
|
|
7
24
|
## [0.1.1] — 2026-08-29
|
|
8
25
|
|
|
9
26
|
### Fixed
|
|
@@ -41,6 +58,8 @@ First release. Unofficial project — not affiliated with RevenueCat, Inc.
|
|
|
41
58
|
- Consistent error output: every error prints `✖ message` + `→ hint`; usage errors (unknown command/option, missing argument) exit with code 2, other failures with 1; `RCC_DEBUG=1` shows stack traces; `NO_COLOR` honoured. (T-052)
|
|
42
59
|
- Programmatic API (`reveclicat` package): schemas, `Subscriber`, `runScenario`, `loadScenario`, `VirtualClock`, `createRng`.
|
|
43
60
|
|
|
44
|
-
[Unreleased]: https://github.com/RadW2020/ReveCliCat/compare/v0.
|
|
61
|
+
[Unreleased]: https://github.com/RadW2020/ReveCliCat/compare/v0.3.0...HEAD
|
|
62
|
+
[0.3.0]: https://github.com/RadW2020/ReveCliCat/compare/v0.2.0...v0.3.0
|
|
63
|
+
[0.2.0]: https://github.com/RadW2020/ReveCliCat/compare/v0.1.1...v0.2.0
|
|
45
64
|
[0.1.1]: https://github.com/RadW2020/ReveCliCat/compare/v0.1.0...v0.1.1
|
|
46
65
|
[0.1.0]: https://github.com/RadW2020/ReveCliCat/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ subscriber: # all optional
|
|
|
94
94
|
period: P1M # ISO-8601 duration
|
|
95
95
|
trial: P1W # omit → no trial
|
|
96
96
|
grace_period: P16D # billing-retry window after BILLING_ISSUE
|
|
97
|
-
store: app_store #
|
|
97
|
+
store: app_store # app_store | play_store | stripe (store-shaped ids, defaults and legal events)
|
|
98
98
|
environment: SANDBOX # SANDBOX | PRODUCTION
|
|
99
99
|
steps:
|
|
100
100
|
- event: INITIAL_PURCHASE # starts the trial (period_type: TRIAL, price 0)
|
|
@@ -115,7 +115,7 @@ expect:
|
|
|
115
115
|
|
|
116
116
|
Rules: a step is exactly one of `event` or `advance`; unknown keys are errors; validation errors point at `file:line:column`. Illegal transitions stop the run with the step number and the list of legal events. `EXPIRATION` is only allowed once the virtual clock has reached `expiration_at_ms` (or the end of the grace period) — the error tells you exactly how much to `advance`.
|
|
117
117
|
|
|
118
|
-
Shipped examples (`rcc init` copies them): `trial-converts`, `trial-churns`, `billing-issue-recovers`, `billing-issue-churns`, `cancel-then-uncancel`, `happy-year` (12 renewals).
|
|
118
|
+
Shipped examples (`rcc init` copies them): `trial-converts`, `trial-churns`, `billing-issue-recovers`, `billing-issue-churns`, `cancel-then-uncancel`, `happy-year` (12 renewals), `play-trial-converts` (Google Play ids), `stripe-billing-issue-recovers` (Stripe dunning flow).
|
|
119
119
|
|
|
120
120
|
## CI
|
|
121
121
|
|
|
@@ -174,7 +174,7 @@ none ──INITIAL_PURCHASE──▶ trial ──RENEWAL (conversion)──▶ a
|
|
|
174
174
|
## Fidelity & scope
|
|
175
175
|
|
|
176
176
|
- Schemas, enums and inclusion rules come from the official docs (fetched 2026-08-29) and the official sample payloads are used as test fixtures. The `TEST` event has no published sample, so its schema is marked *provisional* — a captured real one is very welcome (see `docs/BACKLOG.md`, T-004).
|
|
177
|
-
-
|
|
177
|
+
- The generator models **App Store** (`--store app_store`, 16-digit transaction ids, original kept across resubscriptions), **Google Play** (`--store play_store`, `GPA.…` order ids with `..N` renewal suffixes, `<subscription_id>:<base_plan_id>` product ids, new order on resubscription) and **Stripe** (`--store stripe`, one `si_…` id for the whole subscription, `prod_…` product ids, `renewal_number`, billing issues with the period pre-extended and no grace field, no `UNCANCELLATION`/`TEST` — all taken from real captured events). Amazon, Roku and RevenueCat Billing, a built-in tunnel, a web UI and hosted mode are intentionally out of scope (see the Icebox in `docs/BACKLOG.md`). Receivers accept events from every store.
|
|
178
178
|
- Programmatic use: `import { runScenario, Subscriber, WebhookEnvelopeSchema } from "reveclicat"`.
|
|
179
179
|
|
|
180
180
|
## How this was built
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Command } from "commander";
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "reveclicat",
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.3.0",
|
|
13
13
|
description: "Unofficial CLI to simulate RevenueCat subscription lifecycles and test webhooks locally and in CI. Not affiliated with RevenueCat, Inc.",
|
|
14
14
|
type: "module",
|
|
15
15
|
license: "MIT",
|
|
@@ -165,8 +165,17 @@ var CANCEL_REASONS = [
|
|
|
165
165
|
"UNKNOWN"
|
|
166
166
|
];
|
|
167
167
|
var EXPIRATION_REASONS = [...CANCEL_REASONS, "SUBSCRIPTION_PAUSED"];
|
|
168
|
-
var CLI_STORES = ["app_store"];
|
|
169
|
-
var CLI_STORE_TO_STORE = { app_store: "APP_STORE" };
|
|
168
|
+
var CLI_STORES = ["app_store", "play_store", "stripe"];
|
|
169
|
+
var CLI_STORE_TO_STORE = { app_store: "APP_STORE", play_store: "PLAY_STORE", stripe: "STRIPE" };
|
|
170
|
+
var DEFAULT_PRODUCT_ID = {
|
|
171
|
+
app_store: "com.example.premium.monthly",
|
|
172
|
+
play_store: "com.example.premium:monthly",
|
|
173
|
+
stripe: "prod_RccPremiumMonthly"
|
|
174
|
+
};
|
|
175
|
+
var UNSUPPORTED_EVENTS_BY_STORE = {
|
|
176
|
+
stripe: ["UNCANCELLATION", "TEST"]
|
|
177
|
+
};
|
|
178
|
+
var STORE_LABEL = { app_store: "App Store", play_store: "Google Play", stripe: "Stripe" };
|
|
170
179
|
|
|
171
180
|
// src/core/config.ts
|
|
172
181
|
var CONFIG_FILE = "reveclicat.config.json";
|
|
@@ -421,10 +430,11 @@ var IllegalTransitionError = class extends RccError {
|
|
|
421
430
|
state;
|
|
422
431
|
event;
|
|
423
432
|
legal;
|
|
424
|
-
constructor(state, event) {
|
|
425
|
-
const legal = legalEvents(state);
|
|
433
|
+
constructor(state, event, store) {
|
|
434
|
+
const legal = legalEvents(state, store);
|
|
435
|
+
const unsupported = store !== void 0 && (UNSUPPORTED_EVENTS_BY_STORE[store] ?? []).includes(event);
|
|
426
436
|
super(
|
|
427
|
-
`Illegal transition: cannot apply ${event} while the subscription is "${state}". Legal events from "${state}": ${legal.join(", ")}.`,
|
|
437
|
+
unsupported ? `${STORE_LABEL[store]} does not emit ${event} (RevenueCat store compatibility table). Legal events from "${state}" for ${STORE_LABEL[store]}: ${legal.join(", ")}.` : `Illegal transition: cannot apply ${event} while the subscription is "${state}". Legal events from "${state}": ${legal.join(", ")}.`,
|
|
428
438
|
{ hint: "Check the order of the steps in your scenario (e.g. a RENEWAL needs an INITIAL_PURCHASE first)." }
|
|
429
439
|
);
|
|
430
440
|
this.name = "IllegalTransitionError";
|
|
@@ -449,6 +459,9 @@ var TABLE = {
|
|
|
449
459
|
},
|
|
450
460
|
cancelled_pending_expiration: {
|
|
451
461
|
UNCANCELLATION: (ctx) => ctx.resumeState,
|
|
462
|
+
// Real flows: recovery after a BILLING_ERROR cancellation (Stripe capture 2026-08-29); App Store "CANCELLATION
|
|
463
|
+
// followed by a RENEWAL" when cancelling <24 h before a trial ends (docs S4).
|
|
464
|
+
RENEWAL: () => "active",
|
|
452
465
|
EXPIRATION: () => "expired"
|
|
453
466
|
},
|
|
454
467
|
billing_issue: {
|
|
@@ -458,13 +471,17 @@ var TABLE = {
|
|
|
458
471
|
},
|
|
459
472
|
expired: { INITIAL_PURCHASE: () => "active" }
|
|
460
473
|
};
|
|
461
|
-
function legalEvents(state) {
|
|
462
|
-
|
|
474
|
+
function legalEvents(state, store) {
|
|
475
|
+
const excluded = store === void 0 ? [] : UNSUPPORTED_EVENTS_BY_STORE[store] ?? [];
|
|
476
|
+
return [...Object.keys(TABLE[state]), "TEST"].filter((e) => !excluded.includes(e));
|
|
463
477
|
}
|
|
464
478
|
function transition(state, event, ctx) {
|
|
479
|
+
if (ctx.store !== void 0 && (UNSUPPORTED_EVENTS_BY_STORE[ctx.store] ?? []).includes(event)) {
|
|
480
|
+
throw new IllegalTransitionError(state, event, ctx.store);
|
|
481
|
+
}
|
|
465
482
|
if (event === "TEST") return state;
|
|
466
483
|
const rule = TABLE[state][event];
|
|
467
|
-
if (!rule) throw new IllegalTransitionError(state, event);
|
|
484
|
+
if (!rule) throw new IllegalTransitionError(state, event, ctx.store);
|
|
468
485
|
return rule(ctx);
|
|
469
486
|
}
|
|
470
487
|
|
|
@@ -609,13 +626,14 @@ var Subscriber = class {
|
|
|
609
626
|
this.period = asDuration(opts.period);
|
|
610
627
|
this.trial = opts.trial === void 0 ? void 0 : asDuration(opts.trial);
|
|
611
628
|
this.grace = asDuration(opts.gracePeriod ?? "P16D");
|
|
612
|
-
this.
|
|
629
|
+
this.cliStore = opts.store ?? "app_store";
|
|
630
|
+
this.store = CLI_STORE_TO_STORE[this.cliStore];
|
|
613
631
|
this.environment = opts.environment ?? "SANDBOX";
|
|
614
632
|
this.price = opts.price ?? 9.99;
|
|
615
633
|
this.currency = opts.currency ?? "USD";
|
|
616
634
|
this.countryCode = opts.countryCode ?? "US";
|
|
617
635
|
this.entitlementIds = opts.entitlementIds ?? ["premium"];
|
|
618
|
-
this.productId = opts.productId;
|
|
636
|
+
this.productId = opts.productId ?? DEFAULT_PRODUCT_ID[this.cliStore];
|
|
619
637
|
this.appUserId = opts.appUserId === void 0 || opts.appUserId === "auto" ? `$RCAnonymousID:${deps.rng.hex(32)}` : opts.appUserId;
|
|
620
638
|
this.appId = opts.appId ?? `app${deps.rng.hex(12)}`;
|
|
621
639
|
}
|
|
@@ -627,6 +645,11 @@ var Subscriber = class {
|
|
|
627
645
|
trial;
|
|
628
646
|
grace;
|
|
629
647
|
store;
|
|
648
|
+
cliStore;
|
|
649
|
+
/** Play: number of renewals on the current order (drives the `..N` suffix). */
|
|
650
|
+
renewalIndex = 0;
|
|
651
|
+
/** A BILLING_ISSUE is open (until a RENEWAL recovers it or the subscription expires). */
|
|
652
|
+
billingRetry = false;
|
|
630
653
|
environment;
|
|
631
654
|
price;
|
|
632
655
|
currency;
|
|
@@ -651,7 +674,7 @@ var Subscriber = class {
|
|
|
651
674
|
/** Emit an event: check legality, time guards, build + validate payload, commit state. */
|
|
652
675
|
emit(type, overrides = {}) {
|
|
653
676
|
const from = this._state;
|
|
654
|
-
const next = transition(from, type, { hasTrial: this.trial !== void 0, resumeState: this.resumeState });
|
|
677
|
+
const next = transition(from, type, { hasTrial: this.trial !== void 0, resumeState: this.resumeState, store: this.cliStore });
|
|
655
678
|
const now = this.deps.clock.now();
|
|
656
679
|
if (type === "EXPIRATION") {
|
|
657
680
|
const due = Math.max(this.expirationAtMs ?? 0, from === "billing_issue" ? this.gracePeriodExpirationAtMs ?? 0 : 0);
|
|
@@ -673,6 +696,9 @@ var Subscriber = class {
|
|
|
673
696
|
this.expirationAtMs = draft.expirationAtMs;
|
|
674
697
|
this.periodType = draft.periodType;
|
|
675
698
|
this.gracePeriodExpirationAtMs = draft.gracePeriodExpirationAtMs;
|
|
699
|
+
this.renewalIndex = draft.renewalIndex;
|
|
700
|
+
if (type === "BILLING_ISSUE") this.billingRetry = true;
|
|
701
|
+
if (type === "RENEWAL" || type === "EXPIRATION" || type === "INITIAL_PURCHASE") this.billingRetry = false;
|
|
676
702
|
if (type === "CANCELLATION") this.resumeState = from === "trial" ? "trial" : "active";
|
|
677
703
|
this._state = next;
|
|
678
704
|
}
|
|
@@ -681,11 +707,31 @@ var Subscriber = class {
|
|
|
681
707
|
return event;
|
|
682
708
|
}
|
|
683
709
|
/* ----------------------------------------------------------- internals */
|
|
684
|
-
|
|
685
|
-
let s =
|
|
686
|
-
for (let i = 0; i <
|
|
710
|
+
digits(n) {
|
|
711
|
+
let s = "";
|
|
712
|
+
for (let i = 0; i < n; i++) s += String(this.deps.rng.int(10));
|
|
713
|
+
return s;
|
|
714
|
+
}
|
|
715
|
+
alnum(n) {
|
|
716
|
+
const A = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
717
|
+
let s = "";
|
|
718
|
+
for (let i = 0; i < n; i++) s += A[this.deps.rng.int(A.length)];
|
|
687
719
|
return s;
|
|
688
720
|
}
|
|
721
|
+
/** A brand-new order/transaction id in the store's format (specs/F7-google-play.md, specs/F8-stripe.md). */
|
|
722
|
+
newTransactionId() {
|
|
723
|
+
if (this.cliStore === "stripe") return `si_${this.alnum(14)}`;
|
|
724
|
+
if (this.cliStore === "play_store") {
|
|
725
|
+
return `GPA.${this.digits(4)}-${this.digits(4)}-${this.digits(4)}-${this.digits(5)}`;
|
|
726
|
+
}
|
|
727
|
+
return String(1 + this.deps.rng.int(9)) + this.digits(15);
|
|
728
|
+
}
|
|
729
|
+
/** Renewal id: Play appends `..N`; Stripe keeps the subscription item id; App Store issues a fresh transaction id. */
|
|
730
|
+
renewalTransactionId(originalId, index) {
|
|
731
|
+
if (this.cliStore === "stripe" && originalId !== void 0) return originalId;
|
|
732
|
+
if (this.cliStore === "play_store" && originalId !== void 0) return `${originalId}..${index}`;
|
|
733
|
+
return this.newTransactionId();
|
|
734
|
+
}
|
|
689
735
|
draftFor(type, from, now) {
|
|
690
736
|
const d = {
|
|
691
737
|
originalTransactionId: this.originalTransactionId,
|
|
@@ -693,13 +739,16 @@ var Subscriber = class {
|
|
|
693
739
|
purchasedAtMs: this.purchasedAtMs,
|
|
694
740
|
expirationAtMs: this.expirationAtMs,
|
|
695
741
|
periodType: this.periodType,
|
|
696
|
-
gracePeriodExpirationAtMs: this.gracePeriodExpirationAtMs
|
|
742
|
+
gracePeriodExpirationAtMs: this.gracePeriodExpirationAtMs,
|
|
743
|
+
renewalIndex: this.renewalIndex
|
|
697
744
|
};
|
|
698
745
|
switch (type) {
|
|
699
746
|
case "INITIAL_PURCHASE": {
|
|
700
747
|
const startsTrial = from === "none" && this.trial !== void 0;
|
|
701
748
|
d.transactionId = this.newTransactionId();
|
|
702
|
-
d.originalTransactionId
|
|
749
|
+
if (this.cliStore !== "app_store") d.originalTransactionId = d.transactionId;
|
|
750
|
+
else d.originalTransactionId ??= d.transactionId;
|
|
751
|
+
d.renewalIndex = 0;
|
|
703
752
|
d.purchasedAtMs = now;
|
|
704
753
|
d.expirationAtMs = addDuration(now, startsTrial ? this.trial : this.period);
|
|
705
754
|
d.periodType = startsTrial ? "TRIAL" : "NORMAL";
|
|
@@ -707,8 +756,14 @@ var Subscriber = class {
|
|
|
707
756
|
break;
|
|
708
757
|
}
|
|
709
758
|
case "RENEWAL": {
|
|
759
|
+
d.transactionId = this.renewalTransactionId(d.originalTransactionId, d.renewalIndex);
|
|
760
|
+
if (this.cliStore === "stripe" && this.billingRetry) {
|
|
761
|
+
d.periodType = "NORMAL";
|
|
762
|
+
d.gracePeriodExpirationAtMs = null;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
710
765
|
const start = d.expirationAtMs ?? now;
|
|
711
|
-
d.
|
|
766
|
+
d.renewalIndex += 1;
|
|
712
767
|
d.purchasedAtMs = start;
|
|
713
768
|
d.expirationAtMs = addDuration(start, this.period);
|
|
714
769
|
d.periodType = "NORMAL";
|
|
@@ -716,7 +771,13 @@ var Subscriber = class {
|
|
|
716
771
|
break;
|
|
717
772
|
}
|
|
718
773
|
case "BILLING_ISSUE":
|
|
719
|
-
|
|
774
|
+
if (this.cliStore === "stripe") {
|
|
775
|
+
d.expirationAtMs = addDuration(d.expirationAtMs ?? now, this.period);
|
|
776
|
+
d.renewalIndex += 1;
|
|
777
|
+
d.gracePeriodExpirationAtMs = null;
|
|
778
|
+
} else {
|
|
779
|
+
d.gracePeriodExpirationAtMs = addDuration(now, this.grace);
|
|
780
|
+
}
|
|
720
781
|
break;
|
|
721
782
|
case "TEST":
|
|
722
783
|
if (from === "none") {
|
|
@@ -734,6 +795,7 @@ var Subscriber = class {
|
|
|
734
795
|
buildPayload(type, from, now, d) {
|
|
735
796
|
const isPurchase = type === "INITIAL_PURCHASE" || type === "RENEWAL" || type === "TEST";
|
|
736
797
|
const price = isPurchase && d.periodType !== "TRIAL" ? this.price : 0;
|
|
798
|
+
const stripe = this.cliStore === "stripe";
|
|
737
799
|
const payload = {
|
|
738
800
|
type,
|
|
739
801
|
id: this.deps.rng.uuid(),
|
|
@@ -754,16 +816,17 @@ var Subscriber = class {
|
|
|
754
816
|
transaction_id: d.transactionId,
|
|
755
817
|
original_transaction_id: d.originalTransactionId,
|
|
756
818
|
is_family_share: false,
|
|
757
|
-
country_code: this.countryCode,
|
|
819
|
+
country_code: stripe ? null : this.countryCode,
|
|
758
820
|
store: this.store,
|
|
759
821
|
currency: this.currency,
|
|
760
822
|
price,
|
|
761
823
|
price_in_purchased_currency: price,
|
|
762
824
|
tax_percentage: 0,
|
|
763
|
-
commission_percentage: 0.3,
|
|
764
|
-
takehome_percentage: 0.7,
|
|
825
|
+
commission_percentage: stripe ? 0 : 0.3,
|
|
826
|
+
takehome_percentage: stripe ? 1 : 0.7,
|
|
765
827
|
offer_code: null
|
|
766
828
|
};
|
|
829
|
+
if (stripe) payload["renewal_number"] = d.renewalIndex + 1;
|
|
767
830
|
switch (type) {
|
|
768
831
|
case "RENEWAL":
|
|
769
832
|
payload["is_trial_conversion"] = from === "trial";
|
|
@@ -974,8 +1037,8 @@ function parseEnvironment(input) {
|
|
|
974
1037
|
}
|
|
975
1038
|
function parseStore(input) {
|
|
976
1039
|
if (CLI_STORES.includes(input)) return input;
|
|
977
|
-
throw new RccError(`Unsupported --store "${input}".
|
|
978
|
-
hint: "
|
|
1040
|
+
throw new RccError(`Unsupported --store "${input}". The generator supports: ${CLI_STORES.join(", ")}.`, {
|
|
1041
|
+
hint: "Receivers (listen/tail/inbox) accept events from every store; only generation is limited. Others are in the Icebox."
|
|
979
1042
|
});
|
|
980
1043
|
}
|
|
981
1044
|
function parseSeed(input) {
|
|
@@ -999,7 +1062,7 @@ function buildSingleEvent(type, opts) {
|
|
|
999
1062
|
return { api_version: "1.0", event };
|
|
1000
1063
|
}
|
|
1001
1064
|
function registerSend(program, io) {
|
|
1002
|
-
program.command("send").argument("<EVENT_TYPE>", `event to send: ${EVENT_TYPES.join(" | ")}`).description("Send a single, schema-valid RevenueCat webhook event to your endpoint.").option("--to <url>", `target URL (default: ${DEFAULT_TARGET}, or "to" in ${CONFIG_FILE})`).option("--store <store>", `store: ${CLI_STORES.join(" | ")} (default: app_store, or "store" in ${CONFIG_FILE})`).option("--user <app_user_id>", "app_user_id (default: generated $RCAnonymousID)").option("--product <product_id>", "product_id
|
|
1065
|
+
program.command("send").argument("<EVENT_TYPE>", `event to send: ${EVENT_TYPES.join(" | ")}`).description("Send a single, schema-valid RevenueCat webhook event to your endpoint.").option("--to <url>", `target URL (default: ${DEFAULT_TARGET}, or "to" in ${CONFIG_FILE})`).option("--store <store>", `store to simulate: ${CLI_STORES.join(" | ")} (default: app_store, or "store" in ${CONFIG_FILE})`).option("--user <app_user_id>", "app_user_id (default: generated $RCAnonymousID)").option("--product <product_id>", "product_id (default: com.example.premium.monthly, or com.example.premium:monthly for play_store)").option("--auth-header <value>", `value sent as the Authorization header (default: "authHeader" in ${CONFIG_FILE})`).option("--environment <env>", `${ENVIRONMENTS.join(" | ")} (default: SANDBOX, or "environment" in ${CONFIG_FILE})`).option("--set <key=value>", "override a payload field (repeatable, dot paths allowed)", (v, acc) => [...acc ?? [], v]).option("--seed <seed>", "deterministic ids and timestamps").option("--dry-run", "print the payload instead of sending it").addHelpText("after", `
|
|
1003
1066
|
Examples:
|
|
1004
1067
|
$ rcc send INITIAL_PURCHASE
|
|
1005
1068
|
$ rcc send RENEWAL --to http://localhost:8787/webhook --auth-header "Bearer dev"
|
|
@@ -1211,12 +1274,13 @@ var eventType = z3.enum(EVENT_TYPES, {
|
|
|
1211
1274
|
var httpStatus = z3.int({ error: "response_status must be an integer HTTP status (e.g. 200)." }).min(100).max(599);
|
|
1212
1275
|
var SubscriberConfigSchema = z3.strictObject({
|
|
1213
1276
|
app_user_id: z3.string().min(1).default("auto"),
|
|
1214
|
-
|
|
1277
|
+
/** No default here: the Subscriber picks a per-store default (see specs/F7-google-play.md). */
|
|
1278
|
+
product_id: z3.string().min(1).optional(),
|
|
1215
1279
|
period: duration.default("P1M"),
|
|
1216
1280
|
trial: duration.optional(),
|
|
1217
1281
|
grace_period: duration.default("P16D"),
|
|
1218
1282
|
store: z3.enum(CLI_STORES, {
|
|
1219
|
-
error: (iss) => `Unsupported store "${String(iss.input)}".
|
|
1283
|
+
error: (iss) => `Unsupported store "${String(iss.input)}". Supported: ${list(CLI_STORES)}.`
|
|
1220
1284
|
}).default("app_store"),
|
|
1221
1285
|
environment: z3.enum(ENVIRONMENTS, {
|
|
1222
1286
|
error: (iss) => `Invalid environment "${String(iss.input)}". Use one of: ${list(ENVIRONMENTS)}.`
|
|
@@ -1253,7 +1317,6 @@ var ScenarioSchema = z3.strictObject({
|
|
|
1253
1317
|
description: z3.string().optional(),
|
|
1254
1318
|
subscriber: SubscriberConfigSchema.default({
|
|
1255
1319
|
app_user_id: "auto",
|
|
1256
|
-
product_id: "com.example.premium.monthly",
|
|
1257
1320
|
period: "P1M",
|
|
1258
1321
|
grace_period: "P16D",
|
|
1259
1322
|
store: "app_store",
|