Passed
Push — main ( fb10b0...e71211 )
by Iain
06:32
created

SubscriptionManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
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 12
dl 0
loc 14
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\Provider\ProviderInterface;
19
use Parthenon\Billing\Dto\StartSubscriptionDto;
20
use Parthenon\Billing\Entity\CustomerInterface;
21
use Parthenon\Billing\Entity\PaymentCard;
22
use Parthenon\Billing\Entity\Price;
23
use Parthenon\Billing\Entity\Subscription;
24
use Parthenon\Billing\Entity\SubscriptionPlan;
25
use Parthenon\Billing\Enum\SubscriptionStatus;
26
use Parthenon\Billing\Event\PaymentCreated;
27
use Parthenon\Billing\Event\SubscriptionCancelled;
28
use Parthenon\Billing\Event\SubscriptionCreated;
29
use Parthenon\Billing\Exception\SubscriptionCreationException;
30
use Parthenon\Billing\Factory\EntityFactoryInterface;
31
use Parthenon\Billing\Obol\BillingDetailsFactoryInterface;
32
use Parthenon\Billing\Obol\PaymentFactoryInterface;
33
use Parthenon\Billing\Obol\SubscriptionFactoryInterface;
34
use Parthenon\Billing\Plan\Plan;
35
use Parthenon\Billing\Plan\PlanManagerInterface;
36
use Parthenon\Billing\Plan\PlanPrice;
37
use Parthenon\Billing\Repository\PaymentCardRepositoryInterface;
38
use Parthenon\Billing\Repository\PaymentRepositoryInterface;
39
use Parthenon\Billing\Repository\PriceRepositoryInterface;
40
use Parthenon\Billing\Repository\SubscriptionPlanRepositoryInterface;
41
use Parthenon\Billing\Repository\SubscriptionRepositoryInterface;
42
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43
44
final class SubscriptionManager implements SubscriptionManagerInterface
45
{
46
    public function __construct(
47
        private PaymentCardRepositoryInterface $paymentDetailsRepository,
48
        private ProviderInterface $provider,
49
        private BillingDetailsFactoryInterface $billingDetailsFactory,
50
        private PaymentFactoryInterface $paymentFactory,
51
        private SubscriptionFactoryInterface $subscriptionFactory,
52
        private PaymentRepositoryInterface $paymentRepository,
53
        private PlanManagerInterface $planManager,
54
        private SubscriptionPlanRepositoryInterface $subscriptionPlanRepository,
55
        private PriceRepositoryInterface $priceRepository,
56
        private SubscriptionRepositoryInterface $subscriptionRepository,
57
        private EntityFactoryInterface $entityFactory,
58
        private EventDispatcherInterface $dispatcher,
59
    ) {
60
    }
61
62
    public function startSubscription(CustomerInterface $customer, SubscriptionPlan|Plan $plan, Price|PlanPrice $planPrice, ?PaymentCard $paymentDetails = null, int $seatNumbers = 1, ?bool $hasTrial = null, ?int $trialLengthDays = 0): Subscription
63
    {
64
        $billingDetails = $this->billingDetailsFactory->createFromCustomerAndPaymentDetails($customer, $paymentDetails);
0 ignored issues
show
Bug introduced by
It seems like $paymentDetails can also be of type null; however, parameter $paymentDetails of Parthenon\Billing\Obol\B...omerAndPaymentDetails() does only seem to accept Parthenon\Billing\Entity\PaymentCard, 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

64
        $billingDetails = $this->billingDetailsFactory->createFromCustomerAndPaymentDetails($customer, /** @scrutinizer ignore-type */ $paymentDetails);
Loading history...
65
        $obolSubscription = $this->subscriptionFactory->createSubscription($billingDetails, $planPrice, $seatNumbers, $hasTrial ?? $plan->getHasTrial(), $trialLengthDays ?? $plan->getTrialLengthDays());
66
        $obolSubscription->setStoredPaymentReference($paymentDetails->getStoredPaymentReference());
0 ignored issues
show
Bug introduced by
The method getStoredPaymentReference() does not exist on null. ( Ignorable by Annotation )

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

66
        $obolSubscription->setStoredPaymentReference($paymentDetails->/** @scrutinizer ignore-call */ getStoredPaymentReference());

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...
67
68
        if ($this->subscriptionRepository->hasActiveSubscription($customer)) {
69
            $subscription = $this->subscriptionRepository->getOneActiveSubscriptionForCustomer($customer);
70
71
            if ($subscription->getCurrency() != $planPrice->getCurrency()) {
72
                throw new SubscriptionCreationException("Can't add a child subscription for a different currency");
73
            }
74
75
            $obolSubscription->setParentReference($subscription->getMainExternalReference());
76
        }
77
78
        $subscriptionCreationResponse = $this->provider->payments()->startSubscription($obolSubscription);
79
        if ($subscriptionCreationResponse->hasCustomerCreation()) {
80
            $customer->setPaymentProviderDetailsUrl($subscriptionCreationResponse->getCustomerCreation()->getDetailsUrl());
81
            $customer->setExternalCustomerReference($subscriptionCreationResponse->getCustomerCreation()->getReference());
82
        }
83
84
        $subscription = $this->entityFactory->getSubscriptionEntity();
85
        $subscription->setPlanName($plan->getName());
86
        $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

86
        $subscription->setPaymentSchedule(/** @scrutinizer ignore-type */ $planPrice->getSchedule());
Loading history...
87
        $subscription->setActive(true);
88
        $subscription->setMoneyAmount($subscriptionCreationResponse->getPaymentDetails()?->getAmount());
89
        $subscription->setStatus(SubscriptionStatus::ACTIVE);
90
        $subscription->setMainExternalReference($subscriptionCreationResponse->getSubscriptionId());
91
        $subscription->setChildExternalReference($subscriptionCreationResponse->getLineId());
92
        $subscription->setSeats($seatNumbers);
93
        $subscription->setCreatedAt(new \DateTime());
94
        $subscription->setUpdatedAt(new \DateTime());
95
        $subscription->setStartOfCurrentPeriod(new \DateTime());
96
        $subscription->setValidUntil($subscriptionCreationResponse->getBilledUntil());
97
        $subscription->setCustomer($customer);
98
        $subscription->setMainExternalReferenceDetailsUrl($subscriptionCreationResponse->getDetailsUrl());
99
        $subscription->setPaymentDetails($paymentDetails);
100
        $subscription->setTrialLengthDays($obolSubscription->getTrialLengthDays());
101
        $subscription->setHasTrial($obolSubscription->hasTrial());
102
103
        if ($plan instanceof SubscriptionPlan) {
104
            $subscription->setSubscriptionPlan($plan);
105
        } elseif ($plan->hasEntityId()) {
106
            $subscriptionPlan = $this->subscriptionPlanRepository->findById($plan->getEntityId());
107
            $subscription->setSubscriptionPlan($subscriptionPlan);
108
        }
109
110
        if ($planPrice instanceof Price) {
111
            $subscription->setPrice($planPrice);
112
        } elseif ($planPrice->hasEntityId()) {
113
            $price = $this->priceRepository->findById($planPrice->getEntityId());
114
            $subscription->setPrice($price);
115
        }
116
        $this->subscriptionRepository->save($subscription);
117
        $this->subscriptionRepository->updateValidUntilForAllActiveSubscriptions($customer, $subscription->getMainExternalReference(), $subscriptionCreationResponse->getBilledUntil());
118
119
        $this->dispatcher->dispatch(new SubscriptionCreated($subscription), SubscriptionCreated::NAME);
120
121
        $obolPaymentDetails = $subscriptionCreationResponse->getPaymentDetails();
122
        if ($obolPaymentDetails) {
123
            $payment = $this->paymentFactory->fromSubscriptionCreation($obolPaymentDetails, $customer);
124
            $payment->addSubscription($subscription);
125
            $this->paymentRepository->save($payment);
126
127
            $this->dispatcher->dispatch(new PaymentCreated($payment, true), PaymentCreated::NAME);
128
        }
129
130
        return $subscription;
131
    }
132
133
    public function startSubscriptionWithDto(CustomerInterface $customer, StartSubscriptionDto $startSubscriptionDto): Subscription
134
    {
135
        if (!$startSubscriptionDto->getPaymentDetailsId()) {
136
            $paymentDetails = $this->paymentDetailsRepository->getDefaultPaymentCardForCustomer($customer);
137
        } else {
138
            $paymentDetails = $this->paymentDetailsRepository->findById($startSubscriptionDto->getPaymentDetailsId());
139
        }
140
141
        $plan = $this->planManager->getPlanByName($startSubscriptionDto->getPlanName());
142
        $planPrice = $plan->getPriceForPaymentSchedule($startSubscriptionDto->getSchedule(), $startSubscriptionDto->getCurrency());
143
144
        return $this->startSubscription($customer, $plan, $planPrice, $paymentDetails, $startSubscriptionDto->getSeatNumbers());
145
    }
146
147
    public function cancelSubscriptionAtEndOfCurrentPeriod(Subscription $subscription): Subscription
148
    {
149
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription, false);
0 ignored issues
show
Unused Code introduced by
The call to Parthenon\Billing\Obol\S...ubscriptionFromEntity() has too many arguments starting with false. ( Ignorable by Annotation )

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

149
        /** @scrutinizer ignore-call */ 
150
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription, false);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
150
151
        $cancelRequest = new CancelSubscription();
152
        $cancelRequest->setSubscription($obolSubscription);
153
        $cancelRequest->setInstantCancel(false);
154
155
        $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...
156
157
        $subscription->setStatus(SubscriptionStatus::PENDING_CANCEL);
158
        $subscription->endAtEndOfPeriod();
159
160
        $this->subscriptionRepository->save($subscription);
161
        $this->dispatcher->dispatch(new SubscriptionCancelled($subscription), SubscriptionCancelled::NAME);
162
163
        return $subscription;
164
    }
165
166
    public function cancelSubscriptionInstantly(Subscription $subscription): Subscription
167
    {
168
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
169
170
        $cancelRequest = new CancelSubscription();
171
        $cancelRequest->setSubscription($obolSubscription);
172
        $cancelRequest->setInstantCancel(true);
173
174
        $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...
175
176
        $subscription->setStatus(SubscriptionStatus::CANCELLED);
177
        $subscription->setActive(false);
178
        $subscription->endNow();
179
180
        $this->subscriptionRepository->save($subscription);
181
        $this->dispatcher->dispatch(new SubscriptionCancelled($subscription), SubscriptionCancelled::NAME);
182
183
        return $subscription;
184
    }
185
186
    public function cancelSubscriptionOnDate(Subscription $subscription, \DateTimeInterface $dateTime): Subscription
187
    {
188
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
189
190
        $cancelRequest = new CancelSubscription();
191
        $cancelRequest->setSubscription($obolSubscription);
192
        $cancelRequest->setInstantCancel(false);
193
194
        $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...
195
196
        $subscription->setStatus(SubscriptionStatus::PENDING_CANCEL);
197
        $subscription->setEndedAt($dateTime);
198
        $subscription->setValidUntil($dateTime);
199
200
        $this->subscriptionRepository->save($subscription);
201
        $this->dispatcher->dispatch(new SubscriptionCancelled($subscription), SubscriptionCancelled::NAME);
202
203
        return $subscription;
204
    }
205
206
    public function changeSubscriptionPrice(Subscription $subscription, Price $price): void
207
    {
208
        $subscription->setPrice($price);
209
        $subscription->setMoneyAmount($price->getAsMoney());
210
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
211
212
        $this->provider->subscriptions()->updatePrice($obolSubscription);
213
    }
214
215
    public function changeSubscriptionPlan(Subscription $subscription, SubscriptionPlan $plan, Price $price): void
216
    {
217
        $subscription->setSubscriptionPlan($plan);
218
        $subscription->setPlanName($plan->getName());
219
        $subscription->setPrice($price);
220
        $subscription->setMoneyAmount($price->getAsMoney());
221
222
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
223
224
        $this->provider->subscriptions()->updatePrice($obolSubscription);
225
    }
226
}
227