Passed
Push — main ( 31c5fd...5e12f5 )
by Iain
04:53
created

Billing::buildPricesNode()   A

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 0
dl 0
loc 20
rs 9.7666
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\DependencyInjection\Modules;
16
17
use Parthenon\Billing\Athena\CustomerTeamSection;
18
use Parthenon\Billing\Athena\CustomerUserSection;
19
use Parthenon\Billing\CustomerProviderInterface;
20
use Parthenon\Billing\Repository\CustomerRepositoryInterface;
21
use Parthenon\Billing\TeamCustomerProvider;
22
use Parthenon\Billing\UserCustomerProvider;
23
use Parthenon\Common\Exception\ParameterNotSetException;
24
use Parthenon\User\Repository\TeamRepositoryInterface;
25
use Parthenon\User\Repository\UserRepositoryInterface;
26
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
27
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
28
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
29
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
30
use Symfony\Component\Config\FileLocator;
31
use Symfony\Component\DependencyInjection\ContainerBuilder;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Depend...ection\ContainerBuilder 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...
32
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Depend...on\Loader\XmlFileLoader 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...
33
34
class Billing implements ModuleConfigurationInterface
35
{
36
    public function addConfig(NodeBuilder $nodeBuilder): void
37
    {
38
        $nodeBuilder->arrayNode('billing')
39
            ->children()
40
                ->booleanNode('enabled')->defaultFalse()->end()
41
                ?->scalarNode('customer_type')->defaultValue('team')->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

41
                ?->/** @scrutinizer ignore-call */ scalarNode('customer_type')->defaultValue('team')->end()
Loading history...
42
                ?->arrayNode('payments')
43
                    ->children()
44
                        ->scalarNode('provider')->end()
45
                        ?->booleanNode('pci_mode')->end()
46
                        ?->scalarNode('return_url')->end()
47
                        ?->scalarNode('cancel_url')->end()
48
                        ?->arrayNode('adyen')
49
                            ->children()
50
                                ->scalarNode('api_key')->end()
51
                                ?->scalarNode('merchant_account')->end()
52
                                ?->booleanNode('test_mode')->end()
53
                                ?->scalarNode('prefix')->end()
54
                                ?->scalarNode('cse_url')->end()
55
                            ?->end()
56
                        ->end()
57
                        ?->arrayNode('stripe')
58
                            ->children()
59
                                ->scalarNode('private_api_key')->end()
60
                                ?->scalarNode('public_api_key')->end()
61
                                ?->arrayNode('payment_methods')
62
                                    ->scalarPrototype()->end()
63
                                ?->end()
64
                            ->end()
65
                        ->end()
66
                    ->end()
67
                ->end()
68
                ->end()
69
                ->fixXmlConfig('plans')
70
                ->append($this->getPlansNode())
71
            ?->end();
72
    }
73
74
    public function handleDefaultParameters(ContainerBuilder $container): void
75
    {
76
        $container->setParameter('parthenon_billing_payments_obol_config', []);
77
        $container->setParameter('parthenon_billing_customer_type', 'team');
78
        $container->setParameter('parthenon_billing_config_frontend_info', '');
79
        $container->setParameter('parthenon_billing_plan_plans', []);
80
    }
81
82
    public function handleConfiguration(array $config, ContainerBuilder $container): void
83
    {
84
        if (!isset($config['billing']) || !isset($config['billing']['enabled']) || false === $config['billing']['enabled']) {
85
            return;
86
        }
87
        $container->setParameter('parthenon_billing_enabled', true);
88
89
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../../Resources/config'));
90
        $loader->load('services/billing.xml');
91
        $loader->load('services/orm/billing.xml');
92
93
        $billingConfig = $config['billing'];
94
        $paymentsConfig = $billingConfig['payments'];
95
96
        if ('team' === strtolower($billingConfig['customer_type'])) {
97
            $this->handleTeamCustomer($config, $container);
98
        } elseif ('user' === strtolower($billingConfig['customer_type'])) {
99
            $this->handleUserCustomer($config, $container);
100
        }
101
        $container->setParameter('parthenon_billing_plan_plans', $config['billing']['plan']);
102
103
        $obolConfig = match ($paymentsConfig['provider']) {
104
            'stripe' => $this->handleStripeConfig($paymentsConfig, $container),
105
            'adyen' => $this->handleAdyen($paymentsConfig, $container),
106
            'custom' => [],
107
            default => throw new ParameterNotSetException('billing.payments.provider must be valid'),
108
        };
109
110
        $container->setParameter('parthenon_billing_payments_obol_config', $obolConfig);
111
        $container->setParameter('parthenon_billing_plan_plans', $config['billing']['plan']);
112
    }
113
114
    public function buildPricesNode()
115
    {
116
        $treeBuilder = new TreeBuilder('prices');
117
        $node = $treeBuilder->getRootNode();
118
119
        $priceNode = $node->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

119
        $priceNode = $node->/** @scrutinizer ignore-call */ requiresAtLeastOneElement()
Loading history...
120
            ->useAttributeAsKey('payment_schedule')
121
            ->prototype('array');
122
        assert($priceNode instanceof ArrayNodeDefinition);
123
124
        $priceNode
125
            ->arrayPrototype()
126
            ->children()
127
            ->scalarNode('amount')->end()
128
            ->scalarNode('id')->end()
129
            ->end()
130
            ->end()
131
            ->end();
132
133
        return $node;
134
    }
135
136
    protected function handleTeamCustomer(array $config, ContainerBuilder $containerBuilder): void
0 ignored issues
show
Unused Code introduced by
The parameter $config 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

136
    protected function handleTeamCustomer(/** @scrutinizer ignore-unused */ array $config, ContainerBuilder $containerBuilder): void

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...
137
    {
138
        $containerBuilder->setAlias(CustomerProviderInterface::class, TeamCustomerProvider::class);
139
        $containerBuilder->setAlias(CustomerRepositoryInterface::class, TeamRepositoryInterface::class);
140
        $containerBuilder->removeDefinition(CustomerUserSection::class);
141
    }
142
143
    protected function handleUserCustomer(array $config, ContainerBuilder $containerBuilder): void
0 ignored issues
show
Unused Code introduced by
The parameter $config 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

143
    protected function handleUserCustomer(/** @scrutinizer ignore-unused */ array $config, ContainerBuilder $containerBuilder): void

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...
144
    {
145
        $containerBuilder->setAlias(CustomerProviderInterface::class, UserCustomerProvider::class);
146
        $containerBuilder->setAlias(CustomerRepositoryInterface::class, UserRepositoryInterface::class);
147
        $containerBuilder->removeDefinition(CustomerTeamSection::class);
148
    }
149
150
    protected function handleStripeConfig(array $paymentsConfig, ContainerBuilder $containerBuilder): array
151
    {
152
        if (!isset($paymentsConfig['stripe']['private_api_key'])) {
153
            throw new ParameterNotSetException('billing.payments.stripe.private_api_key must be set.');
154
        }
155
156
        $pciMode = false;
157
158
        if (isset($paymentsConfig['pci_mode'])) {
159
            $pciMode = $paymentsConfig['pci_mode'];
160
        }
161
162
        $config = [
163
            'provider' => 'stripe',
164
            'api_key' => $paymentsConfig['stripe']['private_api_key'],
165
            'pci_mode' => $pciMode,
166
        ];
167
168
        $containerBuilder->setParameter('parthenon_billing_config_frontend_info', $paymentsConfig['stripe']['public_api_key']);
169
170
        if (isset($paymentsConfig['stripe']['payment_methods'])) {
171
            $config['payment_methods'] = $paymentsConfig['stripe']['payment_methods'];
172
        }
173
174
        if (isset($paymentsConfig['return_url'])) {
175
            $config['success_url'] = $paymentsConfig['return_url'];
176
            $config['cancel_url'] = $paymentsConfig['return_url'];
177
        }
178
179
        if (isset($paymentsConfig['cancel_url'])) {
180
            $config['cancel_url'] = $paymentsConfig['cancel_url'];
181
        }
182
183
        return $config;
184
    }
185
186
    protected function handleAdyen(array $paymentsConfig, ContainerBuilder $containerBuilder): array
187
    {
188
        if (!isset($paymentsConfig['adyen']['api_key'])) {
189
            throw new ParameterNotSetException('billing.payments.adyen.api_key must be set.');
190
        }
191
        if (!isset($paymentsConfig['adyen']['merchant_account'])) {
192
            throw new ParameterNotSetException('billing.payments.adyen.merchant_account must be set.');
193
        }
194
195
        $pciMode = false;
196
        if (isset($paymentsConfig['pci_mode'])) {
197
            $pciMode = $paymentsConfig['pci_mode'];
198
        }
199
200
        $testMode = true;
201
        if (isset($paymentsConfig['adyen']['test_mode'])) {
202
            $testMode = $paymentsConfig['adyen']['test_mode'];
203
        }
204
205
        $config = [
206
            'provider' => 'adyen',
207
            'api_key' => $paymentsConfig['adyen']['api_key'],
208
            'merchant_account' => $paymentsConfig['adyen']['merchant_account'],
209
            'pci_mode' => $pciMode,
210
            'test_mode' => $testMode,
211
        ];
212
213
        if ($paymentsConfig['adyen']['prefix']) {
214
            $config['prefix'] = $paymentsConfig['adyen']['prefix'];
215
        }
216
217
        if (isset($paymentsConfig['return_url'])) {
218
            $config['return_url'] = $paymentsConfig['return_url'];
219
        }
220
221
        $containerBuilder->setParameter('parthenon_billing_config_frontend_info', $paymentsConfig['stripe']['cse_url']);
222
223
        return $config;
224
    }
225
226
    private function getPlansNode(): NodeDefinition
227
    {
228
        $treeBuilder = new TreeBuilder('plan');
229
        $node = $treeBuilder->getRootNode();
230
231
        /** @var ArrayNodeDefinition $planNode */
232
        $planNode = $node
233
            ->requiresAtLeastOneElement()
234
            ->useAttributeAsKey('name')
235
            ->prototype('array');
236
237
        $planNode
238
            ->fixXmlConfig('limits')
239
                ->children()
240
                    ->booleanNode('is_free')->defaultFalse()->end()
241
                    ?->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

241
                    ?->/** @scrutinizer ignore-call */ booleanNode('is_per_seat')->defaultFalse()->end()
Loading history...
242
                    ?->scalarNode('user_count')->end()
243
244
                    ->arrayNode('features')
245
                        ->scalarPrototype()->end()
246
                    ->end()
247
                    ->arrayNode('limit')
248
                        ->useAttributeAsKey('name')
249
                        ->prototype('array')
250
                        ->children()
251
                            ->integerNode('limit')->end()
252
                            ->scalarNode('description')->end()
253
                        ->end()
254
                    ->end()
255
                ->end()
256
            ->append($this->buildPricesNode())
257
            ->end();
258
259
        return $node;
260
    }
261
}
262