Passed
Push — main ( 172c2c...d81ed3 )
by Iain
19:13
created

SubscriptionManager::changeSubscriptionPlan()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 4
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright (C) 2020-2024 Iain Cambridge
7
 *
8
 *     This program is free software: you can redistribute it and/or modify
9
 *     it under the terms of the GNU General Public License as published by
10
 *     the Free Software Foundation, either version 3 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 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\BillaBear\Subscription;
23
24
use BillaBear\ApiException;
0 ignored issues
show
Bug introduced by
The type BillaBear\ApiException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use BillaBear\Model\SubscriptionIdCancelBody;
0 ignored issues
show
Bug introduced by
The type BillaBear\Model\SubscriptionIdCancelBody was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use BillaBear\Model\SubscriptionStartBody;
0 ignored issues
show
Bug introduced by
The type BillaBear\Model\SubscriptionStartBody was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
use Obol\Model\Enum\ChargeFailureReasons;
28
use Parthenon\Billing\BillaBear\SdkFactory;
29
use Parthenon\Billing\Dto\StartSubscriptionDto;
30
use Parthenon\Billing\Entity\CustomerInterface;
31
use Parthenon\Billing\Entity\PaymentCard;
32
use Parthenon\Billing\Entity\Price;
33
use Parthenon\Billing\Entity\Subscription;
34
use Parthenon\Billing\Entity\SubscriptionPlan;
35
use Parthenon\Billing\Enum\BillingChangeTiming;
36
use Parthenon\Billing\Enum\SubscriptionStatus;
37
use Parthenon\Billing\Event\SubscriptionCancelled;
38
use Parthenon\Billing\Event\SubscriptionCreated;
39
use Parthenon\Billing\Exception\NoPaymentDetailsException;
40
use Parthenon\Billing\Exception\PaymentFailureException;
41
use Parthenon\Billing\Plan\Plan;
42
use Parthenon\Billing\Plan\PlanManagerInterface;
43
use Parthenon\Billing\Plan\PlanPrice;
44
use Parthenon\Billing\Subscription\SubscriptionManagerInterface;
45
use Parthenon\Common\Exception\GeneralException;
46
use Parthenon\Common\LoggerAwareTrait;
47
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
48
49
class SubscriptionManager implements SubscriptionManagerInterface
50
{
51
    use LoggerAwareTrait;
52
53
    public function __construct(
54
        private SdkFactory $sdkFactory,
55
        private PlanManagerInterface $planManager,
56
        private EventDispatcherInterface $dispatcher,
57
    ) {
58
    }
59
60
    public function startSubscription(
61
        CustomerInterface $customer,
62
        SubscriptionPlan|Plan $plan,
63
        Price|PlanPrice $planPrice,
64
        ?PaymentCard $paymentDetails = null,
65
        int $seatNumbers = 1,
66
        ?bool $hasTrial = null,
67
        ?int $trialLengthDays = 0,
68
    ): Subscription {
69
        if (!$plan instanceof Plan) {
70
            throw new GeneralException('Invalid type of plan given');
71
        }
72
73
        $customerId = $customer->getExternalCustomerReference();
74
        $payload = [
75
            'subscription_plan' => $plan->getEntityId(),
76
            'price' => $planPrice->getEntityId(),
0 ignored issues
show
Bug introduced by
The method getEntityId() does not exist on Parthenon\Billing\Entity\Price. ( Ignorable by Annotation )

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

76
            'price' => $planPrice->/** @scrutinizer ignore-call */ getEntityId(),

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...
77
            'seat_numbers' => $seatNumbers,
78
        ];
79
80
        if ($paymentDetails) {
81
            $payload['payment_details'] = $paymentDetails->getId();
82
        }
83
84
        $subscriptionStart = new SubscriptionStartBody($payload);
85
86
        try {
87
            $response = $this->sdkFactory->createSubscriptionsApi()->customerStartSubscription($subscriptionStart, $customerId);
88
        } catch (ApiException $apiException) {
89
            if (402 === $apiException->getCode()) {
90
                $body = $apiException->getResponseBody();
91
                $json = json_decode($body, true);
92
                throw new PaymentFailureException(ChargeFailureReasons::from($json['reason']), previous: $apiException);
93
            }
94
95
            if (406 === $apiException->getCode()) {
96
                throw new NoPaymentDetailsException('No payment details', previous: $apiException);
97
            }
98
99
            throw new GeneralException(previous: $apiException);
100
        } catch (\Throwable $exception) {
101
            new GeneralException($exception->getMessage(), previous: $exception);
102
        }
103
        $subscription = new Subscription();
104
        $subscription->setCustomer($customer);
105
        $subscription->setId($response->getId());
106
        $subscription->setValidUntil(new \DateTime($response->getValidUntil()));
107
        $subscription->setMainExternalReference($response->getMainExternalReference());
108
        $subscription->setChildExternalReference($response->getChildExternalReference());
109
110
        $this->dispatcher->dispatch(new SubscriptionCreated($subscription), SubscriptionCreated::NAME);
111
112
        return $subscription;
113
    }
114
115
    public function startSubscriptionWithDto(CustomerInterface $customer, StartSubscriptionDto $startSubscriptionDto): Subscription
116
    {
117
        $plan = $this->planManager->getPlanByName($startSubscriptionDto->getPlanName());
118
        $planPrice = $plan->getPriceForPaymentSchedule($startSubscriptionDto->getSchedule(), $startSubscriptionDto->getCurrency());
119
120
        return $this->startSubscription($customer, $plan, $planPrice, null, $startSubscriptionDto->getSeatNumbers());
121
    }
122
123
    public function cancelSubscriptionAtEndOfCurrentPeriod(Subscription $subscription): Subscription
124
    {
125
        $cancelBody = new SubscriptionIdCancelBody();
126
        $cancelBody->setWhen('end-of-run');
127
        $cancelBody->setRefundType('none');
128
        $this->sdkFactory->createSubscriptionsApi()->cancelSubscription($cancelBody, $subscription->getId());
129
130
        $subscription->setStatus(SubscriptionStatus::PENDING_CANCEL);
131
        $subscription->endAtEndOfPeriod();
132
        $this->dispatcher->dispatch(new SubscriptionCancelled($subscription), SubscriptionCancelled::NAME);
133
134
        return $subscription;
135
    }
136
137
    public function cancelSubscriptionInstantly(Subscription $subscription): Subscription
138
    {
139
        $cancelBody = new SubscriptionIdCancelBody();
140
        $cancelBody->setWhen('instantly');
141
        $cancelBody->setRefundType('prorate');
142
        $this->sdkFactory->createSubscriptionsApi()->cancelSubscription($cancelBody, $subscription->getId());
143
144
        $subscription->setStatus(SubscriptionStatus::CANCELLED);
145
        $subscription->setActive(false);
146
        $subscription->endNow();
147
        $this->dispatcher->dispatch(new SubscriptionCancelled($subscription), SubscriptionCancelled::NAME);
148
149
        return $subscription;
150
    }
151
152
    public function cancelSubscriptionOnDate(Subscription $subscription, \DateTimeInterface $dateTime): Subscription
153
    {
154
        $cancelBody = new SubscriptionIdCancelBody();
155
        $cancelBody->setWhen('specific-date');
156
        $cancelBody->setRefundType('prorate');
157
        $cancelBody->setDate($dateTime);
158
        $this->sdkFactory->createSubscriptionsApi()->cancelSubscription($cancelBody, $subscription->getId());
159
160
        $subscription->setStatus(SubscriptionStatus::PENDING_CANCEL);
161
        $subscription->setEndedAt($dateTime);
162
        $subscription->setValidUntil($dateTime);
163
164
        $this->dispatcher->dispatch(new SubscriptionCancelled($subscription), SubscriptionCancelled::NAME);
165
166
        return $subscription;
167
    }
168
169
    public function changeSubscriptionPrice(Subscription $subscription, Price $price, BillingChangeTiming $billingChangeTiming): void
170
    {
171
        // TODO: Implement changeSubscriptionPrice() method.
172
    }
173
174
    public function changeSubscriptionPlan(Subscription $subscription, SubscriptionPlan $plan, Price $price, BillingChangeTiming $billingChangeTiming): void
175
    {
176
        // TODO: Implement changeSubscriptionPlan() method.
177
    }
178
}
179