fix(distribution,audit): propagate ErrSubscriptionNoPayment to handler + P0.12 closure date + E2E regression TODO
Self-review of the v1.0.6.2 hotfix surfaced that
distribution.checkEligibility silently swallowed
subscription.ErrSubscriptionNoPayment as "ineligible, no extra info",
so a user with a fantôme subscription trying to submit a distribution
got "Distribution requires Creator or Premium plan" — misleading, the
user has a plan but no payment. checkEligibility now propagates the
error so the handler can surface "Your subscription is not linked to
a payment. Complete payment to enable distribution."
Security is unchanged — the gate still refuses. This is a UX clarity
fix for honest-path users who landed in the fantôme state via a
broken payment flow.
Also:
- Closure timestamp added to axis-1 P0.12 ("closed 2026-04-17 in
v1.0.6.2 (commit 9a8d2a4e7)") so future readers know the finding's
lifecycle without re-grepping the CHANGELOG.
- Item G in v107-plan.md gains an explicit E2E Playwright @critical
acceptance — the shell probe + Go unit tests validate the fix
today but don't run on every commit, so a refactor of Subscribe or
checkEligibility could silently re-open the bypass. The E2E test
makes regression coverage automatic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
68a0d390e2
commit
26cb523334
4 changed files with 25 additions and 6 deletions
|
|
@ -423,8 +423,8 @@ can_sell_on_marketplace` check on paid plans.
|
|||
a mandatory `pending_payment` state + webhook-driven activation
|
||||
(item G in the v1.0.7 plan).
|
||||
|
||||
**Criticity** — **P0, closed in v1.0.6.2.** Item G in v1.0.7 hardens
|
||||
the creation path end-to-end.
|
||||
**Criticity** — **P0, closed 2026-04-17 in v1.0.6.2 (commit
|
||||
d31f5733d).** Item G in v1.0.7 hardens the creation path end-to-end.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -255,6 +255,13 @@ Acceptance:
|
|||
- Subscribe with provider misconfigured → 503, no row created.
|
||||
- Migration of v1.0.6.2 voided rows — check `voided_subscriptions_20260417`
|
||||
entries stay readable and not re-pickable by the new flow.
|
||||
- **E2E Playwright @critical**: `POST /subscribe` followed by
|
||||
`POST /distribution/submit` asserts 403 with the "complete
|
||||
payment" message until the payment webhook fires. Today's
|
||||
regression coverage is the shell probe + Go unit tests —
|
||||
neither runs on every commit. Wiring a Playwright @critical
|
||||
test turns the probe into a gate so a refactor of `Subscribe`
|
||||
or `checkEligibility` cannot silently re-open the bypass.
|
||||
|
||||
Independent of A/B/C/D/E/F. Can land at any point after D.
|
||||
|
||||
|
|
|
|||
|
|
@ -244,12 +244,18 @@ func (s *Service) checkEligibility(ctx context.Context, userID uuid.UUID) (bool,
|
|||
|
||||
sub, err := s.subscriptionService.GetUserSubscription(ctx, userID)
|
||||
if err != nil {
|
||||
// v1.0.6.2: ErrSubscriptionNoPayment means a row exists in
|
||||
// active/trialing but has no effective payment linkage. Treat as
|
||||
// ineligible, same blast radius as no subscription at all.
|
||||
if errors.Is(err, subscription.ErrNoActiveSubscription) || errors.Is(err, subscription.ErrSubscriptionNoPayment) {
|
||||
// No subscription row: ineligible with no extra signal — handler
|
||||
// surfaces the standard "Creator or Premium plan required" message.
|
||||
if errors.Is(err, subscription.ErrNoActiveSubscription) {
|
||||
return false, nil
|
||||
}
|
||||
// v1.0.6.2: propagate ErrSubscriptionNoPayment so the handler can
|
||||
// surface a distinct message ("complete payment") instead of the
|
||||
// generic "upgrade your plan" — the user has a plan, just no
|
||||
// effective payment linkage.
|
||||
if errors.Is(err, subscription.ErrSubscriptionNoPayment) {
|
||||
return false, err
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"veza-backend-api/internal/core/distribution"
|
||||
"veza-backend-api/internal/core/subscription"
|
||||
apperrors "veza-backend-api/internal/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -46,6 +47,11 @@ func (h *DistributionHandler) Submit(c *gin.Context) {
|
|||
switch {
|
||||
case errors.Is(err, distribution.ErrNotEligible):
|
||||
RespondWithAppError(c, apperrors.NewForbiddenError("Distribution requires Creator or Premium plan"))
|
||||
case errors.Is(err, subscription.ErrSubscriptionNoPayment):
|
||||
// v1.0.6.2: the user has a plan but no effective payment
|
||||
// linkage. Distinct from ErrNotEligible so the UX can tell
|
||||
// them to complete payment rather than upgrade.
|
||||
RespondWithAppError(c, apperrors.NewForbiddenError("Your subscription is not linked to a payment. Complete payment to enable distribution."))
|
||||
case errors.Is(err, distribution.ErrTrackNotPublic):
|
||||
RespondWithAppError(c, apperrors.NewValidationError("Track must be public and belong to you"))
|
||||
case errors.Is(err, distribution.ErrAlreadyDistributed):
|
||||
|
|
|
|||
Loading…
Reference in a new issue