Payments::handleDefaultParameters()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 15
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 20
rs 9.7666
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\DependencyInjection\Modules;
23
24
use Parthenon\Common\Exception\ParameterNotSetException;
25
use Parthenon\Payments\Athena\TeamSubscriberSection;
26
use Parthenon\Payments\Athena\UserSubscriberSection;
27
use Parthenon\Payments\PaymentProvider\TransactionCloud\Config;
28
use Parthenon\Payments\Plan\CounterInterface;
29
use Parthenon\Payments\Repository\SubscriberRepositoryInterface;
30
use Parthenon\Payments\Subscriber\SubscriberInterface;
31
use Parthenon\Payments\Transition\ToActiveTransitionInterface;
32
use Parthenon\Payments\Transition\ToCancelledTransitionInterface;
33
use Parthenon\Payments\Transition\ToOverdueTransitionInterface;
34
use Parthenon\User\Repository\TeamRepositoryInterface;
35
use Parthenon\User\Repository\UserRepositoryInterface;
36
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
37
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
38
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
39
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
40
use Symfony\Component\Config\FileLocator;
41
use Symfony\Component\DependencyInjection\ContainerBuilder;
42
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
43
44
final class Payments implements ModuleConfigurationInterface
45
{
46
    public function addConfig(NodeBuilder $nodeBuilder): void
47
    {
48
        $nodeBuilder
49
            ->arrayNode('payments')
50
                ->children()
51
                    ->booleanNode('enabled')->end()
52
                    ->scalarNode('provider')->end()
0 ignored issues
show
Bug introduced by
The method scalarNode() does not exist on Symfony\Component\Config...der\NodeParentInterface. It seems like you code against a sub-type of Symfony\Component\Config...der\NodeParentInterface such as Symfony\Component\Config...ion\Builder\NodeBuilder. ( Ignorable by Annotation )

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

52
                    ->/** @scrutinizer ignore-call */ scalarNode('provider')->end()
Loading history...
53
                    ->scalarNode('success_redirect_route')->end()
54
                    ->scalarNode('cancel_checkout_redirect_route')->end()
55
                    ->arrayNode('stripe')
56
                        ->children()
57
                            ->scalarNode('private_api_key')->end()
58
                            ->scalarNode('public_api_key')->end()
59
                            ->scalarNode('success_url')->end()
60
                            ->scalarNode('cancel_url')->end()
61
                            ->scalarNode('return_url')->end()
62
                        ->end()
63
                    ->end()
64
                    ->arrayNode('transaction_cloud')
65
                        ->children()
66
                            ->scalarNode('api_key')->end()
67
                            ->scalarNode('api_key_password')->end()
68
                            ->booleanNode('sandbox')->defaultFalse()->end()
69
                            ->scalarNode('customer_id_parameter')->end()
70
                            ->scalarNode('payment_id_parameter')->end()
71
                        ->end()
72
                    ->end()
73
                    ->arrayNode('subscriptions')
74
                    ->children()
75
                        ->scalarNode('subscriber_type')->end()
76
                    ->end()
77
                    ->fixXmlConfig('plans')
78
                    ->append($this->getPlansNode())
79
                    ->end()
80
                ->end()
81
                ->fixXmlConfig('prices')
82
                ->append($this->getPricesNode())
83
84
            ->end();
85
    }
86
87
    public function handleDefaultParameters(ContainerBuilder $container): void
88
    {
89
        $container->setParameter('parthenon_payments_subscriber_type', '');
90
91
        $container->setParameter('parthenon_payments_stripe_private_api_key', '');
92
        $container->setParameter('parthenon_payments_stripe_public_api_key', '');
93
        $container->setParameter('parthenon_payments_stripe_success_url', '');
94
        $container->setParameter('parthenon_payments_stripe_cancel_url', '');
95
        $container->setParameter('parthenon_payments_stripe_return_url', '');
96
97
        $container->setParameter('parthenon_payments_transaction_cloud_api_key', '');
98
        $container->setParameter('parthenon_payments_transaction_cloud_api_key_password', '');
99
        $container->setParameter('parthenon_payments_transaction_cloud_sandbox', false);
100
        $container->setParameter('parthenon_payments_transaction_cloud_customer_id_parameter', Config::DEFAULT_CUSTOMER_ID_PARAMETER);
101
        $container->setParameter('parthenon_payments_transaction_cloud_payment_id_parameter', Config::DEFAULT_PAYMENT_ID_PARAMETER);
102
103
        $container->setParameter('parthenon_payments_prices', []);
104
        $container->setParameter('parthenon_payments_plan_plans', []);
105
        $container->setParameter('parthenon_payments_success_redirect_route', 'app_index');
106
        $container->setParameter('parthenon_payments_cancel_checkout_redirect_route', null);
107
    }
108
109
    public function handleConfiguration(array $config, ContainerBuilder $container): void
110
    {
111
        if (!isset($config['payments']) || !isset($config['payments']['enabled']) || false == $config['payments']['enabled']) {
112
            return;
113
        }
114
        $container->setParameter('parthenon_payments_enabled', true);
115
        $container->setParameter('parthenon_payments_plan_plans', $config['payments']['subscriptions']['plan']);
116
        $container->registerForAutoconfiguration(CounterInterface::class)->addTag('parthenon.payments.plan.counter');
117
        $container->registerForAutoconfiguration(ToActiveTransitionInterface::class)->addTag('parthenon.payments.transitions.to_active');
118
        $container->registerForAutoconfiguration(ToCancelledTransitionInterface::class)->addTag('parthenon.payments.transitions.to_cancelled');
119
        $container->registerForAutoconfiguration(ToOverdueTransitionInterface::class)->addTag('parthenon.payments.transitions.to_overdue');
120
121
        $this->configureSubscriberType($config, $container);
122
123
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../../Resources/config'));
124
125
        if ('stripe' === strtolower($config['payments']['provider'])) {
126
            $this->handlePaymentsStripe($config, $container);
127
            $loader->load('services/payments/stripe.xml');
128
        } elseif ('transaction_cloud' === strtolower($config['payments']['provider'])) {
129
            $this->handlePaymentsTransactionCloud($config, $container);
130
            $loader->load('services/payments/transaction_cloud.xml');
131
        }
132
        $config = $this->configurePrice($config, $container);
0 ignored issues
show
Unused Code introduced by
The assignment to $config is dead and can be removed.
Loading history...
133
134
        $loader->load('services/payments.xml');
135
    }
136
137
    private function handlePaymentsTransactionCloud(array $config, ContainerBuilder $containerBuilder)
138
    {
139
        if (empty($config['payments']['transaction_cloud'])) {
140
            throw new ParameterNotSetException('Then payment.provider is transaction_cloud then payments.transaction_cloud must be provided');
141
        }
142
143
        $transactionCloudConfig = $config['payments']['transaction_cloud'];
144
145
        if (!isset($transactionCloudConfig['api_key'])) {
146
            throw new ParameterNotSetException('Then payment.provider is transaction_cloud then payments.transaction_cloud.api_key must be provided');
147
        }
148
        if (!isset($transactionCloudConfig['api_key_password'])) {
149
            throw new ParameterNotSetException('Then payment.provider is transaction_cloud then payments.transaction_cloud.api_key must be provided');
150
        }
151
152
        $containerBuilder->setParameter('parthenon_payments_transaction_cloud_api_key', $transactionCloudConfig['api_key']);
153
        $containerBuilder->setParameter('parthenon_payments_transaction_cloud_api_key_password', $transactionCloudConfig['api_key_password']);
154
        $containerBuilder->setParameter('parthenon_payments_transaction_cloud_sandbox', $transactionCloudConfig['sandbox'] ?? false);
155
        $containerBuilder->setParameter('parthenon_payments_transaction_cloud_customer_id_parameter', $transactionCloudConfig['customer_id_parameter'] ?? Config::DEFAULT_CUSTOMER_ID_PARAMETER);
156
        $containerBuilder->setParameter('parthenon_payments_transaction_cloud_payment_id_parameter', $transactionCloudConfig['payment_id_parameter'] ?? Config::DEFAULT_PAYMENT_ID_PARAMETER);
157
    }
158
159
    private function handlePaymentsStripe(array $config, ContainerBuilder $containerBuilder)
160
    {
161
        if (empty($config['payments']['stripe'])) {
162
            throw new ParameterNotSetException('When payment.provider is stripe then payments.stripe needs to be provided');
163
        }
164
165
        $stripeConfig = $config['payments']['stripe'];
166
        $stripeConfig = $this->configureStripePrivateApiKey($stripeConfig);
167
        $stripeConfig = $this->configureStripePublicKey($stripeConfig);
168
        $stripeConfig = $this->configureStripeSuccessUrl($stripeConfig);
169
        $stripeConfig = $this->configureCancelUrl($stripeConfig);
170
171
        $containerBuilder->setParameter('parthenon_payments_stripe_private_api_key', $stripeConfig['private_api_key'] ?? '');
172
        $containerBuilder->setParameter('parthenon_payments_stripe_public_api_key', $stripeConfig['public_api_key'] ?? '');
173
        $containerBuilder->setParameter('parthenon_payments_stripe_success_url', $stripeConfig['success_url'] ?? '');
174
        $containerBuilder->setParameter('parthenon_payments_stripe_cancel_url', $stripeConfig['cancel_url'] ?? '');
175
        $containerBuilder->setParameter('parthenon_payments_stripe_return_url', $stripeConfig['return_url'] ?? '');
176
177
        $this->configureSuccessRedirectRoute($config, $containerBuilder);
178
    }
179
180
    private function getPricesNode(): NodeDefinition
181
    {
182
        $treeBuilder = new TreeBuilder('price');
183
        $node = $treeBuilder->getRootNode();
184
185
        /** @var ArrayNodeDefinition $planNode */
186
        $planNode = $node
187
            ->requiresAtLeastOneElement()
0 ignored issues
show
Bug introduced by
The method requiresAtLeastOneElement() does not exist on Symfony\Component\Config...\Builder\NodeDefinition. It seems like you code against a sub-type of Symfony\Component\Config...\Builder\NodeDefinition such as Symfony\Component\Config...der\ArrayNodeDefinition. ( Ignorable by Annotation )

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

187
            ->/** @scrutinizer ignore-call */ requiresAtLeastOneElement()
Loading history...
188
            ->useAttributeAsKey('name')
189
            ->prototype('array');
190
191
        $planNode
192
            ->fixXmlConfig('prices')
193
                    ->requiresAtLeastOneElement()
194
                        ->useAttributeAsKey('paymentSchedule')
195
                        ->prototype('array')
196
                            ->children()
197
                                ->scalarNode('price')->end()
198
                                ->scalarNode('price_id')->end()
199
                                ->booleanNode('subscription')->end()
200
                                ->scalarNode('trial_day_length')->end()
201
                            ->end()
202
                        ->end()
203
                    ->end()
204
            ->end();
205
206
        return $node;
207
    }
208
209
    private function configureSuccessRedirectRoute(array $config, ContainerBuilder $containerBuilder): void
210
    {
211
        if (isset($config['payments']['success_redirect_route'])) {
212
            $containerBuilder->setParameter('parthenon_payments_success_redirect_route', $config['payments']['success_redirect_route']);
213
        }
214
        if (isset($config['payments']['cancel_checkout_redirect_route']) && !empty($config['payments']['cancel_checkout_redirect_route'])) {
215
            $containerBuilder->setParameter('parthenon_payments_cancel_checkout_redirect_route', $config['payments']['cancel_checkout_redirect_route']);
216
        } else {
217
            $defaultRoute = $containerBuilder->getParameter('parthenon_payments_success_redirect_route');
218
            $containerBuilder->setParameter('parthenon_payments_cancel_checkout_redirect_route', $defaultRoute);
219
        }
220
    }
221
222
    private function configurePrice(array $config, ContainerBuilder $containerBuilder): array
223
    {
224
        if (isset($config['payments']['price'])) {
225
            $containerBuilder->setParameter('parthenon_payments_prices', $config['payments']['price']);
226
        }
227
228
        return $config;
229
    }
230
231
    /**
232
     * @throws ParameterNotSetException
233
     */
234
    private function configureStripePrivateApiKey(mixed $stripeConfig): mixed
235
    {
236
        if (!isset($stripeConfig['private_api_key'])) {
237
            throw new ParameterNotSetException('When payment.provide is stripe then payments.stripe.private_api_key needs to be provided');
238
        }
239
240
        return $stripeConfig;
241
    }
242
243
    /**
244
     * @throws ParameterNotSetException
245
     */
246
    private function configureStripePublicKey(mixed $stripeConfig): mixed
247
    {
248
        if (!isset($stripeConfig['public_api_key'])) {
249
            throw new ParameterNotSetException('When payment.provide is stripe then payments.stripe.public_api_key needs to be provided');
250
        }
251
252
        return $stripeConfig;
253
    }
254
255
    /**
256
     * @throws ParameterNotSetException
257
     */
258
    private function configureStripeSuccessUrl(mixed $stripeConfig): mixed
259
    {
260
        if (!isset($stripeConfig['success_url'])) {
261
            throw new ParameterNotSetException('When payment.provide is stripe then payments.stripe.success_url needs to be provided');
262
        }
263
264
        return $stripeConfig;
265
    }
266
267
    /**
268
     * @throws ParameterNotSetException
269
     */
270
    private function configureCancelUrl(mixed $stripeConfig): mixed
271
    {
272
        if (!isset($stripeConfig['cancel_url'])) {
273
            throw new ParameterNotSetException('When payment.provide is stripe then payments.stripe.cancel_url needs to be provided');
274
        }
275
276
        return $stripeConfig;
277
    }
278
279
    /**
280
     * @throws ParameterNotSetException
281
     */
282
    private function configureSubscriberType(array $config, ContainerBuilder $containerBuilder): array
283
    {
284
        if (isset($config['payments']['subscriptions']['subscriber_type'])) {
285
            if (SubscriberInterface::TYPE_USER == $config['payments']['subscriptions']['subscriber_type']) {
286
                $containerBuilder->setAlias(SubscriberRepositoryInterface::class, UserRepositoryInterface::class);
287
288
                // Remove TeamSubscriberSection so only UserSubscriberSection remains
289
                $containerBuilder->removeDefinition(TeamSubscriberSection::class);
290
            } elseif (SubscriberInterface::TYPE_TEAM == $config['payments']['subscriptions']['subscriber_type']) {
291
                $containerBuilder->setAlias(SubscriberRepositoryInterface::class, TeamRepositoryInterface::class);
292
293
                // Remove TeamSubscriberSection so only UserSubscriberSection remains
294
                $containerBuilder->removeDefinition(UserSubscriberSection::class);
295
            } else {
296
                throw new ParameterNotSetException('Invalid setting for payment.subscriptions.subscriber_type');
297
            }
298
299
            $containerBuilder->setParameter('parthenon_payments_subscriber_type', $config['payments']['subscriptions']['subscriber_type']);
300
        }
301
302
        return $config;
303
    }
304
305
    private function getPlansNode(): NodeDefinition
306
    {
307
        $treeBuilder = new TreeBuilder('plan');
308
        $node = $treeBuilder->getRootNode();
309
310
        /** @var ArrayNodeDefinition $planNode */
311
        $planNode = $node
312
            ->requiresAtLeastOneElement()
313
            ->useAttributeAsKey('name')
314
            ->prototype('array');
315
316
        $planNode
317
            ->fixXmlConfig('limits')
318
            ->children()
319
            ->booleanNode('is_free')->defaultFalse()->end()
320
            ->booleanNode('is_per_seat')->defaultFalse()->end()
0 ignored issues
show
Bug introduced by
The method booleanNode() does not exist on Symfony\Component\Config...der\NodeParentInterface. It seems like you code against a sub-type of Symfony\Component\Config...der\NodeParentInterface such as Symfony\Component\Config...ion\Builder\NodeBuilder. ( Ignorable by Annotation )

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

320
            ->/** @scrutinizer ignore-call */ booleanNode('is_per_seat')->defaultFalse()->end()
Loading history...
321
            ->scalarNode('user_count')->end()
322
            ->arrayNode('features')
323
            ->scalarPrototype()->end()
324
            ->end()
325
            ->arrayNode('limit')
326
            ->useAttributeAsKey('name')
327
            ->prototype('array')
328
            ->children()
329
            ->integerNode('limit')->end()
330
            ->scalarNode('description')->end()
331
            ->end()
332
            ->end()
333
            ->end()
334
            ->end();
335
336
        return $node;
337
    }
338
}
339