Drift on existing items: offer-code and win-back campaigns that differ are named (Apple cannot edit them), in-app event attributes patched on drafts, a removed custom page deep link cleared, config languages without a version localization named, data safety dry run says Play has no read API; appinfo reads /v1/appPriceSchedules/{id}/manualPrices; a new Play language without an icon warns instead of stopping; fake store lists for the new reads
This commit is contained in:
parent
f2b841d735
commit
e381bc2256
11 changed files with 206 additions and 10 deletions
|
|
@ -8,8 +8,8 @@
|
|||
import type { Ctx } from '../lib/ctx';
|
||||
import { confirm, need } from '../lib/ctx';
|
||||
import { done, log, printJson, warn } from '../lib/log';
|
||||
import { type TerritoryPoint, validateWinBack, winBackBody } from '../lib/offers';
|
||||
import { allSubscriptions, applePoints, pickPoint } from '../lib/subscriptions';
|
||||
import { type TerritoryPoint, validateWinBack, type WinBackDef, winBackBody } from '../lib/offers';
|
||||
import { allSubscriptions, APPLE_OFFER_MODE, applePoints, pickPoint } from '../lib/subscriptions';
|
||||
import { textTable } from '../lib/table';
|
||||
import { pages } from '../prices';
|
||||
import { ascClient, type J } from './asc';
|
||||
|
|
@ -17,6 +17,17 @@ import { describeReq, execute, type Req } from './jsonapi';
|
|||
|
||||
const TAG = 'win-back';
|
||||
|
||||
/** Differences between an existing win-back offer and the config (mode, price, dates). No PATCH exists for them. `usaPrice` = the offer's USA customer price (undefined: not read, not compared). Pure. */
|
||||
export function winBackDiff(have: { offerMode?: string; startDate?: string | null; endDate?: string | null }, usaPrice: number | undefined, want: WinBackDef): string[] {
|
||||
const out: string[] = [];
|
||||
const mode = APPLE_OFFER_MODE[want.type];
|
||||
if (have.offerMode && have.offerMode !== mode) out.push(`mode ${have.offerMode} -> ${mode}`);
|
||||
if (want.type !== 'free' && usaPrice !== undefined && want.usd !== undefined && Math.abs(usaPrice - want.usd) > 0.005) out.push(`USA price ${usaPrice} -> ${want.usd}`);
|
||||
if ((have.startDate ?? '').slice(0, 10) !== want.start) out.push(`start ${have.startDate ?? '(none)'} -> ${want.start}`);
|
||||
if ((have.endDate ?? '').slice(0, 10) !== (want.end ?? '')) out.push(`end ${have.endDate ?? '(none)'} -> ${want.end ?? '(none)'}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function run(ctx: Ctx): Promise<void> {
|
||||
const { config, root } = ctx.loaded;
|
||||
const a = ctx.args;
|
||||
|
|
@ -39,6 +50,21 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (!remote) { skipped.push({ id: s.id, why: 'not on App Store Connect (storebound apple subscriptions)' }); continue; }
|
||||
const existing = await c.all(`/v1/subscriptions/${remote.id}/winBackOffers?limit=200`);
|
||||
for (const o of existing) rows.push([s.id, o.attributes.offerId, o.attributes.offerMode, o.attributes.startDate ?? '', o.attributes.endDate ?? '', o.attributes.priority ?? '']);
|
||||
for (const o of s.winBack!) {
|
||||
const x = existing.find((e: J) => e.attributes.offerId === o.id);
|
||||
if (!x) continue;
|
||||
let usaPrice: number | undefined;
|
||||
if (o.type !== 'free') {
|
||||
const pr = await pages(c, `/v1/winBackOffers/${x.id}/prices?include=territory,subscriptionPricePoint&limit=200`);
|
||||
const usa = pr.data.find((r: J) => r.relationships?.territory?.data?.id === 'USA');
|
||||
const pid = usa?.relationships?.subscriptionPricePoint?.data?.id;
|
||||
const inc = pid ? pr.included.find((i: J) => i.id === pid) : undefined;
|
||||
const cp = Number(inc?.attributes?.customerPrice);
|
||||
if (Number.isFinite(cp)) usaPrice = cp;
|
||||
}
|
||||
const diffs = winBackDiff(x.attributes, usaPrice, o);
|
||||
if (diffs.length) warn(TAG, `${s.id}: win-back "${o.id}" differs from the config: ${diffs.join('; ')} (Apple does not allow editing: create a new offer with another id)`);
|
||||
}
|
||||
const missing = s.winBack!.filter(o => !existing.some((x: J) => x.attributes.offerId === o.id));
|
||||
if (!missing.length) continue;
|
||||
if (remote.attributes.state !== 'APPROVED') { skipped.push({ id: s.id, why: `subscription is ${remote.attributes.state}: win-back offers need an approved subscription` }); continue; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue