Passed
Push — master ( 7804a1...9491b2 )
by Adam
09:06
created

PaymentController::makePayment()   B

Complexity

Conditions 6
Paths 72

Size

Total Lines 59
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 35
nc 72
nop 2
dl 0
loc 59
rs 8.7377
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Coyote\Http\Controllers\Job;
4
5
use Coyote\Country;
6
use Coyote\Events\PaymentPaid;
7
use Coyote\Exceptions\PaymentFailedException;
8
use Coyote\Firm;
9
use Coyote\Http\Controllers\Controller;
10
use Coyote\Http\Requests\Job\PaymentRequest;
11
use Coyote\Payment;
12
use Coyote\Repositories\Contracts\CountryRepositoryInterface as CountryRepository;
13
use Coyote\Repositories\Contracts\CouponRepositoryInterface as CouponRepository;
14
use Coyote\Repositories\Contracts\PaymentRepositoryInterface as PaymentRepository;
15
use Coyote\Services\Invoice\CalculatorFactory;
16
use Coyote\Services\UrlBuilder;
17
use Coyote\Services\Invoice\Generator as InvoiceGenerator;
18
use Illuminate\Database\Connection as Db;
19
use Illuminate\Http\Request;
20
use Stripe\Exception\SignatureVerificationException;
21
use Stripe\PaymentIntent;
22
use Stripe\Stripe;
23
use Stripe\Webhook;
24
25
class PaymentController extends Controller
26
{
27
    /**
28
     * @var InvoiceGenerator
29
     */
30
    private $invoice;
31
32
    /**
33
     * @var CountryRepository
34
     */
35
    private $country;
36
37
    /**
38
     * @var Db
39
     */
40
    private $db;
41
42
    /**
43
     * @var CouponRepository
44
     */
45
    private $coupon;
46
47
    /**
48
     * @var array
49
     */
50
    private $vatRates;
51
52
    /**
53
     * @param InvoiceGenerator $invoice
54
     * @param Db $db
55
     * @param CountryRepository $country
56
     * @param CouponRepository $coupon
57
     */
58
    public function __construct(InvoiceGenerator $invoice, Db $db, CountryRepository $country, CouponRepository $coupon)
59
    {
60
        parent::__construct();
61
62
        $this->invoice = $invoice;
63
        $this->db = $db;
64
        $this->country = $country;
65
        $this->coupon = $coupon;
66
67
        $this->middleware(
68
            function (Request $request, $next) {
69
                /** @var \Coyote\Payment $payment */
70
                $payment = $request->route('payment');
71
72
                if ($payment instanceof Payment) {
0 ignored issues
show
introduced by
$payment is always a sub-type of Coyote\Payment.
Loading history...
73
                    abort_if($payment->status_id === Payment::PAID, 404);
74
                }
75
76
                return $next($request);
77
            },
78
            ['except' => 'success']
79
        );
80
81
        $this->breadcrumb->push('Praca', route('job.home'));
82
        $this->vatRates = $this->country->vatRatesList();
83
    }
84
85
    /**
86
     * @param \Coyote\Payment $payment
87
     * @return \Illuminate\View\View
88
     */
89
    public function index(Payment $payment)
90
    {
91
        $this->breadcrumb->push($payment->job->title, UrlBuilder::job($payment->job));
92
        $this->breadcrumb->push('Płatność');
93
94
        $firm = $payment->job->firm ?? new Firm();
95
96
        if (empty($firm->country_id)) {
97
            $geoIp = app('geo-ip');
98
            $result = $geoIp->ip($this->request->ip());
0 ignored issues
show
Bug introduced by
The method ip() does not exist on Illuminate\Contracts\Foundation\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

98
            /** @scrutinizer ignore-call */ 
99
            $result = $geoIp->ip($this->request->ip());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
99
100
            $firm->country = $result->country_code ?? null;
101
        }
102
103
        // calculate price based on payment details
104
        $calculator = CalculatorFactory::payment($payment);
105
106
        $countries = $this->country->pluck('code', 'id');
107
108
        return $this->view('job.payment', [
109
            'payment'           => $payment,
110
            'vat_rates'         => $this->vatRates,
111
            'vat_rate'          => $calculator->vatRate,
112
            'net_price'         => $calculator->netPrice(),
113
            'firm'              => $firm,
114
            'countries'         => $countries,
115
            'stripe_key'        => config('services.stripe.key')
116
        ]);
117
    }
118
119
    /**
120
     * @param PaymentRequest $request
121
     * @param Payment $payment
122
     * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
123
     * @throws \Stripe\Exception\ApiErrorException
124
     * @throws \Throwable
125
     */
126
    public function makePayment(PaymentRequest $request, Payment $payment)
127
    {
128
        Stripe::setApiKey(config('services.stripe.secret'));
129
130
        $coupon = $this->coupon->findBy('code', $request->input('coupon'));
131
        $payment->coupon_id = $coupon->id ?? null;
132
133
        $calculator = CalculatorFactory::payment($payment);
134
        $calculator->setCoupon($coupon);
135
136
        $country = $this->country->find($request->input('invoice.country_id'));
137
138
        $calculator->vatRate = $request->filled('invoice.vat_id') ? ($country->vat_rate ?? $payment->plan->vat_rate) : $payment->plan->vat_rate;
139
140
        $invoice = $request->input('invoice', []);
141
142
        if ($payment->job->firm_id) {
143
            // update firm's VAT ID
144
            $payment->job->firm->fill($request->only(['invoice.vat_id', 'invoice.country_id'])['invoice']);
145
            $payment->job->firm->save();
146
        }
147
148
        $this->db->beginTransaction();
149
150
        try {
151
            // save invoice data. keep in mind that we do not setup invoice number until payment is done.
152
            /** @var \Coyote\Invoice $invoice */
153
            $invoice = $this->invoice->create(
154
                array_merge($invoice, ['user_id' => $this->auth->id]),
0 ignored issues
show
Bug introduced by
$invoice of type Coyote\Invoice is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

154
                array_merge(/** @scrutinizer ignore-type */ $invoice, ['user_id' => $this->auth->id]),
Loading history...
155
                $payment,
156
                $calculator
157
            );
158
159
            // associate invoice with payment
160
            $payment->invoice()->associate($invoice);
161
162
            $payment->save();
163
            $this->db->commit();
164
        } catch (PaymentFailedException $e) {
165
            $this->handlePaymentException($e);
166
        } catch (\Exception $e) {
167
            $this->handlePaymentException($e);
168
        }
169
170
        if (!$calculator->grossPrice()) {
171
            return $this->successfulTransaction($payment);
172
        }
173
174
        $intent = PaymentIntent::create([
175
            'amount'                => $payment->invoice->grossPrice() * 100,
176
            'currency'              => strtolower($payment->invoice->currency->name),
177
            'metadata'              => ['id' => $payment->id],
178
            'payment_method_types'  => [$request->input('payment_method')]
179
        ]);
180
181
        return [
182
            'token'             => $intent->client_secret,
183
            'success_url'       => route('job.payment.success', [$payment]),
184
            'status_url'        => route('job.payment.status', [$payment])
185
        ];
186
    }
187
188
    /**
189
     * Successful bank transfer transaction. Redirect to the offer.
190
     *
191
     * @param Payment $payment
192
     * @return \Illuminate\Http\RedirectResponse
193
     */
194
    public function success(Payment $payment)
195
    {
196
        return redirect()
197
            ->to(UrlBuilder::job($payment->job))
198
            ->with('success', trans('payment.pending'));
199
    }
200
201
    /**
202
     * @param PaymentRepository $repository
203
     * @throws SignatureVerificationException
204
     */
205
    public function paymentStatus(PaymentRepository $repository)
206
    {
207
        Stripe::setApiKey(config('services.stripe.secret'));
208
209
        $payload = @file_get_contents('php://input');
210
211
        $event = Webhook::constructEvent($payload, $_SERVER['HTTP_STRIPE_SIGNATURE'], config('services.stripe.endpoint_secret'));
0 ignored issues
show
Bug introduced by
It seems like $payload can also be of type false; however, parameter $payload of Stripe\Webhook::constructEvent() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
        $event = Webhook::constructEvent(/** @scrutinizer ignore-type */ $payload, $_SERVER['HTTP_STRIPE_SIGNATURE'], config('services.stripe.endpoint_secret'));
Loading history...
212
213
        if ($event->type !== 'payment_intent.succeeded') {
214
            return;
215
        }
216
217
        $paymentIntent = $event->data->object;
218
        $payment = $repository->findOrFail($paymentIntent->metadata->id);
219
220
        event(new PaymentPaid($payment));
221
    }
222
223
    /**
224
     * @param Payment $payment
225
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
226
     */
227
    private function successfulTransaction(Payment $payment)
228
    {
229
        // boost job offer, send invoice and reindex
230
        event(new PaymentPaid($payment));
231
232
        session()->flash('success', trans('payment.success'));
233
234
        return response(UrlBuilder::job($payment->job, true), 201);
235
    }
236
237
    /**
238
     * Handle payment exception. Remove sensitive data before saving to logs and sending to sentry.
239
     *
240
     * @param \Exception $exception
241
     * @return \Illuminate\Http\RedirectResponse
242
     */
243
    private function handlePaymentException($exception)
244
    {
245
        $this->db->rollBack();
246
        // log error. sensitive data won't be saved (we removed them)
247
        logger()->error($exception);
248
249
        if (app()->environment('production')) {
0 ignored issues
show
introduced by
The method environment() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
        if (app()->/** @scrutinizer ignore-call */ environment('production')) {
Loading history...
250
            app('sentry')->captureException($exception);
0 ignored issues
show
Bug introduced by
The method captureException() does not exist on Illuminate\Contracts\Foundation\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

250
            app('sentry')->/** @scrutinizer ignore-call */ captureException($exception);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
251
        }
252
253
        throw $exception;
254
    }
255
}
256