156 lines
8.8 KiB
Markdown
156 lines
8.8 KiB
Markdown
|
|
# Commerce Module Review
|
||
|
|
|
||
|
|
**Updated:** 2026-01-21 - Rate limiting, idempotency keys, and expired order cleanup implemented
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The Commerce module is a comprehensive billing and subscription management system that handles:
|
||
|
|
- Order creation and checkout flows (single purchases and subscriptions)
|
||
|
|
- Multi-gateway payment processing (BTCPay primary, Stripe secondary)
|
||
|
|
- Subscription lifecycle management (create, pause, cancel, renew)
|
||
|
|
- Dunning system for failed payment recovery (retry, pause, suspend, cancel stages)
|
||
|
|
- Invoice generation and PDF creation
|
||
|
|
- Coupon/discount management
|
||
|
|
- Tax calculation (UK VAT, EU OSS, US state taxes, Australian GST)
|
||
|
|
- Refund processing
|
||
|
|
- Permission matrix for multi-entity hierarchies (M1/M2/M3 model)
|
||
|
|
- Webhook handling with deduplication and audit logging
|
||
|
|
|
||
|
|
The module follows the modular monolith architecture with Boot.php registering services, routes, commands, and Livewire components.
|
||
|
|
|
||
|
|
## Production Readiness Score: 94/100 (was 92/100 - checkout protection and cleanup added 2026-01-21)
|
||
|
|
|
||
|
|
The module is well-architected with solid fundamentals. All critical issues fixed in Wave 4.
|
||
|
|
|
||
|
|
## Critical Issues (Must Fix)
|
||
|
|
|
||
|
|
- [x] **Webhook signature verification bypassed in tests** - VERIFIED: Production implementation properly calls `$this->gateway->verifyWebhookSignature()` and returns 401 on failure. Already secure.
|
||
|
|
|
||
|
|
- [x] **Missing database indexes on webhook_events table** - VERIFIED: Migration already has `$table->unique(['gateway', 'event_id'])` index. Already correct.
|
||
|
|
|
||
|
|
- [x] **Order.workspace scope uses wrong field** - FIXED: `scopeForWorkspace` now uses `orderable_type` and `orderable_id` for polymorphic relations.
|
||
|
|
|
||
|
|
- [x] **Invoice workspace relationship missing for User orderables** - FIXED: Added `getWorkspaceIdAttribute()` accessor and `getResolvedWorkspace()` method to Order model. Webhook controllers updated to use these.
|
||
|
|
|
||
|
|
- [x] **Missing transaction isolation in webhook handlers** - FIXED: Both `BTCPayWebhookController` and `StripeWebhookController` now wrap processing in `DB::transaction()`.
|
||
|
|
|
||
|
|
- [x] **BTCPay gateway chargePaymentMethod** - FIXED: `CommerceService::retryInvoicePayment()` now correctly checks `$payment->status === 'succeeded'` and handles BTCPay pending payments appropriately.
|
||
|
|
|
||
|
|
## Recommended Improvements
|
||
|
|
|
||
|
|
- [x] **Add rate limiting per customer on checkout** - DONE: `CheckoutRateLimiter` service implemented with sliding window rate limiting. Uses workspace/user/IP hierarchy for throttle keys. 5 attempts per 15 minutes.
|
||
|
|
|
||
|
|
- [x] **Add idempotency keys to order creation** - DONE: `idempotency_key` field added to orders table. Migration `2026_01_21_120000_add_idempotency_key_to_orders_table.php` created. Used in CommerceService and CheckoutPage.
|
||
|
|
|
||
|
|
- [x] **Extract CouponValidationResult to dedicated file** - DONE: Extracted to `Data/CouponValidationResult.php`.
|
||
|
|
|
||
|
|
- [x] **Add scheduled job for expired order cleanup** - DONE: `CleanupExpiredOrders` command (`commerce:cleanup-orders`) created. Cancels pending orders older than configurable TTL. Supports `--dry-run` and `--ttl` options. Chunked processing with logging.
|
||
|
|
|
||
|
|
- [ ] **Add observability/metrics** - Consider adding metrics for: payment success rate, dunning recovery rate, webhook processing time, gateway errors.
|
||
|
|
|
||
|
|
- [ ] **Standardise error responses in API controllers** - `CommerceController` methods should return consistent JSON error structures.
|
||
|
|
|
||
|
|
- [x] **Add model factories for Commerce models** - DONE: Created OrderFactory, InvoiceFactory, SubscriptionFactory, CouponFactory, and PaymentFactory in `Database/Factories/`.
|
||
|
|
|
||
|
|
- [ ] **Implement webhook retry mechanism** - Failed webhooks (logged with status `failed`) have no automatic retry. Consider dead-letter queue or scheduled retry.
|
||
|
|
|
||
|
|
- [ ] **Add cancellation feedback collection** - Store cancellation reasons/feedback when users cancel subscriptions for product insights.
|
||
|
|
|
||
|
|
- [ ] **Tax ID validation is config-gated but implementation unclear** - Config has `validate_tax_ids_api` but actual HMRC/VIES API integration not visible in TaxService.
|
||
|
|
|
||
|
|
## Missing Features (Future)
|
||
|
|
|
||
|
|
- [ ] **Usage-based billing** - Config has `features.usage_billing => false` as placeholder for metered billing.
|
||
|
|
|
||
|
|
- [ ] **Multi-currency support** - Currently defaults to GBP. Config exists but full multi-currency handling not implemented.
|
||
|
|
|
||
|
|
- [ ] **Credit notes** - No model for credit notes when issuing partial refunds or adjustments.
|
||
|
|
|
||
|
|
- [ ] **Payment method management UI** - `PaymentMethods` Livewire component exists but no visible add/remove card flow.
|
||
|
|
|
||
|
|
- [ ] **Subscription upgrade preview API** - `previewUpgrade` endpoint exists but no corresponding frontend to show prorated amounts.
|
||
|
|
|
||
|
|
- [ ] **Bulk coupon generation** - CouponService can generate codes but no bulk creation for marketing campaigns.
|
||
|
|
|
||
|
|
- [ ] **Affiliate/referral tracking** - `RewardAgentReferralOnSubscription` listener exists but full referral system incomplete.
|
||
|
|
|
||
|
|
- [ ] **Warehouse/inventory system** - Models exist (`Warehouse`, `Inventory`, `InventoryMovement`) but services not implemented. May be for physical goods expansion.
|
||
|
|
|
||
|
|
## Test Coverage Assessment
|
||
|
|
|
||
|
|
**Current Coverage:**
|
||
|
|
- `CheckoutFlowTest.php` - Basic checkout flow testing
|
||
|
|
- `CompoundSkuTest.php` - SKU parsing and building
|
||
|
|
- `ContentOverrideServiceTest.php` - Content override system
|
||
|
|
- `CouponServiceTest.php` - Comprehensive coupon validation and calculation
|
||
|
|
- `DunningServiceTest.php` - Thorough dunning lifecycle testing
|
||
|
|
- `ProcessSubscriptionRenewalTest.php` - Renewal job testing
|
||
|
|
- `RefundServiceTest.php` - Refund flow with mocked gateway
|
||
|
|
- `SubscriptionServiceTest.php` - Subscription CRUD operations
|
||
|
|
- `TaxServiceTest.php` - Tax calculation scenarios
|
||
|
|
- `WebhookTest.php` - Extensive webhook handling for both gateways
|
||
|
|
|
||
|
|
**Coverage Gaps:**
|
||
|
|
- [ ] No unit tests (Unit/ directory empty)
|
||
|
|
- [ ] No tests for `PermissionMatrixService` despite complex hierarchy logic
|
||
|
|
- [ ] No tests for `InvoiceService` PDF generation
|
||
|
|
- [ ] No tests for `ProductCatalogService` and `WarehouseService`
|
||
|
|
- [ ] No integration tests for actual gateway API calls
|
||
|
|
- [ ] No tests for admin Livewire components
|
||
|
|
- [ ] No tests for web checkout Livewire components
|
||
|
|
- [ ] Missing edge case tests for concurrent webhook delivery
|
||
|
|
- [ ] No load/stress testing for high-volume scenarios
|
||
|
|
|
||
|
|
**Test Quality:**
|
||
|
|
- Tests use Pest with good describe/it structure
|
||
|
|
- Proper use of RefreshDatabase trait
|
||
|
|
- Good mocking of external dependencies
|
||
|
|
- Notification and Event faking used appropriately
|
||
|
|
|
||
|
|
## Security Concerns
|
||
|
|
|
||
|
|
1. **Webhook signature verification** - Properly implemented using HMAC-SHA256 for BTCPay and Stripe's built-in verification. Signatures are masked in logs.
|
||
|
|
|
||
|
|
2. **No explicit input validation on order metadata** - The `metadata` array passed to orders is stored directly. Consider schema validation to prevent injection of malicious data.
|
||
|
|
|
||
|
|
3. **Invoice number generation uses sequential IDs** - Pattern `INV-{year}-{sequence}` is predictable. Not a vulnerability but consider if invoice numbers should be harder to enumerate.
|
||
|
|
|
||
|
|
4. **PDF generation with user data** - Invoice PDFs include user-provided billing names/addresses. Ensure DomPDF or Snappy has XSS protections enabled.
|
||
|
|
|
||
|
|
5. **Coupon code brute-forcing** - No rate limiting on coupon validation. Attackers could enumerate valid codes.
|
||
|
|
|
||
|
|
6. **Permission matrix training mode** - `training_mode` config allows undefined permissions to prompt for approval. Ensure this is NEVER enabled in production (`COMMERCE_MATRIX_TRAINING=false`).
|
||
|
|
|
||
|
|
7. **API authentication** - Provisioning API uses `commerce.api` middleware but implementation not visible. Verify bearer token validation is secure.
|
||
|
|
|
||
|
|
8. **No audit logging for admin actions** - Coupon creation, refund processing, subscription cancellation by admins should be logged for compliance.
|
||
|
|
|
||
|
|
## Notes
|
||
|
|
|
||
|
|
### Architecture Observations
|
||
|
|
- Clean separation between gateway-specific logic (PaymentGatewayContract implementations) and business logic (Services)
|
||
|
|
- Good use of Laravel events for subscription lifecycle (SubscriptionCreated, SubscriptionUpdated, etc.)
|
||
|
|
- Dunning service follows best practices with configurable retry intervals and grace periods
|
||
|
|
- Webhook logging with deduplication prevents replay attacks and aids debugging
|
||
|
|
|
||
|
|
### Configuration
|
||
|
|
- Comprehensive config file covering all aspects of billing
|
||
|
|
- Environment variable support for sensitive values (API keys, secrets)
|
||
|
|
- Feature flags allow gradual rollout of capabilities
|
||
|
|
|
||
|
|
### Code Quality
|
||
|
|
- Consistent use of type hints and return types
|
||
|
|
- Proper use of database transactions for multi-step operations
|
||
|
|
- Good error handling in critical paths
|
||
|
|
- UK English spelling used consistently (colour, organisation, centre)
|
||
|
|
|
||
|
|
### Dependencies
|
||
|
|
- `barryvdh/laravel-dompdf` for PDF generation
|
||
|
|
- Stripe PHP SDK (implied by StripeGateway)
|
||
|
|
- BTCPay Server API client (custom implementation)
|
||
|
|
|
||
|
|
### Migration State
|
||
|
|
- 11 migrations covering all models
|
||
|
|
- Latest migrations dated 2026-01-21 (pause_count and webhook_events)
|
||
|
|
- No down() methods visible - verify rollback capability
|