Completed
Push — master ( d0a149...d07a9e )
by Arthur
03:35
created

GoCardlessWebhookController::processBills()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 14
nc 7
nop 2
1
<?php namespace BB\Http\Controllers;
2
3
4
use BB\Entities\Payment;
5
use BB\Entities\User;
6
use BB\Helpers\GoCardlessHelper;
7
use BB\Repo\PaymentRepository;
8
use BB\Repo\SubscriptionChargeRepository;
9
use \Carbon\Carbon;
10
use Illuminate\Http\Request;
11
12
class GoCardlessWebhookController extends Controller
13
{
14
15
    /**
16
     * @var PaymentRepository
17
     */
18
    private $paymentRepository;
19
    /**
20
     * @var SubscriptionChargeRepository
21
     */
22
    private $subscriptionChargeRepository;
23
    /**
24
     * @var GoCardlessHelper
25
     */
26
    private $goCardless;
27
    /**
28
     * @var \BB\Repo\UserRepository
29
     */
30
    private $userRepository;
31
32 View Code Duplication
    public function __construct(GoCardlessHelper $goCardless, PaymentRepository $paymentRepository, SubscriptionChargeRepository $subscriptionChargeRepository, \BB\Repo\UserRepository $userRepository)
33
    {
34
        $this->goCardless = $goCardless;
35
        $this->paymentRepository = $paymentRepository;
36
        $this->subscriptionChargeRepository = $subscriptionChargeRepository;
37
        $this->userRepository = $userRepository;
38
    }
39
40
    public function receive()
41
    {
42
        $request = \Request::instance();
43
        $webhookData = $request->getContent();
44
        $signature = $request->header('Webhook-Signature');
45
46
        $hash = hash_hmac('sha256', $webhookData, env('NEW_GOCARDLESS_WEBHOOK_SECRET'));
47
48
        if ($signature != $hash) {
49
            return \Response::make('', 403);
50
        }
51
52
        $webhookData = json_decode($webhookData, true);
53
54
        foreach ($webhookData['events'] as $event) {
55
            $parser = new \BB\Services\Payment\GoCardlessWebhookParser();
56
            $parser->parseResponse($event);
57
58
            switch ($parser->getResourceType()) {
59
                case 'payments':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
60
61
                    switch ($parser->getAction()) {
62
                        case 'created':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
63
64
                            $this->processNewPayment($event);
65
66
                            break;
67
                        case 'submitted':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
68
69
                            $this->processSubmittedPayment($event);
70
71
                            break;
72
                        case 'confirmed':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
73
74
                            $this->processPaidBills($event);
75
76
                            break;
77
                        case 'paid_out':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
78
79
                            break;
80
                        case 'failed':
81
                        case 'cancelled':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
82
83
                            $this->paymentFailed($event);
84
85
                            break;
86
                        default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
87
88
                            \Log::info('GoCardless payment event. Action: ' . $parser->getAction() . '. Data: ' . json_encode($event));
89
                    }
90
91
                    break;
92
                case 'mandates':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
93
94
                    switch ($parser->getAction()) {
95
                        case 'cancelled':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
96
97
                            $this->cancelPreAuth($event);
98
99
                            break;
100
                        default:
101
                    }
102
103
                    break;
104
                case 'subscriptions':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
105
106
                    switch ($parser->getAction()) {
107
                        case 'cancelled':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
108
109
                            $this->cancelSubscriptions($event);
110
111
                            break;
112
                        case 'payment_created':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
113
114
                            $this->processNewSubscriptionPayment($event);
115
116
                            break;
117
                        default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
118
119
                            \Log::info('GoCardless subscription event. Action: ' . $parser->getAction() . '. Data: ' . json_encode($event));
120
                    }
121
122
                    break;
123
            }
124
        }
125
126
        return \Response::make('Success', 200);
127
    }
128
129
130
    /**
131
     * A Bill has been created, these will always start within the system except for subscription payments
132
     *
133
     * @param array $bill
134
     */
135
    private function processNewPayment(array $bill)
136
    {
137
        \Log::info('New payment notification. ' . json_encode($bill));
138
    }
139
140
141
    /**
142
     * A Bill has been created, these will always start within the system except for subscription payments
143
     *
144
     * @param array $bill
145
     */
146
    private function processNewSubscriptionPayment(array $bill)
147
    {
148
        // Lookup the payment from the API
149
        $payment = $this->goCardless->getPayment($bill['links']['payment']);
150
151
        try {
152
153
            //Locate the user through their subscription id
154
            $user = User::where('subscription_id', $bill['links']['subscription'])->first();
155
156
            if ( ! $user) {
157
                \Log::warning("GoCardless new sub payment notification for unmatched user. Bill ID: " . $bill['links']['payment']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal GoCardless new sub payme...matched user. Bill ID: does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
158
159
                return;
160
            }
161
162
            $amount = ($payment->amount * 1) / 100;
163
            $this->paymentRepository->recordSubscriptionPayment($user->id, 'gocardless', $bill['links']['payment'], $amount, $payment->status);
164
165
        } catch (\Exception $e) {
166
            \Log::error($e);
167
        }
168
    }
169
170
171 View Code Duplication
    private function processSubmittedPayment(array $bill)
172
    {
173
        //When a bill is submitted to the bank update the status on the local record
174
175
        $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['links']['payment']);
176
        if ($existingPayment) {
177
            $this->paymentRepository->markPaymentPending($existingPayment->id);
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<BB\Entities\Payment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
178
        } else {
179
            \Log::info("GoCardless Webhook received for unknown payment: " . $bill['links']['payment']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal GoCardless Webhook received for unknown payment: does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
180
        }
181
    }
182
183 View Code Duplication
    private function processPaidBills(array $bill)
184
    {
185
        //When a bill is paid update the status on the local record and the connected sub charge (if there is one)
186
187
        $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['links']['payment']);
188
        if ($existingPayment) {
189
            $this->paymentRepository->markPaymentPaid($existingPayment->id, Carbon::now());
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<BB\Entities\Payment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
190
        } else {
191
            \Log::info("GoCardless Webhook received for unknown payment: " . $bill['links']['payment']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal GoCardless Webhook received for unknown payment: does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
192
        }
193
    }
194
195
    /**
196
     * @param array $bill
197
     */
198
    private function paymentFailed(array $bill)
199
    {
200
        $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['links']['payment']);
201
        $payment = $this->goCardless->getPayment($bill['links']['payment']);
202
        if ($existingPayment) {
203
            $this->paymentRepository->recordPaymentFailure($existingPayment->id, $payment->status);
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<BB\Entities\Payment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
204
        } else {
205
            \Log::info("GoCardless Webhook received for unknown payment: " . $bill['links']['payment']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal GoCardless Webhook received for unknown payment: does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
206
        }
207
208
    }
209
210
    /**
211
     * @param string $action
212
     */
213
    private function processBills($action, array $bills)
214
    {
215
        foreach ($bills as $bill) {
216
            $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['id']);
217
            if ($existingPayment) {
218
                if (($bill['status'] == 'pending') && ($action == 'resubmission_requested')) {
219
                    //Failed payment is being retried
220
                    $subCharge = $this->subscriptionChargeRepository->getById($existingPayment->reference);
0 ignored issues
show
Documentation introduced by
The property reference does not exist on object<BB\Entities\Payment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
221
                    if ($subCharge) {
222
                        if ($subCharge->amount == $bill['amount']) {
223
                            $this->subscriptionChargeRepository->markChargeAsProcessing($subCharge->id);
224
                        } else {
225
                            //@TODO: Handle partial payments
226
                            \Log::warning("Sub charge handling - gocardless partial payment");
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal Sub charge handling - gocardless partial payment does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
227
                        }
228
                    }
229
                } elseif ($bill['status'] == 'refunded') {
230
                    //Payment refunded
231
                    //Update the payment record and possible the user record
232
                }
233
            } else {
234
                \Log::info("GoCardless Webhook received for unknown payment: " . $bill['id']);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal GoCardless Webhook received for unknown payment: does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
235
            }
236
        }
237
238
    }
239
240
    /**
241
     * @param array $preAuth
242
     */
243
    private function cancelPreAuth($preAuth)
244
    {
245
        /** @var User $user */
246
        $user = User::where('mandate_id', $preAuth['links']['mandate'])->first();
247
        if ($user) {
248
            $user->cancelSubscription();
0 ignored issues
show
Deprecated Code introduced by
The method BB\Entities\User::cancelSubscription() has been deprecated.

This method has been deprecated.

Loading history...
249
        }
250
    }
251
252
253
    private function cancelSubscriptions($subscription)
254
    {
255
        //Make sure our local record is correct
256
        /** @var User $user */
257
        $user = User::where('subscription_id', $subscription['links']['subscription'])->first();
258
        if ($user) {
259
            if ($user->payment_method == 'gocardless') {
260
                $user->cancelSubscription();
0 ignored issues
show
Deprecated Code introduced by
The method BB\Entities\User::cancelSubscription() has been deprecated.

This method has been deprecated.

Loading history...
261
            } else {
262
                // The user probably has a new subscription alongside an existing mandate,
263
                // we don't want to touch that so just remove the subscription details
264
                $this->userRepository->recordGoCardlessSubscription($user->id, null);
265
            }
266
        }
267
    }
268
269
}
270