fix(security): gate guest self-confirmed orders behind PENDING_REVIEW (#18) #30
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/guest-payment-pending-review"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Issue #18:
POST /api/guest-orders/{token}/payis public (no JWT) andconfirmGuestPaymentsimply transitionsPENDING_PAYMENT -> PROCESSINGwithout verifying any Swish payment was received. A guest can mark an order paid without paying, and BilHej eats the PostNord cost.Approach
Interim guard (Tier 1 Swish Commerce API integration is a separate, larger effort). Self-confirmed guest orders now enter
PENDING_REVIEWinstead ofPROCESSING, gating fulfillment on manual admin confirmation.Changes
PENDING_REVIEW("pending_review")PENDING_REVIEWinstead ofPROCESSING; do NOT callnotifyOrderProcessingPENDING_REVIEW -> PROCESSING, CANCELLED, FAILED;canRegisterShipmentreturns falsePENDING_REVIEW -> PROCESSING, callnotifyOrderProcessingto trigger fulfillmentPENDING_REVIEW+ no notification after guest self-confirmThe authenticated
confirmPaymentpath is unchanged (acknowledged Phase 0 honor-system with user traceability).Test plan
./gradlew :backend:test— BUILD SUCCESSFULnpx vitest run src/__tests__/GuestOrderPage.spec.ts— 12/12 passCloses #18
POST /api/guest-orders/{token}/pay is public (no JWT) and confirmGuestPayment simply transitions PENDING_PAYMENT -> PROCESSING without verifying any Swish payment was received. A guest can mark an order paid without paying, and BilHej eats the PostNord cost for a free letter. Interim fix (Tier 1 Swish Commerce API integration is a separate, larger effort): self-confirmed guest orders now enter PENDING_REVIEW instead of PROCESSING, gating fulfillment on manual admin confirmation. Changes: - OrderStatus: add PENDING_REVIEW("pending_review") - OrderService.confirmGuestPayment: set PENDING_REVIEW instead of PROCESSING; do NOT call notifyOrderProcessing (no fulfillment yet) - AdminOrderStatusRules: allow PENDING_REVIEW -> PROCESSING, CANCELLED, FAILED; canRegisterShipment returns false for PENDING_REVIEW - AdminOrderWorkflowService: when admin advances PENDING_REVIEW -> PROCESSING, call notifyOrderProcessing to trigger fulfillment - GuestOrderPage.vue: status label 'Betalning mottagen, under granskning' for pending_review - OrderServiceTest: assert PENDING_REVIEW status + no notification after guest self-confirm - AdminOrderStatusRulesTest: 3 new tests for PENDING_REVIEW transitions (allowed targets, valid PROCESSING transition, rejected SENT transition) The authenticated confirmPayment path is unchanged (acknowledged Phase 0 honor-system with user traceability). Closes #18Verdict: Approve with minor test-coverage suggestions.
Clean, minimal guard for #18. The status-machine change is correct: guest self-confirm lands in
PENDING_REVIEW(no auto-fulfillment trigger), admin workflow only re-firesnotifyOrderProcessingon the explicitPENDING_REVIEW -> PROCESSINGadvance, and the newAdminOrderStatusRulesadditions correctly seal off directPENDING_REVIEW -> SENTandcanRegisterShipment(so a self-confirmed order cannot slip straight to PostNord). Tests forOrderServiceandAdminOrderStatusRulescover the core mechanic; backward-compatible with existingPROCESSINGorders.Two test gaps and one readability nit below; none are blocking.
Critical
None.
Warnings
None. No security regressions; the autonomous notify was correctly removed from
confirmGuestPayment.Suggestions
Backend: cover the new notification trigger.
AdminOrderWorkflowService.updateOrderStatusgains aPENDING_REVIEW -> PROCESSING -> notifyOrderProcessingbranch, butAdminOrderWorkflowServiceTestwas not updated. This notification is the load-bearing human-loop confirmation for the security fix — without a guard test, the branch can silently regress. Suggested test:shouldNotifyProcessingWhenAdminAdvancesFromPendingReview(mirror the existingnotifyOrderFailedtest).Frontend: cover the new
pending_reviewlabel.GuestOrderPage.spec.tsasserts labels forpaid/sent/delivered/failed/cancelledbut skips the newpending_reviewcase (currently renders'Betalning mottagen, under granskning'). Tests still pass due to the switchdefault, but the new label is uncovered. Addit('shows "Betalning mottagen, under granskning" for pending_review status').Add a one-line comment above the new branch in
AdminOrderWorkflowService.updateOrderStatusnoting why this transition is special (confirmGuestPayment intentionally skips notify; this is the admin-vouched re-fire). Contrast withFAILED -> notifyOrderFailedabove, which doesn't trigger notifyOrderProcessing on theFAILED -> PROCESSINGrevert.Looks Good
OrderStatusenum insertion point betweenPAIDandPROCESSINGis harmless; every switch has adefault.confirmGuestPaymentcorrectly drops the autonomousnotifyOrderProcessingcall; customer now sees 'under granskning' instead of 'Behandlas'.AdminOrderStatusRuleschanges correctly seal offPENDING_REVIEW -> SENTand disablecanRegisterShipment.previousStatus == PENDING_REVIEWguard is precise; it does not fire forFAILED -> PROCESSINGrecoveries (matching pre-existing behavior).confirmGuestPaymentclearly explains the rationale.💡 Test gap + readability nit. This branch is the load-bearing fix for #18 (re-fires the 'Behandlas' notification only when an admin vouches for the order), and it is not covered by
AdminOrderWorkflowServiceTest— thenotifyOrderFailedbranch above is tested, this one isn't. Consider addingshouldNotifyProcessingWhenAdminAdvancesFromPendingReview.Also worth a one-line comment clarifying why
PROCESSINGfromPENDING_REVIEWnotifications wherePROCESSINGfromFAILEDdoes not (becauseconfirmGuestPaymentdeliberately skipped the notify).💡 Frontend test gap.
GuestOrderPage.spec.tsasserts labels forpaid/sent/delivered/failed/cancelledbut not for the newpending_reviewcase (renders'Betalning mottagen, under granskning'). The switchdefaultkeeps existing tests green, but this new label is uncovered — worth addingit('shows "Betalning mottagen, under granskning" for pending_review status').View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.