Passed
Push — master ( ec36ee...74df59 )
by Darko
10:08 queued 04:04
created

BtcPaymentController::btcPayCallback()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 22
c 2
b 1
f 0
dl 0
loc 34
rs 8.6346
cc 7
nc 7
nop 1
1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Models\User;
6
use Blacklight\libraries\Geary;
7
use Illuminate\Http\Request;
8
use Illuminate\Http\Response;
9
use Illuminate\Support\Facades\Log;
10
use Spatie\Permission\Models\Role;
11
12
class BtcPaymentController extends BasePageController
13
{
14
    /**
15
     * @return \Illuminate\Http\RedirectResponse|void
16
     *
17
     * @throws \Exception
18
     */
19
    public function show(Request $request)
20
    {
21
        $this->setPreferences();
22
        $gateway_id = config('settings.mycelium_gateway_id');
23
        $gateway_secret = config('settings.mycelium_gateway_secret');
24
25
        $action = $request->input('action') ?? 'view';
26
        $donation = Role::query()->where('donation', '>', 0)->get(['id', 'name', 'donation', 'addyears']);
27
        $this->smarty->assign('donation', $donation);
28
29
        switch ($action) {
30
            case 'submit':
31
                $price = $request->input('price');
32
                $role = $request->input('role');
33
                $roleName = $request->input('rolename');
34
                $addYears = $request->input('addyears');
35
                $data = ['user_id' => $this->userdata->id, 'username' => $this->userdata->username, 'price' => $price, 'role' => $role, 'rolename' => $roleName, 'addyears' => $addYears];
36
                $keychain_id = random_int(0, 19);
37
                $callback_data = json_encode($data);
38
39
                $geary = new Geary($gateway_id, $gateway_secret);
40
                $order = $geary->create_order($price, $keychain_id, $callback_data);
41
42
                if ($order->payment_id) {
43
                    // Redirect to a payment gateway
44
                    $url = 'https://gateway.gear.mycelium.com/pay/'.$order->payment_id;
45
46
                    return redirect()->to($url);
47
                }
48
                break;
49
            case 'view':
50
            default:
51
                $userId = $this->userdata->id;
0 ignored issues
show
Unused Code introduced by
The assignment to $userId is dead and can be removed.
Loading history...
52
                break;
53
        }
54
55
        $title = 'Become a supporter';
56
        $meta_title = 'Become a supporter';
57
        $meta_description = 'Become a supporter';
58
59
        $content = $this->smarty->fetch('btc_payment.tpl');
60
61
        $this->smarty->assign(compact('content', 'meta_title', 'title', 'meta_description'));
62
        $this->pagerender();
63
    }
64
65
    /**
66
     * Callback data from Mycelium Gear.
67
     */
68
    public function callback(): void
69
    {
70
        $gateway_id = config('settings.mycelium_gateway_id');
71
        $gateway_secret = config('settings.mycelium_gateway_secret');
72
73
        $geary = new Geary($gateway_id, $gateway_secret);
74
        $order = $geary->check_order_callback();
75
76
        // Order status was received
77
        if ($order !== false) {
78
            $callback_data = json_decode($order['callback_data'], true);
79
            $newRole = $callback_data['role'];
80
            $amount = $callback_data['price'];
0 ignored issues
show
Unused Code introduced by
The assignment to $amount is dead and can be removed.
Loading history...
81
            $addYear = $callback_data['addyears'];
82
            // If order was paid in full (2) or overpaid (4)
83
            if ((int) $order['status'] === 2 || (int) $order['status'] === 4) {
84
                User::updateUserRole($callback_data['user_id'], $newRole);
85
                User::updateUserRoleChangeDate($callback_data['user_id'], null, $addYear);
86
            }
87
        }
88
    }
89
90
    public function btcPayCallback(Request $request): Response
91
    {
92
        $hashCheck = 'sha256='.hash_hmac('sha256', $request->getContent(), config('nntmux.btcpay_webhook_secret'));
93
        if ($hashCheck !== $request->header('btcpay-sig')) {
94
            Log::error('BTCPay webhook hash check failed: '.$request->header('btcpay-sig'));
0 ignored issues
show
Bug introduced by
Are you sure $request->header('btcpay-sig') of type array|null|string can be used in concatenation? ( Ignorable by Annotation )

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

94
            Log::error('BTCPay webhook hash check failed: './** @scrutinizer ignore-type */ $request->header('btcpay-sig'));
Loading history...
95
96
            return response('Not Found', 404);
97
        }
98
        $payload = json_decode($request->getContent(), true);
0 ignored issues
show
Bug introduced by
It seems like $request->getContent() can also be of type resource; however, parameter $json of json_decode() 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

98
        $payload = json_decode(/** @scrutinizer ignore-type */ $request->getContent(), true);
Loading history...
99
        // We have received a payment for an invoice and user should be upgraded to a paid plan based on order
100
        if ($payload['type'] === 'InvoiceReceivedPayment' || $payload['type'] === 'InvoicePaymentSettled') {
101
            preg_match('/(?P<role>\w+(\s\+\+)?)[\s](?P<addYears>\d+)/i', $payload['metadata']['itemDesc'], $matches);
102
            if (empty($matches)) {
103
                Log::error('Could not parse BTCPay webhook: '.$payload['metadata']['itemDesc']);
104
                preg_match('/(?P<role>\w+(\s\+\+)?)[\s](?P<addYears>\d+)/i', $payload['metadata']['itemCode'], $matches);
105
                if (empty($matches)) {
106
                    Log::error('Could not parse BTCPay webhook: '.$payload['metadata']['itemDesc']);
107
108
                    return response('Not Found', 404);
109
                }
110
            }
111
            $user = User::query()->where('email', '=', $payload['metadata']['buyerEmail'])->first();
112
            if ($user) {
113
                User::updateUserRole($user->id, $matches['role']);
114
                User::updateUserRoleChangeDate($user->id, null, $matches['addYears']);
115
                Log::info('User upgraded to '.$matches['role'].' for BTCPay webhook: '.$payload['metadata']['buyerEmail']);
116
            } else {
117
                Log::error('User not found for BTCPay webhook: '.$payload['metadata']['buyerEmail']);
118
119
                return response('Not Found', 404);
120
            }
121
        }
122
123
        return response('OK', 200);
124
    }
125
}
126