Hello @mikecastrodemaria
This duplicate processing issue is still under active investigation on our side. We also have a fixing package available for testing, but it comes with limitations and we can only provide it through direct support. If you want to test it, please open a ticket here: https://paypal.inpsyde.com/docs/request-support/
Make sure to include a link to this thread so we can attach your context to the ticket immediately.
Kind Regards,
Krystian
Done , but :
We couldn’t create a request
System Report cannot exceed 32767 characters
I add as attachement
I’ve found the root cause of your duplicate order processing issue. Let me explain the problem and provide solutions.
Root Cause Analysis
The Bug
PayPal sends two different webhook events for the same transaction:
- CHECKOUT.ORDER.COMPLETED
- PAYMENT.CAPTURE.COMPLETED Both webhook handlers call payment_complete(), which triggers:
- Email notifications
- Inventory reduction
- Order status change Race Condition Flow Initial order status: ‘on-hold’
- First webhook arrives (CHECKOUT.ORDER.COMPLETED)
- CheckoutOrderCompleted.php:77 – Checks if status is ‘pending’ or ‘on-hold’ → ✓ passes
- Calls payment_complete() → sends email #1, reduces inventory -1
- Second webhook arrives simultaneously (PAYMENT.CAPTURE.COMPLETED)
- PaymentCaptureCompleted.php:98 – Checks if status is ‘on-hold’ → ✓ passes (before first webhook updates status)
- Calls payment_complete() → sends email #2, reduces inventory -1 again
Why Current Safeguards Fail
- Status checks are not atomic: Both webhooks read ‘on-hold’ status before either updates it
- No locking mechanism: WebhookOrchestrator.php exists but is NOT being used by the handlers
- No event deduplication: Webhook event IDs are stored (WebhookEventStorage.php:39) but never checked to prevent reprocessing
- No processing flag: No metadata flag is set before processing to indicate “order is being processed right now”
Proposed Solutions
I’ll give you 3 solutions from quickest fix to most comprehensive:
Solution 1: Add Per-Order Lock (Recommended – Quick Fix)
Modify both webhook handlers to use WebhookOrchestrator with order-specific locks:
Files to modify:
- modules/ppcp-webhooks/src/Handler/CheckoutOrderCompleted.php
- modules/ppcp-webhooks/src/Handler/PaymentCaptureCompleted.php What to change:
Add order-specific locking before calling payment_complete() to prevent concurrent processing of the same order.
- Solution 2: Add Processing Metadata Flag (Most Reliable) Add a metadata flag (_ppcp_payment_processing) before attempting to call payment_complete(): Logic:
- Check if ‘_ppcp_payment_processing’ flag exists → skip if true
- Set ‘_ppcp_payment_processing’ = ‘true’
- Check order status
- Call payment_complete()
- Set ‘_ppcp_payment_completed’ = current webhook event type
- Remove ‘_ppcp_payment_processing’ flag
- Solution 3: Restrict PaymentCaptureCompleted Handler (Targeted Fix) Make PaymentCaptureCompleted only process orders that were explicitly in an authorized state (have _ppcp_paypal_authorized metadata but
not _ppcp_paypal_captured). This prevents double-processing for standard checkout flows where authorization and capture happen atomically.
My Recommendation
Use Solution 2 (Metadata Flag) because it:
- Provides the strongest guarantee against duplicates
- Works even if webhooks arrive microseconds apart
- Tracks which webhook actually processed the payment
- Survives server restarts (unlike transient-based locks)
- Provides audit trail for troubleshooting
I’m scared of using this plugin anymore, too much problems in the latests months… and this problem during Black Friday promotion week!
Hello @antoniogallo
We released a fixing package yesterday that addresses the recent problems, and in most cases applying this update resolves the situation.
Please open a ticket here: https://paypal.inpsyde.com/docs/request-support/
Kind Regards,
Krystian