Update pricing UX, billing flows, and API rules

This commit is contained in:
Dwindi Ramadhana
2026-02-12 00:52:40 +07:00
parent cf065fab1e
commit a905256353
202 changed files with 22348 additions and 301 deletions

View File

@@ -2,7 +2,13 @@
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;
use App\Services\LiveSqlImportService;
use App\Services\Billing\PaypalWebhookProcessor;
use App\Services\Billing\PayPalPlanSyncService;
use App\Models\WebhookEvent;
use Illuminate\Support\Facades\Mail;
use App\Mail\TestMailketing;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
@@ -16,3 +22,80 @@ Artisan::command('dewemoji:import-live-sql {path : Absolute path to dewemojiAPI_
$importer = app(LiveSqlImportService::class);
$importer->import($path, $truncate, $batch, $this->output);
})->purpose('Import live SQL dump into the current database');
Artisan::command('dewemoji:webhooks:process {--limit=100 : Max events to process} {--status=pending,received : Comma-separated statuses}', function () {
$limit = (int) $this->option('limit');
$statuses = array_filter(array_map('trim', explode(',', (string) $this->option('status'))));
if (empty($statuses)) {
$this->error('No statuses provided.');
return 1;
}
$processor = app(PaypalWebhookProcessor::class);
$events = WebhookEvent::query()
->whereIn('status', $statuses)
->orderBy('id')
->limit($limit)
->get();
$processed = 0;
$failed = 0;
foreach ($events as $event) {
try {
if ($event->provider === 'paypal') {
$processor->process((string) ($event->event_type ?? ''), $event->payload ?? []);
}
$event->update([
'status' => 'processed',
'processed_at' => now(),
'error' => null,
]);
$processed++;
} catch (\Throwable $e) {
$event->update([
'status' => 'error',
'processed_at' => now(),
'error' => $e->getMessage(),
]);
$failed++;
}
}
$this->info("Processed {$processed} events, failed {$failed}.");
return 0;
})->purpose('Process pending webhook events');
Schedule::command('dewemoji:webhooks:process --limit=200')->everyMinute()->withoutOverlapping();
Artisan::command('paypal:sync-plans {--mode=both : sandbox|live|both}', function () {
$mode = (string) $this->option('mode');
$service = app(PayPalPlanSyncService::class);
$runMode = match ($mode) {
'sandbox' => ['sandbox'],
'live' => ['live'],
default => ['sandbox', 'live'],
};
foreach ($runMode as $env) {
$this->info("Syncing PayPal plans: {$env}");
$result = $service->sync($env);
$this->line("Created: {$result['created']} · Updated: {$result['updated']} · Deactivated: {$result['deactivated']} · Skipped: {$result['skipped']}");
}
})->purpose('Create/rotate PayPal plans based on pricing plans');
Artisan::command('mailketing:test {email : Recipient email address}', function () {
$email = (string) $this->argument('email');
try {
Mail::to($email)->send(new TestMailketing());
$this->info("Mailketing test email sent to {$email}.");
return 0;
} catch (\Throwable $e) {
$this->error('Mailketing test failed: '.$e->getMessage());
return 1;
}
})->purpose('Send a Mailketing API test email');