Compare commits

..

2 commits

Author SHA1 Message Date
Claude
5fb038448b
test: add comprehensive tests for ReferralService
Cover referral code management, click tracking, conversion flow,
commission calculation, maturation, payout lifecycle, fraud prevention
(self-referral, disqualification), and statistics endpoints.

Fixes #6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:25:09 +00:00
Claude
5bce748a0f
security: add CSRF protection to API billing endpoints
- Add `verified` middleware to billing route group so only
  email-verified users can access billing endpoints
- Separate read-only GET routes from state-changing POST routes
- Add `throttle:6,1` rate limiting to state-changing endpoints
  (cancel, resume, upgrade/preview, upgrade) — 6 requests per minute
- Reorganise route group with clear section comments

Fixes #13

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:19:30 +00:00
2 changed files with 1431 additions and 12 deletions

View file

@ -52,10 +52,12 @@ Route::prefix('webhooks')->group(function () {
// });
// ─────────────────────────────────────────────────────────────────────────────
// Commerce Billing API (authenticated)
// Commerce Billing API (authenticated + verified)
// ─────────────────────────────────────────────────────────────────────────────
Route::middleware('auth')->prefix('commerce')->group(function () {
Route::middleware(['auth', 'verified'])->prefix('commerce')->group(function () {
// ── Read-only endpoints ──────────────────────────────────────────────
// Billing overview
Route::get('/billing', [CommerceController::class, 'billing'])
->name('api.commerce.billing');
@ -74,21 +76,27 @@ Route::middleware('auth')->prefix('commerce')->group(function () {
Route::get('/invoices/{invoice}/download', [CommerceController::class, 'downloadInvoice'])
->name('api.commerce.invoices.download');
// Subscription
// Subscription (read)
Route::get('/subscription', [CommerceController::class, 'subscription'])
->name('api.commerce.subscription');
Route::post('/cancel', [CommerceController::class, 'cancelSubscription'])
->name('api.commerce.cancel');
Route::post('/resume', [CommerceController::class, 'resumeSubscription'])
->name('api.commerce.resume');
// Usage
Route::get('/usage', [CommerceController::class, 'usage'])
->name('api.commerce.usage');
// Plan changes
Route::post('/upgrade/preview', [CommerceController::class, 'previewUpgrade'])
->name('api.commerce.upgrade.preview');
Route::post('/upgrade', [CommerceController::class, 'executeUpgrade'])
->name('api.commerce.upgrade');
// ── State-changing endpoints (rate-limited) ──────────────────────────
Route::middleware('throttle:6,1')->group(function () {
// Subscription management
Route::post('/cancel', [CommerceController::class, 'cancelSubscription'])
->name('api.commerce.cancel');
Route::post('/resume', [CommerceController::class, 'resumeSubscription'])
->name('api.commerce.resume');
// Plan changes
Route::post('/upgrade/preview', [CommerceController::class, 'previewUpgrade'])
->name('api.commerce.upgrade.preview');
Route::post('/upgrade', [CommerceController::class, 'executeUpgrade'])
->name('api.commerce.upgrade');
});
});

File diff suppressed because it is too large Load diff