- Redesigned all email templates with professional corporate style - Created base layout with dark header, status cards, and footer - Updated: subscription-cancelled, account-activation, welcome, welcome-new-user, due-payments-alert - Removed emojis and gradients for cleaner look - Added multi-language support (ES, PT-BR, EN) - Fixed email delivery (sync instead of queue) - Fixed PayPal already-cancelled subscription handling - Cleaned orphan subscriptions from deleted users
190 lines
6.4 KiB
PHP
190 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Plan;
|
|
use App\Services\PayPalService;
|
|
use Illuminate\Console\Command;
|
|
|
|
class SetupPayPalPlans extends Command
|
|
{
|
|
protected $signature = 'paypal:setup-plans';
|
|
protected $description = 'Create products and billing plans in PayPal for all active plans';
|
|
|
|
public function handle()
|
|
{
|
|
$this->info('🚀 Starting PayPal Plans Setup...');
|
|
$this->newLine();
|
|
|
|
$paypal = new PayPalService();
|
|
|
|
// Test authentication first
|
|
$this->info('Testing PayPal authentication...');
|
|
try {
|
|
$token = $paypal->getAccessToken();
|
|
$this->info('✅ PayPal authentication successful!');
|
|
$this->newLine();
|
|
} catch (\Exception $e) {
|
|
$this->error('❌ PayPal authentication failed: ' . $e->getMessage());
|
|
return 1;
|
|
}
|
|
|
|
// Get plans that don't have PayPal plan IDs yet
|
|
$plans = Plan::where('is_active', true)
|
|
->where('is_free', false)
|
|
->get();
|
|
|
|
if ($plans->isEmpty()) {
|
|
$this->warn('No paid plans found in database.');
|
|
return 0;
|
|
}
|
|
|
|
$this->info("Found {$plans->count()} paid plan(s) to setup in PayPal:");
|
|
$this->newLine();
|
|
|
|
foreach ($plans as $plan) {
|
|
$this->info("📦 Processing: {$plan->name}");
|
|
|
|
// Check if already has PayPal plan ID
|
|
if ($plan->paypal_plan_id) {
|
|
$this->warn(" Already has PayPal Plan ID: {$plan->paypal_plan_id}");
|
|
$this->line(" Skipping...");
|
|
$this->newLine();
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
// Step 1: Create Product in PayPal
|
|
$this->line(" Creating product in PayPal...");
|
|
$product = $this->createProduct($paypal, $plan);
|
|
$this->info(" ✅ Product created: {$product['id']}");
|
|
|
|
// Step 2: Create Billing Plan in PayPal
|
|
$this->line(" Creating billing plan in PayPal...");
|
|
$billingPlan = $this->createBillingPlan($paypal, $plan, $product['id']);
|
|
$this->info(" ✅ Billing Plan created: {$billingPlan['id']}");
|
|
|
|
// Step 3: Save PayPal Plan ID to database
|
|
$plan->paypal_plan_id = $billingPlan['id'];
|
|
$plan->save();
|
|
$this->info(" ✅ Saved to database!");
|
|
|
|
$this->newLine();
|
|
|
|
} catch (\Exception $e) {
|
|
$this->error(" ❌ Error: " . $e->getMessage());
|
|
$this->newLine();
|
|
}
|
|
}
|
|
|
|
$this->info('🎉 PayPal Plans Setup completed!');
|
|
$this->newLine();
|
|
|
|
// Show summary
|
|
$this->table(
|
|
['Plan', 'Price', 'Billing', 'PayPal Plan ID'],
|
|
Plan::where('is_free', false)->get()->map(function ($p) {
|
|
return [
|
|
$p->name,
|
|
'€' . number_format($p->price, 2),
|
|
$p->billing_period,
|
|
$p->paypal_plan_id ?? 'Not set'
|
|
];
|
|
})
|
|
);
|
|
|
|
return 0;
|
|
}
|
|
|
|
private function createProduct(PayPalService $paypal, Plan $plan): array
|
|
{
|
|
$baseUrl = config('services.paypal.mode') === 'sandbox'
|
|
? 'https://api-m.sandbox.paypal.com'
|
|
: 'https://api-m.paypal.com';
|
|
|
|
// Get fresh token for this request
|
|
\Illuminate\Support\Facades\Cache::forget('paypal_access_token');
|
|
$token = $paypal->getAccessToken();
|
|
|
|
$response = \Illuminate\Support\Facades\Http::withToken($token)
|
|
->post("{$baseUrl}/v1/catalogs/products", [
|
|
'name' => "WEBMoney - {$plan->name}",
|
|
'description' => $plan->description ?? "Subscription plan for WEBMoney",
|
|
'type' => 'SERVICE',
|
|
'category' => 'SOFTWARE',
|
|
'home_url' => config('app.url'),
|
|
]);
|
|
|
|
if (!$response->successful()) {
|
|
throw new \Exception('Failed to create product: ' . $response->body());
|
|
}
|
|
|
|
return $response->json();
|
|
}
|
|
|
|
private function createBillingPlan(PayPalService $paypal, Plan $plan, string $productId): array
|
|
{
|
|
$baseUrl = config('services.paypal.mode') === 'sandbox'
|
|
? 'https://api-m.sandbox.paypal.com'
|
|
: 'https://api-m.paypal.com';
|
|
|
|
$billingCycles = [];
|
|
|
|
// Add trial period if plan has trial
|
|
if ($plan->trial_days > 0) {
|
|
$billingCycles[] = [
|
|
'frequency' => [
|
|
'interval_unit' => 'DAY',
|
|
'interval_count' => $plan->trial_days,
|
|
],
|
|
'tenure_type' => 'TRIAL',
|
|
'sequence' => 1,
|
|
'total_cycles' => 1,
|
|
'pricing_scheme' => [
|
|
'fixed_price' => [
|
|
'value' => '0',
|
|
'currency_code' => $plan->currency,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
// Regular billing cycle
|
|
$billingCycles[] = [
|
|
'frequency' => [
|
|
'interval_unit' => $plan->billing_period === 'annual' ? 'YEAR' : 'MONTH',
|
|
'interval_count' => 1,
|
|
],
|
|
'tenure_type' => 'REGULAR',
|
|
'sequence' => $plan->trial_days > 0 ? 2 : 1,
|
|
'total_cycles' => 0, // Infinite
|
|
'pricing_scheme' => [
|
|
'fixed_price' => [
|
|
'value' => number_format($plan->price, 2, '.', ''),
|
|
'currency_code' => $plan->currency,
|
|
],
|
|
],
|
|
];
|
|
|
|
$response = \Illuminate\Support\Facades\Http::withToken($paypal->getAccessToken())
|
|
->post("{$baseUrl}/v1/billing/plans", [
|
|
'product_id' => $productId,
|
|
'name' => $plan->name,
|
|
'description' => $plan->description ?? "WEBMoney {$plan->name} subscription",
|
|
'status' => 'ACTIVE',
|
|
'billing_cycles' => $billingCycles,
|
|
'payment_preferences' => [
|
|
'auto_bill_outstanding' => true,
|
|
'setup_fee_failure_action' => 'CONTINUE',
|
|
'payment_failure_threshold' => 3,
|
|
],
|
|
]);
|
|
|
|
if (!$response->successful()) {
|
|
throw new \Exception('Failed to create billing plan: ' . $response->body());
|
|
}
|
|
|
|
return $response->json();
|
|
}
|
|
}
|