Align commerce module with the monorepo module structure by updating all namespaces to use the Core\Mod\Commerce convention. This change supports the recent monorepo separation and ensures consistency with other modules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
122 lines
3.5 KiB
PHP
122 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace Core\Mod\Commerce\Console;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Core\Mod\Commerce\Models\Subscription;
|
|
use Core\Mod\Commerce\Services\UsageBillingService;
|
|
|
|
/**
|
|
* Sync usage records to Stripe metered billing.
|
|
*
|
|
* Run periodically to ensure usage is reported to Stripe
|
|
* for metered billing invoices.
|
|
*/
|
|
class SyncUsageToStripe extends Command
|
|
{
|
|
protected $signature = 'commerce:sync-usage
|
|
{--subscription= : Sync only a specific subscription ID}
|
|
{--dry-run : Show what would be synced without making changes}';
|
|
|
|
protected $description = 'Sync usage records to Stripe metered billing API';
|
|
|
|
public function __construct(
|
|
protected UsageBillingService $usageBilling
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
public function handle(): int
|
|
{
|
|
if (! config('commerce.features.usage_billing', false)) {
|
|
$this->info('Usage billing is disabled.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
if (! config('commerce.usage_billing.sync_to_stripe', true)) {
|
|
$this->info('Stripe sync is disabled.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$dryRun = $this->option('dry-run');
|
|
$subscriptionId = $this->option('subscription');
|
|
|
|
if ($dryRun) {
|
|
$this->warn('DRY RUN MODE - No changes will be made');
|
|
}
|
|
|
|
$this->info('Syncing usage to Stripe...');
|
|
$this->newLine();
|
|
|
|
// Get subscriptions to sync
|
|
$query = Subscription::query()
|
|
->where('gateway', 'stripe')
|
|
->whereNotNull('gateway_subscription_id')
|
|
->active();
|
|
|
|
if ($subscriptionId) {
|
|
$query->where('id', $subscriptionId);
|
|
}
|
|
|
|
$subscriptions = $query->get();
|
|
|
|
if ($subscriptions->isEmpty()) {
|
|
$this->info('No Stripe subscriptions found to sync.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->info("Found {$subscriptions->count()} subscription(s) to sync.");
|
|
$this->newLine();
|
|
|
|
$totalSynced = 0;
|
|
$errors = 0;
|
|
|
|
$this->withProgressBar($subscriptions, function (Subscription $subscription) use ($dryRun, &$totalSynced, &$errors) {
|
|
if ($dryRun) {
|
|
// Count unsynced usage for preview
|
|
$unsynced = $subscription->usageRecords()
|
|
->whereNull('synced_at')
|
|
->where('quantity', '>', 0)
|
|
->count();
|
|
$totalSynced += $unsynced;
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$synced = $this->usageBilling->syncToStripe($subscription);
|
|
$totalSynced += $synced;
|
|
} catch (\Exception $e) {
|
|
$errors++;
|
|
Log::error('Failed to sync usage to Stripe', [
|
|
'subscription_id' => $subscription->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
});
|
|
|
|
$this->newLine(2);
|
|
|
|
if ($dryRun) {
|
|
$this->info("Would sync {$totalSynced} usage record(s).");
|
|
} else {
|
|
$this->info("Synced {$totalSynced} usage record(s) to Stripe.");
|
|
|
|
if ($errors > 0) {
|
|
$this->warn("{$errors} subscription(s) had sync errors. Check logs for details.");
|
|
}
|
|
}
|
|
|
|
Log::info('Usage sync completed', [
|
|
'synced' => $totalSynced,
|
|
'errors' => $errors,
|
|
'dry_run' => $dryRun,
|
|
]);
|
|
|
|
return $errors > 0 ? self::FAILURE : self::SUCCESS;
|
|
}
|
|
}
|