stripe = new StripeClient(config('paisa.gateways.stripe.secret_key')); } // ── Refund a PaymentIntent (full or partial) ──────────────── public function refundPaymentIntent(string $paymentIntentId, ?int $amountInPence = null, string $reason = ''): array { try { $params = [ 'payment_intent' => $paymentIntentId, 'reason' => 'requested_by_customer', ]; if ($amountInPence !== null && $amountInPence > 0) { $params['amount'] = $amountInPence; } $refund = $this->stripe->refunds->create($params); Log::info('Stripe refund created', [ 'refund_id' => $refund->id, 'payment_intent' => $paymentIntentId, 'amount' => $refund->amount, 'status' => $refund->status, 'reason' => $reason, ]); return [ 'success' => true, 'refund_id' => $refund->id, 'amount' => $refund->amount, 'status' => $refund->status, ]; } catch (ApiErrorException $e) { Log::error('Stripe refund failed', [ 'payment_intent' => $paymentIntentId, 'error' => $e->getMessage(), 'reason' => $reason, ]); return ['success' => false, 'error' => $e->getMessage()]; } } // ── Detach payment method from a SetupIntent ──────────────── // This is how we "cancel" recurring: remove the card so no // future charges can be made. public function detachPaymentMethod(string $setupIntentId): array { try { $si = $this->stripe->setupIntents->retrieve($setupIntentId); if (! $si->payment_method) { return ['success' => true, 'message' => 'No payment method attached']; } $pmId = is_string($si->payment_method) ? $si->payment_method : $si->payment_method->id; $this->stripe->paymentMethods->detach($pmId); Log::info('Stripe payment method detached', [ 'setup_intent' => $setupIntentId, 'payment_method' => $pmId, ]); return [ 'success' => true, 'message' => "Payment method {$pmId} detached", 'payment_method' => $pmId, ]; } catch (ApiErrorException $e) { Log::error('Failed to detach payment method', [ 'setup_intent' => $setupIntentId, 'error' => $e->getMessage(), ]); return ['success' => false, 'error' => $e->getMessage()]; } } // ── Retrieve PaymentIntent details for display ────────────── public function getPaymentDetails(string $paymentIntentId): ?array { try { $pi = $this->stripe->paymentIntents->retrieve($paymentIntentId, [ 'expand' => ['latest_charge'], ]); $charge = $pi->latest_charge; return [ 'id' => $pi->id, 'status' => $pi->status, 'amount' => $pi->amount, 'currency' => strtoupper($pi->currency), 'created' => date('d M Y H:i', $pi->created), 'refunded' => $charge?->refunded ?? false, 'amount_refunded' => $charge?->amount_refunded ?? 0, 'card_brand' => $charge?->payment_method_details?->card?->brand ?? null, 'card_last4' => $charge?->payment_method_details?->card?->last4 ?? null, ]; } catch (ApiErrorException $e) { return null; } } // ── Retrieve SetupIntent details for display ──────────────── public function getSetupIntentDetails(string $setupIntentId): ?array { try { $si = $this->stripe->setupIntents->retrieve($setupIntentId, [ 'expand' => ['payment_method'], ]); $pm = $si->payment_method; return [ 'id' => $si->id, 'status' => $si->status, 'created' => date('d M Y H:i', $si->created), 'payment_method_id' => is_string($pm) ? $pm : ($pm?->id ?? null), 'card_brand' => is_object($pm) ? ($pm->card?->brand ?? null) : null, 'card_last4' => is_object($pm) ? ($pm->card?->last4 ?? null) : null, 'card_exp_month' => is_object($pm) ? ($pm->card?->exp_month ?? null) : null, 'card_exp_year' => is_object($pm) ? ($pm->card?->exp_year ?? null) : null, 'customer_id' => $si->customer, ]; } catch (ApiErrorException $e) { return null; } } }