Skip to content

Fix prepaid payment mapping and featured expiration - #2980

Merged
Armanul46 merged 6 commits into
sovware:developmentfrom
syedgalib:fix/generic-issues-2
Aug 27, 2026
Merged

Fix prepaid payment mapping and featured expiration#2980
Armanul46 merged 6 commits into
sovware:developmentfrom
syedgalib:fix/generic-issues-2

Conversation

@syedgalib

@syedgalib syedgalib commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Security fix
  • Improvement
  • New Feature
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Text changes
  • Other... Please describe:

Description

Why is this PR needed?

Legacy prepaid REST orders could write an invalid payment status, producing corrupted or missing payment rows while still returning a successful response. Concurrent payment inserts could make a successful insert appear to fail. Featured-listing updates could renew an already-paid entitlement indefinitely, while orders inserted directly as paid could miss expiration initialization. Failed or expired orders could also remove featured status even when another paid entitlement remained active.

What is done?

  • Maps legacy prepaid payments to paid while preserving the order and legacy response as prepaid.
  • Persists legacy REST payments without automatically synchronizing the order status.
  • Validates each inserted payment by its returned ID before synchronizing completed orders or firing paid-order hooks.
  • Removes partial payment and order records and returns an HTTP 500 error when payment creation fails.
  • Prevents the payment repository from synchronizing paid orders when the payment insert fails or stores an unexpected status.
  • Preserves an existing active standalone or plan-backed featured entitlement when another featured order fails.
  • Refreshes featured expiration when an order transitions from a non-paid status to paid.
  • Initializes missing expiration for featured orders inserted directly as paid.
  • Prevents duplicate callbacks and paid-order updates with an existing expiration from extending an entitlement.
  • Checks every paid featured order before cron removes featured status.
  • Preserves later and never-expiring listing dates while keeping order expiration available to cron.
  • Uses a focused expiration update so nullable order fields do not cause strict-MySQL update failures.

How to test

  1. Enable monetization and featured listings, and set the featured duration to 30 days.
  2. Create a legacy REST order with payment_status set to prepaid and verify the order remains prepaid, the payment is paid, and the response reports prepaid.
  3. Repeat the prepaid request with strict and non-strict MySQL modes and verify exactly one valid payment row is created without firing a paid-order update hook.
  4. Create a completed legacy REST order and verify the payment exists before the paid-order hook runs, while the order, payment, and response report the completed state.
  5. Force payment insertion to fail under strict MySQL and verify the endpoint returns HTTP 500, creates no order or payment row, and does not fire paid-order or success hooks.
  6. Insert two payments for the same order so a newer payment exists before the first is validated; verify the first payment succeeds by its own ID and synchronizes its paid order.
  7. Create a featured listing with an active paid standalone order, fail a later featured renewal, and verify the listing remains featured.
  8. Change a prepaid featured order to paid and verify its order and listing expirations are set to the payment time plus 30 days.
  9. Create a completed featured order directly as paid without an expiration and verify its first paid update initializes the order and listing expirations.
  10. Update that paid order again and repeat its paid payment callback; verify neither expiration changes.
  11. Create two paid featured orders for one listing, with the lower-ID order active and the higher-ID order expired; run cron and verify the listing remains featured.
  12. Expire both orders, rerun cron, and verify the listing is unfeatured.
  13. Verify a manually featured listing with no paid standalone order remains featured after cron.
  14. Verify a later listing expiration and a never-expiring listing remain unchanged while their featured order expiration is saved.
  15. Complete a zero-cost featured checkout and verify the order is paid, has a fresh expiration, and the payment receipt remains correct.

Any linked issues

N/A

Checklist

@RabbiIslamRony RabbiIslamRony left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two release-blocking issues remain on commit 33f08652ae.

  1. Preserve the prepaid order state when storing the paid payment (includes/rest-api/Version1/class-orders-controller.php:334)

A legacy REST request with payment_status=prepaid initially creates an order with the prepaid status, but the new payment mapping creates a paid payment. PaymentRepository::create() then automatically updates the same order to paid. In both strict and non-strict MySQL tests, the final stored order was paid, the payment was paid, and the REST response reported completed. This removes the collected-but-not-yet-renewed prepaid order state introduced by PR #2955 and makes prepaid indistinguishable from completed. The payment can remain paid, but creating it must preserve the order as prepaid, and the legacy response should expose the prepaid order state.

  1. Do not fire paid-order hooks before payment persistence is confirmed (includes/rest-api/Version1/class-orders-controller.php:164)

When a strict-MySQL payment insert fails, PaymentRepository::create() still sees the DTO status as paid, changes the order from prepaid to paid, and fires directorist_after_order_update before this controller validates the payment. I reproduced this with an oversized transaction ID: the endpoint correctly returned HTTP 500 and removed the order/payment rows, but the paid-order hook had already fired with old status prepaid and new status paid. Any renewal, entitlement, notification, or extension side effect triggered by that hook cannot be undone by deleting the rows. Order synchronization and paid-order hooks must be gated on a successful payment insert, ideally within an atomic workflow.

The featured-expiration changes passed the targeted runtime tests: delayed prepaid payment, later listing expiry, never-expire listing, strict partial update, immediate and expired cron, and zero-cost checkout/receipt. Desktop and 390px author-profile regression checks also passed. PHP syntax, targeted PHPCS, PHP 7.4-8.3 compatibility, and git diff --check passed locally.

Please fix the two prepaid payment/order lifecycle issues and rerun the strict and non-strict REST regression cases.

@RabbiIslamRony RabbiIslamRony left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

@Armanul46
Armanul46 merged commit b35b669 into sovware:development Aug 27, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants