Passed
Push — master ( 9491b2...270076 )
by Adam
11:03
created

PaymentController::makePayment()   A

Complexity

Conditions 5
Paths 36

Size

Total Lines 60
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 36
nc 36
nop 2
dl 0
loc 60
rs 9.0328
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\Invoice\VatRateCalculator;
17
use Coyote\Services\UrlBuilder;
18
use Coyote\Services\Invoice\Generator as InvoiceGenerator;
19
use Illuminate\Database\Connection as Db;
20
use Illuminate\Http\Request;
21
use Stripe\Exception\SignatureVerificationException;
22
use Stripe\PaymentIntent;
23
use Stripe\Stripe;
24
use Stripe\Webhook;
25
26
class PaymentController extends Controller
27
{
28
    /**
29
     * @var InvoiceGenerator
30
     */
31
    private $invoice;
32
33
    /**
34
     * @var CountryRepository
35
     */
36
    private $country;
37
38
    /**
39
     * @var Db
40
     */
41
    private $db;
42
43
    /**
44
     * @var CouponRepository
45
     */
46
    private $coupon;
47
48
    /**
49
     * @var array
50
     */
51
    private $vatRates;
52
53
    /**
54
     * @param InvoiceGenerator $invoice
55
     * @param Db $db
56
     * @param CountryRepository $country
57
     * @param CouponRepository $coupon
58
     */
59
    public function __construct(InvoiceGenerator $invoice, Db $db, CountryRepository $country, CouponRepository $coupon)
60
    {
61
        parent::__construct();
62
63
        $this->invoice = $invoice;
64
        $this->db = $db;
65
        $this->country = $country;
66
        $this->coupon = $coupon;
67
68
        $this->middleware(
69
            function (Request $request, $next) {
70
                /** @var \Coyote\Payment $payment */
71
                $payment = $request->route('payment');
72
73
                if ($payment instanceof Payment) {
0 ignored issues
show
introduced by
$payment is always a sub-type of Coyote\Payment.
Loading history...
74
                    abort_if($payment->status_id === Payment::PAID, 404);
75
                }
76
77
                return $next($request);
78
            },
79
            ['except' => 'success']
80
        );
81
82
        $this->breadcrumb->push('Praca', route('job.home'));
83
        $this->vatRates = $this->country->vatRatesList();
84
    }
85
86
    /**
87
     * @param \Coyote\Payment $payment
88
     * @return \Illuminate\View\View
89
     */
90
    public function index(Payment $payment)
91
    {
92
        $this->breadcrumb->push($payment->job->title, UrlBuilder::job($payment->job));
93
        $this->breadcrumb->push('Płatność');
94
95
        $firm = $payment->job->firm ?? new Firm();
96
97
        if (empty($firm->country_id)) {
98
            $geoIp = app('geo-ip');
99
            $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

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

156
                array_merge(/** @scrutinizer ignore-type */ $invoice, ['user_id' => $this->auth->id]),
Loading history...
157
                $payment,
158
                $calculator
159
            );
160
161
            // associate invoice with payment
162
            $payment->invoice()->associate($invoice);
163
164
            $payment->save();
165
            $this->db->commit();
166
        } catch (PaymentFailedException $e) {
167
            $this->handlePaymentException($e);
168
        } catch (\Exception $e) {
169
            $this->handlePaymentException($e);
170
        }
171
172
        if (!$calculator->grossPrice()) {
173
            return $this->successfulTransaction($payment);
174
        }
175
176
        $intent = PaymentIntent::create([
177
            'amount'                => $payment->invoice->grossPrice() * 100,
178
            'currency'              => strtolower($payment->invoice->currency->name),
179
            'metadata'              => ['id' => $payment->id],
180
            'payment_method_types'  => [$request->input('payment_method')]
181
        ]);
182
183
        return [
184
            'token'             => $intent->client_secret,
185
            'success_url'       => route('job.payment.success', [$payment]),
186
            'status_url'        => route('job.payment.status', [$payment])
187
        ];
188
    }
189
190
    /**
191
     * Successful bank transfer transaction. Redirect to the offer.
192
     *
193
     * @param Payment $payment
194
     * @return \Illuminate\Http\RedirectResponse
195
     */
196
    public function success(Payment $payment)
197
    {
198
        return redirect()
199
            ->to(UrlBuilder::job($payment->job))
200
            ->with('success', trans('payment.pending'));
201
    }
202
203
    /**
204
     * @param PaymentRepository $repository
205
     * @throws SignatureVerificationException
206
     */
207
    public function paymentStatus(PaymentRepository $repository)
208
    {
209
        Stripe::setApiKey(config('services.stripe.secret'));
210
211
        $payload = @file_get_contents('php://input');
212
213
        $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

213
        $event = Webhook::constructEvent(/** @scrutinizer ignore-type */ $payload, $_SERVER['HTTP_STRIPE_SIGNATURE'], config('services.stripe.endpoint_secret'));
Loading history...
214
215
        if ($event->type !== 'payment_intent.succeeded') {
216
            return;
217
        }
218
219
        $paymentIntent = $event->data->object;
220
        $payment = $repository->findOrFail($paymentIntent->metadata->id);
221
222
        event(new PaymentPaid($payment));
223
    }
224
225
    /**
226
     * @param Payment $payment
227
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
228
     */
229
    private function successfulTransaction(Payment $payment)
230
    {
231
        // boost job offer, send invoice and reindex
232
        event(new PaymentPaid($payment));
233
234
        session()->flash('success', trans('payment.success'));
235
236
        return response(UrlBuilder::job($payment->job, true), 201);
237
    }
238
239
    /**
240
     * Handle payment exception. Remove sensitive data before saving to logs and sending to sentry.
241
     *
242
     * @param \Exception $exception
243
     * @return \Illuminate\Http\RedirectResponse
244
     */
245
    private function handlePaymentException($exception)
246
    {
247
        $this->db->rollBack();
248
        // log error. sensitive data won't be saved (we removed them)
249
        logger()->error($exception);
250
251
        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

251
        if (app()->/** @scrutinizer ignore-call */ environment('production')) {
Loading history...
252
            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

252
            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...
253
        }
254
255
        throw $exception;
256
    }
257
}
258