Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dev/apollo-federation/supergraph.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,10 @@ type FygaroTopupAllowancePayload
Always empty. Refusals are reported as `unavailableReason`, not as errors — the field is decoration on a screen the customer is still filling in, so a read failure must not surface as one. Present for consistency with every other payload type.
"""
errors: [Error!]!
held: CentAmount! @deprecated(reason: "Use `allowance.held`. Present only for flash-mobile v0.6.7.")
holdsExpireAt: Timestamp @deprecated(reason: "Use `allowance.holdsExpireAt`. Present only for flash-mobile v0.6.7.")
limit: CentAmount! @deprecated(reason: "Use `allowance.limit`. Present only for flash-mobile v0.6.7.")
remaining: CentAmount! @deprecated(reason: "Use `allowance.remaining`. Present only for flash-mobile v0.6.7.")

"""Null whenever `allowance` is present, and only then."""
unavailableReason: FygaroTopupAllowanceUnavailableReason
Expand Down
4 changes: 4 additions & 0 deletions src/graphql/public/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,10 @@
Always empty. Refusals are reported as `unavailableReason`, not as errors — the field is decoration on a screen the customer is still filling in, so a read failure must not surface as one. Present for consistency with every other payload type.
"""
errors: [Error!]!
held: CentAmount! @deprecated(reason: "Use `allowance.held`. Present only for flash-mobile v0.6.7.")

Check notice on line 957 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'held' was added to object type 'FygaroTopupAllowancePayload'

Field 'held' was added to object type 'FygaroTopupAllowancePayload'

Check notice on line 957 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'FygaroTopupAllowancePayload.held' is deprecated

Field 'FygaroTopupAllowancePayload.held' is deprecated

Check notice on line 957 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Directive 'deprecated' was added to field 'FygaroTopupAllowancePayload.held'

Directive 'deprecated' was added to field 'held'

Check notice on line 957 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Argument 'reason' was added to '@deprecated'

Argument 'reason' was added to '@deprecated'
holdsExpireAt: Timestamp @deprecated(reason: "Use `allowance.holdsExpireAt`. Present only for flash-mobile v0.6.7.")

Check notice on line 958 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'holdsExpireAt' was added to object type 'FygaroTopupAllowancePayload'

Field 'holdsExpireAt' was added to object type 'FygaroTopupAllowancePayload'

Check notice on line 958 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'FygaroTopupAllowancePayload.holdsExpireAt' is deprecated

Field 'FygaroTopupAllowancePayload.holdsExpireAt' is deprecated

Check notice on line 958 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Directive 'deprecated' was added to field 'FygaroTopupAllowancePayload.holdsExpireAt'

Directive 'deprecated' was added to field 'holdsExpireAt'

Check notice on line 958 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Argument 'reason' was added to '@deprecated'

Argument 'reason' was added to '@deprecated'
limit: CentAmount! @deprecated(reason: "Use `allowance.limit`. Present only for flash-mobile v0.6.7.")

Check notice on line 959 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'limit' was added to object type 'FygaroTopupAllowancePayload'

Field 'limit' was added to object type 'FygaroTopupAllowancePayload'

Check notice on line 959 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'FygaroTopupAllowancePayload.limit' is deprecated

Field 'FygaroTopupAllowancePayload.limit' is deprecated

Check notice on line 959 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Directive 'deprecated' was added to field 'FygaroTopupAllowancePayload.limit'

Directive 'deprecated' was added to field 'limit'

Check notice on line 959 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Argument 'reason' was added to '@deprecated'

Argument 'reason' was added to '@deprecated'
remaining: CentAmount! @deprecated(reason: "Use `allowance.remaining`. Present only for flash-mobile v0.6.7.")

Check notice on line 960 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'remaining' was added to object type 'FygaroTopupAllowancePayload'

Field 'remaining' was added to object type 'FygaroTopupAllowancePayload'

Check notice on line 960 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Field 'FygaroTopupAllowancePayload.remaining' is deprecated

Field 'FygaroTopupAllowancePayload.remaining' is deprecated

Check notice on line 960 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Directive 'deprecated' was added to field 'FygaroTopupAllowancePayload.remaining'

Directive 'deprecated' was added to field 'remaining'

Check notice on line 960 in src/graphql/public/schema.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector

Argument 'reason' was added to '@deprecated'

Argument 'reason' was added to '@deprecated'

"""Null whenever `allowance` is present, and only then."""
unavailableReason: FygaroTopupAllowanceUnavailableReason
Expand Down
95 changes: 95 additions & 0 deletions src/graphql/public/types/object/fygaro-topup-allowance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,100 @@ export const FygaroTopupAllowanceUnavailableReasonEnum = GT.Enum({
values: UNAVAILABLE_REASON_ENUM_VALUES,
})

/**
* COMPATIBILITY SHIM for flash-mobile v0.6.7. Delete once that build has aged
* out of the stores.
*
* v0.6.7 queries the payload as if it WERE the allowance:
*
* fygaroTopupAllowance { limit held remaining holdsExpireAt }
*
* Those are fields of `FygaroTopupAllowance`, not of this payload, so the
* server answers 400 GRAPHQL_VALIDATION_FAILED — and the app's global Apollo
* handler turns any 4xx into a toast reading "StatusCode: 400 / Error code:
* undefined" on the card top-up amount screen, every time it is opened. The
* app shipped that way because codegen ran against a checked-in
* `public-schema.graphql` that still described the pre-payload shape: the
* generator validated happily against a schema the server had already moved
* past.
*
* Aliasing the four fields here fixes every INSTALLED v0.6.7 without a store
* release, which is the whole point — those users cannot be reached any other
* way.
*
* `limit`/`held`/`remaining` are NON-NULL on purpose, and that is the subtle
* part. When the allowance is unavailable (ERPNext unreadable, rate limited,
* checkout disabled) `allowance` is null and these resolve to null, which is a
* non-null violation. GraphQL propagates that up and nulls `data`, so the app
* sees no allowance and falls back to rendering the flat per-level cap — which
* is EXACTLY what it does today when the whole query 400s, and exactly what
* the pre-payload schema did by returning a null allowance.
*
* Making them nullable instead would be worse, not safer: the app builds its
* allowance object from any truthy payload (`use-card-topup-allowance.ts`), so
* nulls would render as a zeroed allowance and tell the customer they have
* nothing left to spend — a false refusal in place of a graceful fallback.
*
* The error that propagation produces is a GRAPHQL error on a 200, not a
* network error. The app logs those and never toasts them (`client.tsx`:
* "only network error are managed globally"), so the shim is silent in exactly
* the case it needs to be.
*
* NEVER select these fields alongside `allowance`/`unavailableReason` in the
* same document. When the allowance is unavailable, the non-null violation on
* a flat field nulls the ENTIRE payload — `unavailableReason` included — and
* that field's whole purpose is separating "hide the option" from "retry".
* A transitional query that selects both shapes is precisely the likeliest
* next query someone writes while migrating off these, and it silently
* reinstates the invite-then-refuse loop the payload type exists to end.
* v0.6.8+ must query ONLY the payload shape; these four are for the frozen
* v0.6.7 document and nothing else. Pinned by the mixed-selection test in
* fygaro-allowance-legacy-fields.spec.ts.
*/
type AllowancePayloadSource = {
allowance?: {
limit?: number
held?: number
remaining?: number
// At runtime this is what the root resolver passes through from the app
// layer: a `Date` when something is held, `undefined` when nothing is
// (src/app/fygaro/topup-allowance.ts `holdsExpireAt?: Date`). `number` is
// kept in the union because `Timestamp.serialize` accepts already-Unix
// values too — but do NOT compare this field numerically: a Date coerces
// via `valueOf` to MILLISECONDS, off by 1000x from Unix seconds.
holdsExpireAt?: Date | number | null
} | null
}

const LEGACY_UNWRAPPED_FIELDS = {
limit: {
type: GT.NonNull(CentAmount),
deprecationReason: "Use `allowance.limit`. Present only for flash-mobile v0.6.7.",
resolve: (source: AllowancePayloadSource) => source.allowance?.limit,
},
held: {
type: GT.NonNull(CentAmount),
deprecationReason: "Use `allowance.held`. Present only for flash-mobile v0.6.7.",
resolve: (source: AllowancePayloadSource) => source.allowance?.held,
},
remaining: {
type: GT.NonNull(CentAmount),
deprecationReason: "Use `allowance.remaining`. Present only for flash-mobile v0.6.7.",
resolve: (source: AllowancePayloadSource) => source.allowance?.remaining,
},
// Nullable in the inner type and nullable here: it is legitimately absent
// when nothing is held, and v0.6.7 already guards it
// (`raw.holdsExpireAt ? new Date(...) : undefined`). Making it non-null would
// null the whole payload for every account with no open checkout — the
// common case — and hide a perfectly good allowance behind the flat cap.
holdsExpireAt: {
type: Timestamp,
deprecationReason:
"Use `allowance.holdsExpireAt`. Present only for flash-mobile v0.6.7.",
resolve: (source: AllowancePayloadSource) => source.allowance?.holdsExpireAt ?? null,
},
}

export const FygaroTopupAllowancePayload = GT.Object({
name: "FygaroTopupAllowancePayload",
description:
Expand Down Expand Up @@ -196,6 +290,7 @@ export const FygaroTopupAllowancePayload = GT.Object({
type: FygaroTopupAllowanceUnavailableReasonEnum,
description: "Null whenever `allowance` is present, and only then.",
},
...LEGACY_UNWRAPPED_FIELDS,
}),
})

Expand Down
Loading
Loading