Passed
Push — main ( b448d4...f7598c )
by Iain
04:19
created

SubscriptionManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 10
dl 0
loc 12
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright Iain Cambridge 2020-2023.
7
 *
8
 * Use of this software is governed by the Business Source License included in the LICENSE file and at https://getparthenon.com/docs/next/license.
9
 *
10
 * Change Date: TBD ( 3 years after 2.2.0 release )
11
 *
12
 * On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
13
 */
14
15
namespace Parthenon\Billing\Subscription;
16
17
use Obol\Model\CancelSubscription;
18
use Obol\Model\Enum\RefundType;
19
use Obol\Provider\ProviderInterface;
20
use Parthenon\Billing\Dto\StartSubscriptionDto;
21
use Parthenon\Billing\Entity\CustomerInterface;
22
use Parthenon\Billing\Entity\EmbeddedSubscription;
23
use Parthenon\Billing\Entity\PaymentDetails;
24
use Parthenon\Billing\Entity\Price;
25
use Parthenon\Billing\Entity\Subscription;
26
use Parthenon\Billing\Entity\SubscriptionPlan;
27
use Parthenon\Billing\Exception\SubscriptionCreationException;
28
use Parthenon\Billing\Obol\BillingDetailsFactoryInterface;
29
use Parthenon\Billing\Obol\PaymentFactoryInterface;
30
use Parthenon\Billing\Obol\SubscriptionFactoryInterface;
31
use Parthenon\Billing\Plan\Plan;
32
use Parthenon\Billing\Plan\PlanManagerInterface;
33
use Parthenon\Billing\Plan\PlanPrice;
34
use Parthenon\Billing\Repository\PaymentDetailsRepositoryInterface;
35
use Parthenon\Billing\Repository\PaymentRepositoryInterface;
36
use Parthenon\Billing\Repository\PriceRepositoryInterface;
37
use Parthenon\Billing\Repository\SubscriptionPlanRepositoryInterface;
38
use Parthenon\Billing\Repository\SubscriptionRepositoryInterface;
39
40
final class SubscriptionManager implements SubscriptionManagerInterface
41
{
42
    public function __construct(
43
        private PaymentDetailsRepositoryInterface $paymentDetailsRepository,
44
        private ProviderInterface $provider,
45
        private BillingDetailsFactoryInterface $billingDetailsFactory,
46
        private PaymentFactoryInterface $paymentFactory,
47
        private SubscriptionFactoryInterface $subscriptionFactory,
48
        private PaymentRepositoryInterface $paymentRepository,
49
        private PlanManagerInterface $planManager,
50
        private SubscriptionPlanRepositoryInterface $subscriptionPlanRepository,
51
        private PriceRepositoryInterface $priceRepository,
52
        private SubscriptionRepositoryInterface $subscriptionRepository
53
    ) {
54
    }
55
56
    public function startSubscription(CustomerInterface $customer, SubscriptionPlan|Plan $plan, Price|PlanPrice $planPrice, PaymentDetails $paymentDetails, int $seatNumbers): Subscription
57
    {
58
        $billingDetails = $this->billingDetailsFactory->createFromCustomerAndPaymentDetails($customer, $paymentDetails);
59
        $obolSubscription = $this->subscriptionFactory->createSubscription($billingDetails, $planPrice, $seatNumbers);
0 ignored issues
show
Bug introduced by
It seems like $planPrice can also be of type Parthenon\Billing\Entity\Price; however, parameter $planPrice of Parthenon\Billing\Obol\S...e::createSubscription() does only seem to accept Parthenon\Billing\Plan\PlanPrice, 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

59
        $obolSubscription = $this->subscriptionFactory->createSubscription($billingDetails, /** @scrutinizer ignore-type */ $planPrice, $seatNumbers);
Loading history...
60
        $obolSubscription->setStoredPaymentReference($paymentDetails->getStoredPaymentReference());
61
62
        if ($this->subscriptionRepository->hasActiveSubscription($customer)) {
63
            $subscription = $this->subscriptionRepository->getOneActiveSubscriptionForCustomer($customer);
64
65
            if ($subscription->getCurrency() != $planPrice->getCurrency()) {
66
                throw new SubscriptionCreationException("Can't add a child subscription for a different currency");
67
            }
68
69
            $obolSubscription->setParentReference($subscription->getMainExternalReference());
70
        }
71
72
        $subscriptionCreationResponse = $this->provider->payments()->startSubscription($obolSubscription);
73
        if ($subscriptionCreationResponse->hasCustomerCreation()) {
74
            $customer->setPaymentProviderDetailsUrl($subscriptionCreationResponse->getCustomerCreation()->getDetailsUrl());
75
            $customer->setExternalCustomerReference($subscriptionCreationResponse->getCustomerCreation()->getReference());
76
        }
77
78
        $subscription = new Subscription();
79
        $subscription->setPlanName($plan->getName());
80
        $subscription->setPaymentSchedule($planPrice->getSchedule());
0 ignored issues
show
Bug introduced by
It seems like $planPrice->getSchedule() can also be of type null; however, parameter $paymentSchedule of Parthenon\Billing\Entity...n::setPaymentSchedule() 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

80
        $subscription->setPaymentSchedule(/** @scrutinizer ignore-type */ $planPrice->getSchedule());
Loading history...
81
        $subscription->setActive(true);
82
        $subscription->setMoneyAmount($subscriptionCreationResponse->getPaymentDetails()->getAmount());
83
        $subscription->setStatus(\Parthenon\Billing\Entity\EmbeddedSubscription::STATUS_ACTIVE);
84
        $subscription->setMainExternalReference($subscriptionCreationResponse->getSubscriptionId());
85
        $subscription->setChildExternalReference($subscriptionCreationResponse->getLineId());
86
        $subscription->setSeats($seatNumbers);
87
        $subscription->setCreatedAt(new \DateTime());
88
        $subscription->setUpdatedAt(new \DateTime());
89
        $subscription->setStartOfCurrentPeriod(new \DateTime());
90
        $subscription->setValidUntil($subscriptionCreationResponse->getBilledUntil());
91
        $subscription->setCustomer($customer);
92
        $subscription->setMainExternalReferenceDetailsUrl($subscriptionCreationResponse->getDetailsUrl());
93
        $subscription->setPaymentExternalReference($subscriptionCreationResponse->getPaymentDetails()->getStoredPaymentReference());
94
95
        if ($plan instanceof SubscriptionPlan) {
96
            $subscription->setSubscriptionPlan($plan);
97
        } elseif ($plan->hasEntityId()) {
98
            $subscriptionPlan = $this->subscriptionPlanRepository->findById($plan->getEntityId());
99
            $subscription->setSubscriptionPlan($subscriptionPlan);
100
        }
101
102
        if ($planPrice instanceof Price) {
103
            $subscription->setPrice($planPrice);
104
        } elseif ($planPrice->hasEntityId()) {
105
            $price = $this->priceRepository->findById($planPrice->getEntityId());
106
            $subscription->setPrice($price);
107
        }
108
        $this->subscriptionRepository->save($subscription);
109
        $this->subscriptionRepository->updateValidUntilForAllActiveSubscriptions($customer, $subscription->getMainExternalReference(), $subscriptionCreationResponse->getBilledUntil());
110
111
        $payment = $this->paymentFactory->fromSubscriptionCreation($subscriptionCreationResponse, $customer);
112
        $payment->addSubscription($subscription);
113
        $this->paymentRepository->save($payment);
114
115
        return $subscription;
116
    }
117
118
    public function startSubscriptionWithDto(CustomerInterface $customer, StartSubscriptionDto $startSubscriptionDto): Subscription
119
    {
120
        if (!$startSubscriptionDto->getPaymentDetailsId()) {
121
            $paymentDetails = $this->paymentDetailsRepository->getDefaultPaymentDetailsForCustomer($customer);
122
        } else {
123
            $paymentDetails = $this->paymentDetailsRepository->findById($startSubscriptionDto->getPaymentDetailsId());
124
        }
125
126
        $plan = $this->planManager->getPlanByName($startSubscriptionDto->getPlanName());
127
        $planPrice = $plan->getPriceForPaymentSchedule($startSubscriptionDto->getSchedule(), $startSubscriptionDto->getCurrency());
128
129
        return $this->startSubscription($customer, $plan, $planPrice, $paymentDetails, $startSubscriptionDto->getSeatNumbers());
130
    }
131
132
    public function cancelSubscriptionAtEndOfCurrentPeriod(Subscription $subscription): Subscription
133
    {
134
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
135
136
        $cancelRequest = new CancelSubscription();
137
        $cancelRequest->setSubscription($obolSubscription);
138
        $cancelRequest->setInstantCancel(false);
139
        $cancelRequest->setRefundType(RefundType::NONE);
0 ignored issues
show
Bug introduced by
The method setRefundType() does not exist on Obol\Model\CancelSubscription. ( Ignorable by Annotation )

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

139
        $cancelRequest->/** @scrutinizer ignore-call */ 
140
                        setRefundType(RefundType::NONE);

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...
140
141
        $cancellation = $this->provider->payments()->stopSubscription($cancelRequest);
0 ignored issues
show
Unused Code introduced by
The assignment to $cancellation is dead and can be removed.
Loading history...
142
143
        $subscription->setStatus(EmbeddedSubscription::STATUS_CANCELLED);
144
        $subscription->endAtEndOfPeriod();
145
146
        return $subscription;
147
    }
148
149
    public function cancelSubscriptionInstantly(Subscription $subscription): Subscription
150
    {
151
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
152
153
        $cancelRequest = new CancelSubscription();
154
        $cancelRequest->setSubscription($obolSubscription);
155
        $cancelRequest->setInstantCancel(true);
156
157
        $cancellation = $this->provider->payments()->stopSubscription($cancelRequest);
0 ignored issues
show
Unused Code introduced by
The assignment to $cancellation is dead and can be removed.
Loading history...
158
159
        $subscription->setStatus(EmbeddedSubscription::STATUS_CANCELLED);
160
        $subscription->endNow();
161
162
        return $subscription;
163
    }
164
165
    public function cancelSubscriptionOnDate(Subscription $subscription, \DateTime $dateTime): Subscription
166
    {
167
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
168
169
        $cancelRequest = new CancelSubscription();
170
        $cancelRequest->setSubscription($obolSubscription);
171
        $cancelRequest->setInstantCancel(false);
172
173
        $cancellation = $this->provider->payments()->stopSubscription($cancelRequest);
0 ignored issues
show
Unused Code introduced by
The assignment to $cancellation is dead and can be removed.
Loading history...
174
175
        $subscription->setStatus(EmbeddedSubscription::STATUS_CANCELLED);
176
        $subscription->setEndedAt($dateTime);
177
        $subscription->setValidUntil($dateTime);
178
179
        return $subscription;
180
    }
181
182
    /**
183
     * @param Plan      $plan
184
     * @param PlanPrice $planPrice
185
     */
186
    public function createSubscription(SubscriptionPlan|Plan $plan, PlanPrice|Price $planPrice, \Obol\Model\SubscriptionCreationResponse $subscriptionCreationResponse, int $seatNumbers, CustomerInterface $customer): Subscription
0 ignored issues
show
Unused Code introduced by
The parameter $customer is not used and could be removed. ( Ignorable by Annotation )

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

186
    public function createSubscription(SubscriptionPlan|Plan $plan, PlanPrice|Price $planPrice, \Obol\Model\SubscriptionCreationResponse $subscriptionCreationResponse, int $seatNumbers, /** @scrutinizer ignore-unused */ CustomerInterface $customer): Subscription

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $planPrice is not used and could be removed. ( Ignorable by Annotation )

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

186
    public function createSubscription(SubscriptionPlan|Plan $plan, /** @scrutinizer ignore-unused */ PlanPrice|Price $planPrice, \Obol\Model\SubscriptionCreationResponse $subscriptionCreationResponse, int $seatNumbers, CustomerInterface $customer): Subscription

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $plan is not used and could be removed. ( Ignorable by Annotation )

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

186
    public function createSubscription(/** @scrutinizer ignore-unused */ SubscriptionPlan|Plan $plan, PlanPrice|Price $planPrice, \Obol\Model\SubscriptionCreationResponse $subscriptionCreationResponse, int $seatNumbers, CustomerInterface $customer): Subscription

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $seatNumbers is not used and could be removed. ( Ignorable by Annotation )

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

186
    public function createSubscription(SubscriptionPlan|Plan $plan, PlanPrice|Price $planPrice, \Obol\Model\SubscriptionCreationResponse $subscriptionCreationResponse, /** @scrutinizer ignore-unused */ int $seatNumbers, CustomerInterface $customer): Subscription

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $subscriptionCreationResponse is not used and could be removed. ( Ignorable by Annotation )

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

186
    public function createSubscription(SubscriptionPlan|Plan $plan, PlanPrice|Price $planPrice, /** @scrutinizer ignore-unused */ \Obol\Model\SubscriptionCreationResponse $subscriptionCreationResponse, int $seatNumbers, CustomerInterface $customer): Subscription

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
187
    {
188
    }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Parthenon\Billing\Entity\Subscription. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
189
}
190