Passed
Push — main ( 350a1e...a10163 )
by Iain
05:08
created

cancelSubscriptionWithFullRefund()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 17
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 26
rs 9.7
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\BillingAdminInterface;
22
use Parthenon\Billing\Entity\CustomerInterface;
23
use Parthenon\Billing\Entity\EmbeddedSubscription;
24
use Parthenon\Billing\Entity\PaymentDetails;
25
use Parthenon\Billing\Entity\Price;
26
use Parthenon\Billing\Entity\Refund;
27
use Parthenon\Billing\Entity\Subscription;
28
use Parthenon\Billing\Entity\SubscriptionPlan;
29
use Parthenon\Billing\Exception\SubscriptionCreationException;
30
use Parthenon\Billing\Obol\BillingDetailsFactoryInterface;
31
use Parthenon\Billing\Obol\PaymentFactoryInterface;
32
use Parthenon\Billing\Obol\SubscriptionFactoryInterface;
33
use Parthenon\Billing\Plan\Plan;
34
use Parthenon\Billing\Plan\PlanManagerInterface;
35
use Parthenon\Billing\Plan\PlanPrice;
36
use Parthenon\Billing\Repository\PaymentDetailsRepositoryInterface;
37
use Parthenon\Billing\Repository\PaymentRepositoryInterface;
38
use Parthenon\Billing\Repository\PriceRepositoryInterface;
39
use Parthenon\Billing\Repository\RefundRepositoryInterface;
40
use Parthenon\Billing\Repository\SubscriptionPlanRepositoryInterface;
41
use Parthenon\Billing\Repository\SubscriptionRepositoryInterface;
42
43
final class SubscriptionManager implements SubscriptionManagerInterface
44
{
45
    public function __construct(
46
        private PaymentDetailsRepositoryInterface $paymentDetailsRepository,
47
        private ProviderInterface $provider,
48
        private BillingDetailsFactoryInterface $billingDetailsFactory,
49
        private PaymentFactoryInterface $paymentFactory,
50
        private SubscriptionFactoryInterface $subscriptionFactory,
51
        private PaymentRepositoryInterface $paymentRepository,
52
        private PlanManagerInterface $planManager,
53
        private SubscriptionPlanRepositoryInterface $subscriptionPlanRepository,
54
        private PriceRepositoryInterface $priceRepository,
55
        private SubscriptionRepositoryInterface $subscriptionRepository,
56
        private RefundRepositoryInterface $refundRepository
57
    ) {
58
    }
59
60
    public function startSubscriptionWithEntities(CustomerInterface $customer, SubscriptionPlan $subscriptionPlan, Price $price, PaymentDetails $paymentDetails, int $seatNumbers): Subscription
61
    {
62
        $billingDetails = $this->billingDetailsFactory->createFromCustomerAndPaymentDetails($customer, $paymentDetails);
63
        $obolSubscription = $this->subscriptionFactory->createSubscriptionWithPrice($billingDetails, $price, $seatNumbers);
64
        $obolSubscription->setStoredPaymentReference($paymentDetails->getStoredPaymentReference());
65
66
        if ($this->subscriptionRepository->hasActiveSubscription($customer)) {
67
            $subscription = $this->subscriptionRepository->getOneActiveSubscriptionForCustomer($customer);
68
69
            if ($subscription->getCurrency() != $price->getCurrency()) {
70
                throw new SubscriptionCreationException("Can't add a child subscription for a different currency");
71
            }
72
73
            $obolSubscription->setParentReference($subscription->getMainExternalReference());
74
        }
75
76
        $subscriptionCreationResponse = $this->provider->payments()->startSubscription($obolSubscription);
77
        if ($subscriptionCreationResponse->hasCustomerCreation()) {
78
            $customer->setPaymentProviderDetailsUrl($subscriptionCreationResponse->getCustomerCreation()->getDetailsUrl());
79
            $customer->setExternalCustomerReference($subscriptionCreationResponse->getCustomerCreation()->getReference());
80
        }
81
        $payment = $this->paymentFactory->fromSubscriptionCreation($subscriptionCreationResponse, $customer);
82
        $this->paymentRepository->save($payment);
83
84
        $subscription = new Subscription();
85
        $subscription->setPlanName($subscriptionPlan->getName());
86
        $subscription->setPaymentSchedule($price->getSchedule());
0 ignored issues
show
Bug introduced by
It seems like $price->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 */ $price->getSchedule());
Loading history...
87
        $subscription->setActive(true);
88
        $subscription->setMoneyAmount($price->getAsMoney());
89
        $subscription->setStatus(\Parthenon\Billing\Entity\EmbeddedSubscription::STATUS_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->setValidUntil($subscriptionCreationResponse->getBilledUntil());
96
        $subscription->setCustomer($customer);
97
        $subscription->setMainExternalReferenceDetailsUrl($subscriptionCreationResponse->getDetailsUrl());
98
        $subscription->setSubscriptionPlan($subscriptionPlan);
99
        $subscription->setPrice($price);
100
101
        $this->subscriptionRepository->save($subscription);
102
        $this->subscriptionRepository->updateValidUntilForAllActiveSubscriptions($customer, $subscription->getMainExternalReference(), $subscriptionCreationResponse->getBilledUntil());
103
104
        return $subscription;
105
    }
106
107
    public function startSubscription(CustomerInterface $customer, Plan $plan, PlanPrice $planPrice, PaymentDetails $paymentDetails, int $seatNumbers): Subscription
108
    {
109
        $billingDetails = $this->billingDetailsFactory->createFromCustomerAndPaymentDetails($customer, $paymentDetails);
110
        $obolSubscription = $this->subscriptionFactory->createSubscription($billingDetails, $planPrice, $seatNumbers);
111
        $obolSubscription->setStoredPaymentReference($paymentDetails->getStoredPaymentReference());
112
113
        if ($this->subscriptionRepository->hasActiveSubscription($customer)) {
114
            $subscription = $this->subscriptionRepository->getOneActiveSubscriptionForCustomer($customer);
115
116
            if ($subscription->getCurrency() != $planPrice->getCurrency()) {
117
                throw new SubscriptionCreationException("Can't add a child subscription for a different currency");
118
            }
119
120
            $obolSubscription->setParentReference($subscription->getMainExternalReference());
121
        }
122
123
        $subscriptionCreationResponse = $this->provider->payments()->startSubscription($obolSubscription);
124
        if ($subscriptionCreationResponse->hasCustomerCreation()) {
125
            $customer->setPaymentProviderDetailsUrl($subscriptionCreationResponse->getCustomerCreation()->getDetailsUrl());
126
            $customer->setExternalCustomerReference($subscriptionCreationResponse->getCustomerCreation()->getReference());
127
        }
128
        $payment = $this->paymentFactory->fromSubscriptionCreation($subscriptionCreationResponse, $customer);
129
        $this->paymentRepository->save($payment);
130
131
        $subscription = new Subscription();
132
        $subscription->setPlanName($plan->getName());
133
        $subscription->setPaymentSchedule($planPrice->getSchedule());
134
        $subscription->setActive(true);
135
        $subscription->setMoneyAmount($planPrice->getPriceAsMoney());
136
        $subscription->setStatus(\Parthenon\Billing\Entity\EmbeddedSubscription::STATUS_ACTIVE);
137
        $subscription->setMainExternalReference($subscriptionCreationResponse->getSubscriptionId());
138
        $subscription->setChildExternalReference($subscriptionCreationResponse->getLineId());
139
        $subscription->setSeats($seatNumbers);
140
        $subscription->setCreatedAt(new \DateTime());
141
        $subscription->setUpdatedAt(new \DateTime());
142
        $subscription->setValidUntil($subscriptionCreationResponse->getBilledUntil());
143
        $subscription->setCustomer($customer);
144
        $subscription->setMainExternalReferenceDetailsUrl($subscriptionCreationResponse->getDetailsUrl());
145
        $subscription->setPaymentExternalReference($subscriptionCreationResponse->getPaymentDetails()->getStoredPaymentReference());
146
147
        if ($plan->hasEntityId()) {
148
            $subscriptionPlan = $this->subscriptionPlanRepository->findById($plan->getEntityId());
149
            $subscription->setSubscriptionPlan($subscriptionPlan);
150
        }
151
152
        if ($planPrice->hasEntityId()) {
153
            $price = $this->priceRepository->findById($planPrice->getEntityId());
154
            $subscription->setPrice($price);
155
        }
156
157
        $this->subscriptionRepository->save($subscription);
158
        $this->subscriptionRepository->updateValidUntilForAllActiveSubscriptions($customer, $subscription->getMainExternalReference(), $subscriptionCreationResponse->getBilledUntil());
159
160
        return $subscription;
161
    }
162
163
    public function startSubscriptionWithDto(CustomerInterface $customer, StartSubscriptionDto $startSubscriptionDto): Subscription
164
    {
165
        if (!$startSubscriptionDto->getPaymentDetailsId()) {
166
            $paymentDetails = $this->paymentDetailsRepository->getDefaultPaymentDetailsForCustomer($customer);
167
        } else {
168
            $paymentDetails = $this->paymentDetailsRepository->findById($startSubscriptionDto->getPaymentDetailsId());
169
        }
170
171
        $plan = $this->planManager->getPlanByName($startSubscriptionDto->getPlanName());
172
        $planPrice = $plan->getPriceForPaymentSchedule($startSubscriptionDto->getSchedule(), $startSubscriptionDto->getCurrency());
173
174
        return $this->startSubscription($customer, $plan, $planPrice, $paymentDetails, $startSubscriptionDto->getSeatNumbers());
175
    }
176
177
    public function cancelSubscriptionAtEndOfCurrentPeriod(Subscription $subscription): Subscription
178
    {
179
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
180
181
        $cancelRequest = new CancelSubscription();
182
        $cancelRequest->setSubscription($obolSubscription);
183
        $cancelRequest->setInstantCancel(false);
184
        $cancelRequest->setRefundType(RefundType::NONE);
185
186
        $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...
187
188
        $subscription->setStatus(EmbeddedSubscription::STATUS_CANCELLED);
189
190
        return $subscription;
191
    }
192
193
    public function cancelSubscriptionWithFullRefund(Subscription $subscription, BillingAdminInterface $billingAdmin): Subscription
194
    {
195
        $obolSubscription = $this->subscriptionFactory->createSubscriptionFromEntity($subscription);
196
197
        $cancelRequest = new CancelSubscription();
198
        $cancelRequest->setSubscription($obolSubscription);
199
        $cancelRequest->setInstantCancel(true);
200
        $cancelRequest->setRefundType(RefundType::FULL);
201
202
        $cancellation = $this->provider->payments()->stopSubscription($cancelRequest);
203
204
        if ($cancellation->hasRefund()) {
205
            $refund = new Refund();
206
            $refund->setAmount($cancellation->getRefund()->getAmount());
207
            $refund->setCurrency($cancellation->getRefund()->getCurrency());
208
            $refund->setExternalReference($cancellation->getRefund()->getId());
209
            $refund->setStatus('refunded');
210
            $refund->setBillingAdmin($billingAdmin);
211
            $refund->setCustomer($subscription->getCustomer());
212
213
            $this->refundRepository->save($refund);
214
        }
215
216
        $subscription->setStatus(EmbeddedSubscription::STATUS_CANCELLED);
217
218
        return $subscription;
219
    }
220
}
221