Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.00% covered (warning)
84.00%
21 / 25
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
PaymentManager
84.00% covered (warning)
84.00%
21 / 25
60.00% covered (warning)
60.00%
6 / 10
19.33
0.00% covered (danger)
0.00%
0 / 1
 driver
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 keys
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isOn
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
 config
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enabled
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 hasMoreThanCod
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 charge
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 verify
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 refund
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
1<?php
2
3namespace App\Services\Payment;
4
5use App\Models\Order;
6use App\Models\Payment;
7use App\Services\Payment\Contracts\PaymentGatewayContract;
8use Illuminate\Http\Request;
9use Illuminate\Support\Collection;
10
11/**
12 * Resolves gateways from config/payment_gateways.php and their saved state
13 * from the `payment_gateways` settings group:
14 *   payment_gateways.<key>.enabled  bool  — operator turned it on
15 *   payment_gateways.<key>.config   array — its configSchema() field values
16 *
17 * A gateway is only actually available (see enabled()) when: it's
18 * registered, its feature flag (if any) is on, and the operator switched it
19 * on — three independent gates, so turning `online_payments` off hides
20 * every BD wallet from checkout in one flip regardless of their individual
21 * toggles, and vice versa a wallet stays off until BOTH the flag and its
22 * own toggle are on.
23 */
24class PaymentManager
25{
26    /** @var array<string, PaymentGatewayContract> */
27    private array $resolved = [];
28
29    public function driver(string $key): PaymentGatewayContract
30    {
31        if (isset($this->resolved[$key])) {
32            return $this->resolved[$key];
33        }
34
35        $entry = config("payment_gateways.{$key}");
36
37        if (! $entry) {
38            throw new \InvalidArgumentException("Unknown payment gateway \"{$key}\".");
39        }
40
41        return $this->resolved[$key] = app($entry['driver']);
42    }
43
44    public function has(string $key): bool
45    {
46        return config("payment_gateways.{$key}") !== null;
47    }
48
49    /** @return string[] every registered gateway key, in config order */
50    public function keys(): array
51    {
52        return array_keys(config('payment_gateways', []));
53    }
54
55    public function isOn(string $key): bool
56    {
57        $entry = config("payment_gateways.{$key}");
58
59        if (! $entry) {
60            return false;
61        }
62
63        if ($entry['feature'] && ! feature($entry['feature'])) {
64            return false;
65        }
66
67        return (bool) settings("payment_gateways.{$key}.enabled", $entry['default_enabled'] ?? false);
68    }
69
70    /** @return array<string, mixed> this gateway's saved config field values */
71    public function config(string $key): array
72    {
73        return (array) settings("payment_gateways.{$key}.config", []);
74    }
75
76    /** Every gateway currently selectable at checkout — on, and actually finished. */
77    public function enabled(): Collection
78    {
79        return collect($this->keys())
80            ->filter(fn ($key) => $this->isOn($key) && $this->driver($key)->isImplemented())
81            ->map(fn ($key) => $this->driver($key))
82            ->values();
83    }
84
85    public function hasMoreThanCod(): bool
86    {
87        return $this->enabled()->contains(fn (PaymentGatewayContract $d) => $d->key() !== 'cod');
88    }
89
90    public function charge(string $key, Order $order): PaymentResult
91    {
92        return $this->driver($key)->charge($order, $this->config($key));
93    }
94
95    public function verify(string $key, Request $request): PaymentResult
96    {
97        return $this->driver($key)->verify($request, $this->config($key));
98    }
99
100    /** False (never throws) when this gateway/payment can't be refunded through the API. */
101    public function refund(Payment $payment, float $amount): bool
102    {
103        if (! $payment->gateway || ! $this->has($payment->gateway)) {
104            return false;
105        }
106
107        return $this->driver($payment->gateway)->refund($payment, $amount, $this->config($payment->gateway));
108    }
109}