startSubscriptionWithPaymentDetails()   B
last analyzed

Complexity

Conditions 10
Paths 45

Size

Total Lines 61
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 10
eloc 35
c 1
b 1
f 0
nc 45
nop 6
dl 0
loc 61
rs 7.6666

How to fix   Long Method    Complexity   

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
declare(strict_types=1);
4
5
/*
6
 * Copyright (C) 2020-2025 Iain Cambridge
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
10
 * the Free Software Foundation, either version 2.1 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
 */
21
22
namespace Parthenon\Billing\Controller;
23
24
use Obol\Exception\UnsupportedFunctionalityException;
25
use Parthenon\Billing\CustomerProviderInterface;
26
use Parthenon\Billing\Dto\StartSubscriptionDto;
27
use Parthenon\Billing\Enum\BillingChangeTiming;
28
use Parthenon\Billing\Exception\NoCustomerException;
29
use Parthenon\Billing\Exception\NoPaymentDetailsException;
30
use Parthenon\Billing\Exception\NoPlanFoundException;
31
use Parthenon\Billing\Exception\NoPlanPriceFoundException;
32
use Parthenon\Billing\Exception\PaymentFailureException;
33
use Parthenon\Billing\Plan\PlanManagerInterface;
34
use Parthenon\Billing\Repository\CustomerRepositoryInterface;
35
use Parthenon\Billing\Response\StartSubscriptionResponse;
36
use Parthenon\Billing\Subscription\SubscriptionManagerInterface;
37
use Parthenon\Billing\Subscription\SubscriptionProviderInterface;
38
use Parthenon\Common\Exception\NoEntityFoundException;
39
use Parthenon\Common\LoggerAwareTrait;
40
use Symfony\Component\HttpFoundation\JsonResponse;
41
use Symfony\Component\HttpFoundation\Request;
42
use Symfony\Component\HttpFoundation\Response;
43
use Symfony\Component\Routing\Attribute\Route;
44
use Symfony\Component\Serializer\SerializerInterface;
45
use Symfony\Component\Validator\Validator\ValidatorInterface;
46
47
class SubscriptionController
48
{
49
    use LoggerAwareTrait;
50
51
    #[Route('/billing/subscription/{subscriptionId}/cancel', name: 'parthenon_billing_subscription_cancel', methods: ['POST'])]
52
    public function cancelSubscription(
53
        Request $request,
54
        SubscriptionProviderInterface $subscriptionProvider,
55
        SubscriptionManagerInterface $subscriptionManager,
56
    ): Response {
57
        try {
58
            $subscription = $subscriptionProvider->getSubscription($request->get('subscriptionId'));
59
        } catch (NoEntityFoundException $E) {
60
            return new JsonResponse(status: JsonResponse::HTTP_NOT_FOUND);
61
        }
62
63
        $subscriptionManager->cancelSubscriptionAtEndOfCurrentPeriod($subscription);
64
65
        return new JsonResponse(status: JsonResponse::HTTP_ACCEPTED);
66
    }
67
68
    #[Route('/billing/subscription/start', name: 'parthenon_billing_subscription_start_with_payment_details', methods: ['POST'])]
69
    public function startSubscriptionWithPaymentDetails(
70
        Request $request,
71
        CustomerProviderInterface $customerProvider,
72
        SerializerInterface $serializer,
73
        CustomerRepositoryInterface $customerRepository,
74
        ValidatorInterface $validator,
75
        SubscriptionManagerInterface $subscriptionManager,
76
    ): Response {
77
        $this->getLogger()->info('Starting the subscription');
78
79
        try {
80
            $customer = $customerProvider->getCurrentCustomer();
81
        } catch (NoCustomerException $exception) {
82
            $this->getLogger()->error('No customer found when starting subscription with payment details - probable misconfigured firewall.');
83
84
            return new JsonResponse(StartSubscriptionResponse::createGeneralError(), JsonResponse::HTTP_BAD_REQUEST);
85
        }
86
87
        try {
88
            /** @var StartSubscriptionDto $subscriptionDto */
89
            $subscriptionDto = $serializer->deserialize($request->getContent(), StartSubscriptionDto::class, 'json');
90
91
            $errors = $validator->validate($subscriptionDto);
92
93
            if (count($errors) > 0) {
94
                return new JsonResponse(StartSubscriptionResponse::createInvalidRequestResponse($errors), JsonResponse::HTTP_BAD_REQUEST);
95
            }
96
97
            $subscription = $subscriptionManager->startSubscriptionWithDto($customer, $subscriptionDto);
98
99
            $customerRepository->save($customer);
100
        } catch (NoEntityFoundException $exception) {
101
            return new JsonResponse(StartSubscriptionResponse::createGeneralError(), JsonResponse::HTTP_BAD_REQUEST);
102
        } catch (NoPlanPriceFoundException $exception) {
103
            $this->getLogger()->warning('No price plan found');
104
105
            return new JsonResponse(StartSubscriptionResponse::createPlanPriceNotFound(), JsonResponse::HTTP_BAD_REQUEST);
106
        } catch (NoPlanFoundException $exception) {
107
            $this->getLogger()->warning('No plan found');
108
109
            return new JsonResponse(StartSubscriptionResponse::createPlanNotFound(), JsonResponse::HTTP_BAD_REQUEST);
110
        } catch (PaymentFailureException $exception) {
111
            $this->getLogger()->warning('Payment failed so subscription was not created');
112
113
            return new JsonResponse(StartSubscriptionResponse::createPaymentFailed($exception->getChargeFailureReason()), JsonResponse::HTTP_BAD_REQUEST);
114
        } catch (NoPaymentDetailsException $exception) {
115
            $this->getLogger()->warning('Customer does not have payment details so subscription was not created');
116
117
            return new JsonResponse(StartSubscriptionResponse::createNoPaymentDetails(), JsonResponse::HTTP_BAD_REQUEST);
118
        } catch (UnsupportedFunctionalityException $exception) {
119
            $this->getLogger()->error('Payment provider does not support payment details');
120
121
            return new JsonResponse(StartSubscriptionResponse::createUnsupportedPaymentProvider(), JsonResponse::HTTP_BAD_REQUEST);
122
        } catch (\Throwable $t) {
123
            $this->getLogger()->error('Unknown error while starting a subscription');
124
125
            throw $t;
126
        }
127
128
        return new JsonResponse(StartSubscriptionResponse::createSuccessResponse($subscription), JsonResponse::HTTP_CREATED);
129
    }
130
131
    #[Route('/billing/subscription/{id}/change/{planName}/{schedule}/{currency}', name: 'parthenon_billing_subscription_change', methods: ['POST'])]
132
    public function changeSubscription(
133
        Request $request,
134
        SubscriptionProviderInterface $subscriptionProvider,
135
        SubscriptionManagerInterface $subscriptionManager,
136
        PlanManagerInterface $planManager,
137
    ): Response {
138
        $subscriptions = $subscriptionProvider->getSubscription($request->get('id'));
139
140
        $schedule = $request->get('schedule');
141
        $currency = $request->get('currency');
142
        $plan = $planManager->getPlanByName($request->get('planName'));
143
        $price = $plan->getPriceForPaymentSchedule($schedule, $currency);
144
145
        $subscriptionManager->changeSubscriptionPlan($subscriptions, $plan, $price, BillingChangeTiming::INSTANTLY);
146
147
        return new JsonResponse(['success' => true], JsonResponse::HTTP_CREATED);
148
    }
149
}
150