Completed
Push — gocardless-upgrade ( ab99fa )
by Arthur
02:58
created

GoCardlessWebhookController::paymentFailed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 1
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
    public function __construct(GoCardlessHelper $goCardless, PaymentRepository $paymentRepository, SubscriptionChargeRepository $subscriptionChargeRepository)
29
    {
30
        $this->goCardless = $goCardless;
31
        $this->paymentRepository = $paymentRepository;
32
        $this->subscriptionChargeRepository = $subscriptionChargeRepository;
33
    }
34
35
    public function receive()
36
    {
37
        $request = \Request::instance();
38
        $webhookData = $request->getContent();
39
        $signature = $request->header('Webhook-Signature');
40
41
        $hash = hash_hmac('sha256', $webhookData, 'Z4Zw1Bna_aoHo1Ifg-ZyU3NRVOjcQSuQP10zWHKg');
42
43
        if ($signature != $hash) {
44
            return \Response::make('', 403);
45
        }
46
47
        $webhookData = json_decode($webhookData, true);
48
49
        foreach ($webhookData['events'] as $event) {
50
            $parser = new \BB\Services\Payment\GoCardlessWebhookParser();
51
            $parser->parseResponse($event);
52
53
            switch ($parser->getResourceType()) {
54
                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...
55
56
                    switch ($parser->getAction()) {
57
                        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...
58
59
                            $this->processNewPayment($event);
60
61
                            break;
62
                        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...
63
64
                            break;
65
                        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...
66
67
                            $this->processPaidBills($event);
68
69
                            break;
70
                        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...
71
72
                            break;
73
                        case 'failed':
74
                        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...
75
76
                            $this->paymentFailed($event);
77
78
                            break;
79
                        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...
80
81
                            \Log::info('GoCardless payment event. Action: ' . $parser->getAction() . '. Data: ' . json_encode($event));
82
                    }
83
84
                    break;
85
                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...
86
87
                    switch ($parser->getAction()) {
88
                        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...
89
90
                            $this->cancelPreAuth($event);
91
92
                            break;
93
                        default:
94
                    }
95
96
                    break;
97
                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...
98
99
                    switch ($parser->getAction()) {
100
                        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...
101
102
                            $this->cancelSubscriptions($event);
103
104
                            break;
105
                        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...
106
107
                            $this->processNewSubscriptionPayment($event);
108
109
                            break;
110
                        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...
111
112
                            \Log::info('GoCardless subscription event. Action: ' . $parser->getAction() . '. Data: ' . json_encode($event));
113
                    }
114
115
                    break;
116
            }
117
        }
118
119
        return \Response::make('Success', 200);
120
    }
121
122
123
    /**
124
     * A Bill has been created, these will always start within the system except for subscription payments
125
     *
126
     * @param array $bill
127
     */
128
    private function processNewPayment(array $bill)
129
    {
130
        \Log::info('New payment notification. ' . json_encode($bill));
131
    }
132
133
134
    /**
135
     * A Bill has been created, these will always start within the system except for subscription payments
136
     *
137
     * @param array $bill
138
     */
139
    private function processNewSubscriptionPayment(array $bill)
140
    {
141
        // Lookup the payment from the API
142
        $payment = $this->goCardless->getPayment($bill['links']['payment']);
143
144
        try {
145
146
            //Locate the user through their subscription id
147
            $user = User::where('payment_method', 'gocardless')->where('subscription_id', $bill['links']['subscription'])->first();
148
149
            if ( ! $user) {
150
                \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...
151
152
                return;
153
            }
154
155
            $amount = ($payment->amount * 1) / 100;
156
            $this->paymentRepository->recordSubscriptionPayment($user->id, 'gocardless', $bill['links']['payment'], $amount, $payment->status);
157
158
        } catch (\Exception $e) {
159
            \Log::error($e);
160
        }
161
    }
162
163
164
    private function processPaidBills(array $bill)
165
    {
166
        //When a bill is paid update the status on the local record and the connected sub charge (if there is one)
167
168
        $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['links']['payment']);
169
        if ($existingPayment) {
170
171
            if (isset($bill['paid_at'])) {
172
                $paymentDate = new Carbon($bill['paid_at']);
173
            } else {
174
                $paymentDate = new Carbon();
175
            }
176
177
            $this->paymentRepository->markPaymentPaid($existingPayment->id, $paymentDate);
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
179
        } else {
180
            \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...
181
        }
182
    }
183
184
    /**
185
     * @param array $bill
186
     */
187
    private function paymentFailed(array $bill)
188
    {
189
        $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['links']['payment']);
190
        $payment = $this->goCardless->getPayment($bill['links']['payment']);
191
        if ($existingPayment) {
192
            $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...
193
        } else {
194
            \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...
195
        }
196
197
    }
198
199
    /**
200
     * @param string $action
201
     */
202
    private function processBills($action, array $bills)
203
    {
204
        foreach ($bills as $bill) {
205
            $existingPayment = $this->paymentRepository->getPaymentBySourceId($bill['id']);
206
            if ($existingPayment) {
207
                if (($bill['status'] == 'pending') && ($action == 'resubmission_requested')) {
208
                    //Failed payment is being retried
209
                    $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...
210
                    if ($subCharge) {
211
                        if ($subCharge->amount == $bill['amount']) {
212
                            $this->subscriptionChargeRepository->markChargeAsProcessing($subCharge->id);
213
                        } else {
214
                            //@TODO: Handle partial payments
215
                            \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...
216
                        }
217
                    }
218
                } elseif ($bill['status'] == 'refunded') {
219
                    //Payment refunded
220
                    //Update the payment record and possible the user record
221
                }
222
            } else {
223
                \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...
224
            }
225
        }
226
227
    }
228
229
    /**
230
     * @param array $preAuth
231
     */
232
    private function cancelPreAuth($preAuth)
233
    {
234
        /** @var User $user */
235
        $user = User::where('payment_method', 'gocardless-variable')->where('subscription_id', $preAuth['links']['mandate'])->first();
236
        if ($user) {
237
            $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...
238
        }
239
    }
240
241
242
    private function cancelSubscriptions($subscription)
243
    {
244
        //Make sure our local record is correct
245
        /** @var User $user */
246
        $user = User::where('payment_method', 'gocardless')->where('subscription_id', $subscription['links']['subscription'])->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