[ 'name' => 'Bitcoin', 'network' => 'mainnet', 'confirmations_required' => 3, 'block_time_minutes' => 10, ], 'ETH' => [ 'name' => 'Ethereum', 'network' => 'mainnet', 'confirmations_required' => 12, 'block_time_minutes' => 12, ], 'USDT' => [ 'name' => 'Tether', 'network' => 'ethereum', 'confirmations_required' => 12, 'block_time_minutes' => 12, ], 'USDC' => [ 'name' => 'USD Coin', 'network' => 'ethereum', 'confirmations_required' => 12, 'block_time_minutes' => 12, ], 'LTC' => [ 'name' => 'Litecoin', 'network' => 'mainnet', 'confirmations_required' => 6, 'block_time_minutes' => 2.5, ], ]; public function __construct(array $config = []) { $defaultConfig = [ 'webhook_secret' => null, 'success_url' => null, 'cancel_url' => null, 'confirmation_timeout_minutes' => 30, 'exchange_rate_provider' => 'coingecko', // or 'binance' ]; // Try to get config values if Laravel is available try { if (function_exists('config')) { $defaultConfig['webhook_secret'] = config('payments.crypto.webhook_secret'); } if (function_exists('route')) { $defaultConfig['success_url'] = route('payment.success'); $defaultConfig['cancel_url'] = route('payment.cancel'); } } catch (\Exception $e) { // Laravel not available, keep defaults } $this->config = array_merge($defaultConfig, $config); } public function getName(): string { return 'crypto'; } public function isActive(): bool { return ! empty($this->config['webhook_secret']); } public function createSubscription(User $user, Plan $plan, array $options = []): array { try { $crypto = $options['crypto'] ?? 'BTC'; $usdAmount = $plan->price; // Get current exchange rate $cryptoAmount = $this->convertUsdToCrypto($usdAmount, $crypto); // Generate payment address $paymentAddress = $this->generatePaymentAddress($crypto); // Create payment record $paymentId = $this->createPaymentRecord([ 'user_id' => $user->id, 'plan_id' => $plan->id, 'crypto' => $crypto, 'usd_amount' => $usdAmount, 'crypto_amount' => $cryptoAmount, 'address' => $paymentAddress, 'status' => 'pending', 'expires_at' => now()->addMinutes($this->config['confirmation_timeout_minutes']), ]); return [ 'provider_subscription_id' => $paymentId, 'status' => 'pending_payment', 'payment_address' => $paymentAddress, 'crypto' => $crypto, 'crypto_amount' => $cryptoAmount, 'usd_amount' => $usdAmount, 'expires_at' => now()->addMinutes($this->config['confirmation_timeout_minutes'])->toISOString(), 'type' => 'crypto_payment', ]; } catch (\Exception $e) { Log::error('Crypto subscription creation failed', [ 'user_id' => $user->id, 'plan_id' => $plan->id, 'error' => $e->getMessage(), ]); throw $e; } } public function cancelSubscription(Subscription $subscription, string $reason = ''): bool { try { // For crypto, we just mark as cancelled since there's no external subscription $paymentId = $subscription->provider_subscription_id; $this->updatePaymentRecord($paymentId, [ 'status' => 'cancelled', 'cancelled_at' => now(), 'cancellation_reason' => $reason, ]); return true; } catch (\Exception $e) { Log::error('Crypto subscription cancellation failed', [ 'subscription_id' => $subscription->id, 'error' => $e->getMessage(), ]); throw $e; } } public function updateSubscription(Subscription $subscription, Plan $newPlan): array { try { // Cancel old payment and create new one for upgraded plan $this->cancelSubscription($subscription, 'Plan upgrade'); $user = $subscription->user; return $this->createSubscription($user, $newPlan); } catch (\Exception $e) { Log::error('Crypto subscription update failed', [ 'subscription_id' => $subscription->id, 'new_plan_id' => $newPlan->id, 'error' => $e->getMessage(), ]); throw $e; } } public function pauseSubscription(Subscription $subscription): bool { // Crypto subscriptions don't support pausing in the traditional sense // We could implement a temporary suspension logic here if needed return false; } public function resumeSubscription(Subscription $subscription): bool { // Crypto subscriptions don't support pausing return false; } public function getSubscriptionDetails(string $providerSubscriptionId): array { try { $payment = $this->getPaymentRecord($providerSubscriptionId); return [ 'id' => $payment['id'], 'status' => $payment['status'], 'crypto' => $payment['crypto'], 'usd_amount' => $payment['usd_amount'], 'crypto_amount' => $payment['crypto_amount'], 'address' => $payment['address'], 'confirmations' => $payment['confirmations'], 'created_at' => $payment['created_at'], 'expires_at' => $payment['expires_at'], 'confirmed_at' => $payment['confirmed_at'], ]; } catch (\Exception $e) { Log::error('Crypto subscription details retrieval failed', [ 'subscription_id' => $providerSubscriptionId, 'error' => $e->getMessage(), ]); throw $e; } } public function createCheckoutSession(User $user, Plan $plan, array $options = []): array { return $this->createSubscription($user, $plan, $options); } public function createCustomerPortalSession(User $user): array { // Crypto doesn't have customer portals return [ 'portal_url' => route('dashboard'), 'message' => 'Crypto payments are managed through the dashboard', ]; } public function processWebhook(Request $request): array { try { $payload = $request->getContent(); $webhookData = json_decode($payload, true); if (! $this->validateWebhook($request)) { throw new \Exception('Invalid webhook signature'); } $eventType = $webhookData['type'] ?? 'unknown'; $result = [ 'event_type' => $eventType, 'processed' => false, 'data' => [], ]; switch ($eventType) { case 'payment_received': $result = $this->handlePaymentReceived($webhookData); break; case 'payment_confirmed': $result = $this->handlePaymentConfirmed($webhookData); break; case 'payment_expired': $result = $this->handlePaymentExpired($webhookData); break; default: Log::info('Unhandled crypto webhook event', ['event_type' => $eventType]); } return $result; } catch (\Exception $e) { Log::error('Crypto webhook processing failed', [ 'error' => $e->getMessage(), 'payload' => $request->getContent(), ]); throw $e; } } public function validateWebhook(Request $request): bool { try { $signature = $request->header('X-Signature'); $payload = $request->getContent(); if (! $signature || ! $this->config['webhook_secret']) { return false; } $expectedSignature = hash_hmac('sha256', $payload, $this->config['webhook_secret']); return hash_equals($signature, $expectedSignature); } catch (\Exception $e) { Log::warning('Crypto webhook validation failed', [ 'error' => $e->getMessage(), ]); return false; } } public function getConfiguration(): array { return $this->config; } public function syncSubscriptionStatus(Subscription $subscription): array { return $this->getSubscriptionDetails($subscription->provider_subscription_id); } public function getPaymentMethodDetails(string $paymentMethodId): array { try { $payment = $this->getPaymentRecord($paymentMethodId); return [ 'id' => $payment['id'], 'type' => 'crypto_address', 'crypto' => $payment['crypto'], 'address' => $payment['address'], 'network' => $this->supportedCryptos[$payment['crypto']]['network'] ?? 'unknown', 'created_at' => $payment['created_at'], ]; } catch (\Exception $e) { Log::error('Crypto payment method details retrieval failed', [ 'payment_method_id' => $paymentMethodId, 'error' => $e->getMessage(), ]); throw $e; } } public function processRefund(string $paymentId, float $amount, string $reason = ''): array { try { // Crypto payments are typically not refundable // We could implement a manual refund process if needed throw new \Exception('Crypto payments are not refundable'); } catch (\Exception $e) { Log::error('Crypto refund processing failed', [ 'payment_id' => $paymentId, 'amount' => $amount, 'error' => $e->getMessage(), ]); throw $e; } } public function getTransactionHistory(User $user, array $filters = []): array { try { $payments = $this->getUserPayments($user->id, $filters); $transactions = []; foreach ($payments as $payment) { $transactions[] = [ 'id' => $payment['id'], 'crypto' => $payment['crypto'], 'amount' => $payment['usd_amount'], 'crypto_amount' => $payment['crypto_amount'], 'status' => $payment['status'], 'address' => $payment['address'], 'confirmations' => $payment['confirmations'], 'created_at' => $payment['created_at'], 'confirmed_at' => $payment['confirmed_at'], ]; } return $transactions; } catch (\Exception $e) { Log::error('Crypto transaction history retrieval failed', [ 'user_id' => $user->id, 'error' => $e->getMessage(), ]); throw $e; } } public function calculateFees(float $amount): array { // Crypto fees: 1% network fee + 0.5% service fee $networkFee = $amount * 0.01; $serviceFee = $amount * 0.005; $totalFee = $networkFee + $serviceFee; return [ 'fixed_fee' => 0, 'percentage_fee' => $totalFee, 'total_fee' => $totalFee, 'net_amount' => $amount - $totalFee, ]; } public function getSupportedCurrencies(): array { return ['USD']; // We accept USD but process in crypto } public function supportsRecurring(): bool { return true; // Through manual renewal } public function supportsOneTime(): bool { return true; } // Helper methods public function convertUsdToCrypto(float $usdAmount, string $crypto): float { $cacheKey = "crypto_rate_{$crypto}_usd"; return Cache::remember($cacheKey, now()->addMinutes(5), function () use ($usdAmount, $crypto) { $rate = $this->getExchangeRate($crypto, 'USD'); return $usdAmount / $rate; }); } protected function getExchangeRate(string $fromCrypto, string $toCurrency): float { // This would integrate with CoinGecko, Binance, or other exchange rate APIs // For now, return mock rates $mockRates = [ 'BTC' => 45000.00, // 1 BTC = $45,000 'ETH' => 3000.00, // 1 ETH = $3,000 'USDT' => 1.00, // 1 USDT = $1.00 'USDC' => 1.00, // 1 USDC = $1.00 'LTC' => 150.00, // 1 LTC = $150 ]; return $mockRates[$fromCrypto] ?? 1.0; } protected function generatePaymentAddress(string $crypto): string { // In a real implementation, this would integrate with a crypto payment processor // For now, generate a mock address $prefix = [ 'BTC' => 'bc1q', 'ETH' => '0x', 'USDT' => '0x', 'USDC' => '0x', 'LTC' => 'ltc1', ]; $randomPart = bin2hex(random_bytes(32)); return ($prefix[$crypto] ?? '0x').substr($randomPart, 0, 40); } protected function createPaymentRecord(array $data): string { // In a real implementation, this would save to a database // For now, generate a mock ID and cache it $paymentId = 'crypto_'.uniqid(more_entropy: true); Cache::put("crypto_payment_{$paymentId}", array_merge($data, [ 'id' => $paymentId, 'created_at' => now()->toISOString(), 'confirmations' => 0, ]), now()->addHours(24)); return $paymentId; } protected function getPaymentRecord(string $paymentId): array { return Cache::get("crypto_payment_{$paymentId}", []); } protected function updatePaymentRecord(string $paymentId, array $updates): void { $payment = Cache::get("crypto_payment_{$paymentId}", []); if ($payment) { $updatedPayment = array_merge($payment, $updates); Cache::put("crypto_payment_{$paymentId}", $updatedPayment, now()->addHours(24)); } } protected function getUserPayments(int $userId, array $filters = []): array { // In a real implementation, this would query the database // For now, return empty array return []; } protected function confirmPayment(string $paymentId, int $confirmations, string $transactionHash): void { $this->updatePaymentRecord($paymentId, [ 'status' => 'confirmed', 'confirmations' => $confirmations, 'transaction_hash' => $transactionHash, 'confirmed_at' => now()->toISOString(), ]); } // Webhook handlers protected function handlePaymentReceived(array $webhookData): array { $paymentId = $webhookData['payment_id']; $confirmations = $webhookData['confirmations'] ?? 0; $transactionHash = $webhookData['transaction_hash'] ?? ''; $this->confirmPayment($paymentId, $confirmations, $transactionHash); return [ 'event_type' => 'payment_received', 'processed' => true, 'data' => [ 'payment_id' => $paymentId, 'confirmations' => $confirmations, 'transaction_hash' => $transactionHash, ], ]; } protected function handlePaymentConfirmed(array $webhookData): array { $paymentId = $webhookData['payment_id']; // Mark as fully confirmed $this->updatePaymentRecord($paymentId, [ 'status' => 'completed', ]); return [ 'event_type' => 'payment_confirmed', 'processed' => true, 'data' => [ 'payment_id' => $paymentId, ], ]; } protected function handlePaymentExpired(array $webhookData): array { $paymentId = $webhookData['payment_id']; $this->updatePaymentRecord($paymentId, [ 'status' => 'expired', ]); return [ 'event_type' => 'payment_expired', 'processed' => true, 'data' => [ 'payment_id' => $paymentId, ], ]; } // Additional interface methods public function getSubscriptionMetadata(Subscription $subscription): array { $payment = $this->getPaymentRecord($subscription->provider_subscription_id); return $payment['metadata'] ?? []; } public function updateSubscriptionMetadata(Subscription $subscription, array $metadata): bool { $paymentId = $subscription->provider_subscription_id; $payment = $this->getPaymentRecord($paymentId); if ($payment) { $this->updatePaymentRecord($paymentId, ['metadata' => $metadata]); return true; } return false; } public function startTrial(Subscription $subscription, int $trialDays): bool { // Crypto subscriptions don't have trials in the traditional sense return false; } public function applyCoupon(Subscription $subscription, string $couponCode): array { // Crypto doesn't support coupons natively throw new \Exception('Coupons not supported for crypto payments'); } public function removeCoupon(Subscription $subscription): bool { return false; // No coupons to remove } public function getUpcomingInvoice(Subscription $subscription): array { // Crypto subscriptions don't have invoices return [ 'amount_due' => 0, 'currency' => 'USD', 'next_payment_date' => null, ]; } public function retryFailedPayment(Subscription $subscription): array { // Crypto payments can't be retried automatically // User would need to make a new payment return $this->syncSubscriptionStatus($subscription); } public function canModifySubscription(Subscription $subscription): bool { try { $details = $this->getSubscriptionDetails($subscription->provider_subscription_id); return in_array($details['status'], ['pending_payment', 'confirmed', 'completed']); } catch (\Exception $e) { return false; } } public function getCancellationTerms(Subscription $subscription): array { return [ 'immediate_cancellation' => true, 'refund_policy' => 'non_refundable', 'cancellation_effective' => 'immediately', 'billing_cycle_proration' => false, ]; } public function exportSubscriptionData(Subscription $subscription): array { return [ 'provider' => 'crypto', 'provider_subscription_id' => $subscription->provider_subscription_id, 'data' => $subscription->provider_data, ]; } public function importSubscriptionData(User $user, array $subscriptionData): array { throw new \Exception('Import to crypto payments not implemented'); } }