Plugin Directory

Changeset 3306095


Ignore:
Timestamp:
06/04/2025 01:23:53 AM (10 months ago)
Author:
withflex
Message:

Add 3.0.0

Location:
pay-with-flex/trunk
Files:
14 added
7 deleted
18 edited

Legend:

Unmodified
Added
Removed
  • pay-with-flex/trunk/composer.lock

    r3301881 r3306095  
    608608        {
    609609            "name": "symfony/options-resolver",
    610             "version": "v7.2.0",
     610            "version": "v7.3.0",
    611611            "source": {
    612612                "type": "git",
    613613                "url": "https://github.com/symfony/options-resolver.git",
    614                 "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
    615             },
    616             "dist": {
    617                 "type": "zip",
    618                 "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
    619                 "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
     614                "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca"
     615            },
     616            "dist": {
     617                "type": "zip",
     618                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca",
     619                "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca",
    620620                "shasum": ""
    621621            },
     
    655655            ],
    656656            "support": {
    657                 "source": "https://github.com/symfony/options-resolver/tree/v7.2.0"
     657                "source": "https://github.com/symfony/options-resolver/tree/v7.3.0"
    658658            },
    659659            "funding": [
     
    671671                }
    672672            ],
    673             "time": "2024-11-20T11:17:29+00:00"
     673            "time": "2025-04-04T13:12:05+00:00"
    674674        },
    675675        {
  • pay-with-flex/trunk/pay-with-flex.php

    r3301881 r3306095  
    33 * Plugin Name:      Flex HSA/FSA Payments
    44 * Description:      Accept HSA/FSA payments directly in the checkout flow.
    5  * Version:          2.2.0
     5 * Version:          3.0.0
    66 * Plugin URI:       https://wordpress.org/plugins/pay-with-flex/
    77 * Author:           Flex
     
    2424use Flex\Exception\FlexException;
    2525use Flex\PaymentGateway;
    26 use Flex\Resource\LineItem;
     26use Flex\Resource\CheckoutSession\LineItem;
    2727use Flex\Resource\Price;
    2828use Flex\Resource\Product;
     
    6363
    6464    if ( null === $hub ) {
    65         $data = get_plugin_data( __FILE__ );
     65        $data = get_plugin_data(
     66            plugin_file: __FILE__,
     67            translate: false
     68        );
    6669
    6770        $client = ClientBuilder::create(
     
    7477                // Exclude any events that are not "in app" as defined above.
    7578                'before_send'          => function ( Event $event ): ?Event {
     79                    // Allow users to opt-out of telemetry.
     80                    if ( defined( 'FLEX_TELEMETRY' ) && false === \FLEX_TELEMETRY ) {
     81                        return null;
     82                    }
     83
    7684                    $trace = $event->getStacktrace();
    7785                    $exceptions = $event->getExceptions();
  • pay-with-flex/trunk/readme.txt

    r3301881 r3306095  
    44Requires at least: 6.8
    55Tested up to: 6.8
    6 Stable tag: 2.2.0
     6Stable tag: 3.0.0
    77Requires PHP: 8.1
    88License: GPLv3 or later
     
    5656== Changelog ==
    5757
     58= 3.0.0 =
     59* Added support fro processing refunds from within WooCommerce.
     60* Added the `FLEX_TELEMETRY` constant which allows users to opt-out of telemetry by setting the constant to `false`.
     61* Fixed `PHP Notice: Function _load_textdomain_just_in_time was called incorrectly.`
     62
    5863= 2.2.0 =
    5964* Added support for [WooCommerce block-based checkout](https://woocommerce.com/checkout-blocks/).
  • pay-with-flex/trunk/src/Controller/OrderController.php

    r3296637 r3306095  
    1111
    1212use Automattic\WooCommerce\Enums\OrderStatus;
    13 use Flex\Resource\CheckoutSession;
    14 use Flex\Resource\CheckoutSessionStatus;
     13use Flex\Resource\CheckoutSession\CheckoutSession;
     14use Flex\Resource\CheckoutSession\Status as CheckoutSessionStatus;
    1515use Flex\Resource\ResourceAction;
    1616
  • pay-with-flex/trunk/src/Controller/WebhookController.php

    r3296637 r3306095  
    1111
    1212use Automattic\WooCommerce\Enums\OrderStatus;
    13 use Flex\Resource\CheckoutSession;
     13use Flex\Exception\FlexException;
     14use Flex\Resource\CheckoutSession\CheckoutSession;
     15use Flex\Resource\Refund;
     16use Flex\Resource\WebhookEvent;
    1417use Flex\Resource\Webhook;
     18use Sentry\Breadcrumb;
    1519
    1620use function Flex\payment_gateway;
     21use function Flex\sentry;
    1722
    1823/**
     
    2025 */
    2126class WebhookController extends Controller {
    22 
    23     protected const CHECKOUT_SESSION_COMPLETED = 'checkout.session.completed';
    2427
    2528    /**
     
    120123        $data = $request->get_json_params();
    121124
     125        sentry()->addBreadcrumb(
     126            new Breadcrumb(
     127                category: 'event',
     128                level: Breadcrumb::LEVEL_INFO,
     129                type: Breadcrumb::TYPE_DEFAULT,
     130                metadata: $data,
     131            )
     132        );
     133
    122134        if ( ! isset( $data['event_type'] ) ) {
    123             $this->logger->error(
    124                 '[Flex] Webhook Event missing event_type',
    125                 $context,
    126             );
     135            $error = new FlexException( 'Webhook event missing event_type' );
     136            $this->logger->error( $error->getMessage(), $context );
     137            sentry()->captureException( $error );
    127138            return new \WP_REST_Response(
    128139                data: array(
     
    133144        }
    134145
    135         if ( self::CHECKOUT_SESSION_COMPLETED !== $data['event_type'] ) {
    136             $context['event_type'] = $data['event_type'];
    137 
    138             $this->logger->error(
    139                 '[Flex] Cannot handle event type',
    140                 $context,
    141             );
     146        $context['event_type'] = $data['event_type'];
     147
     148        $type = WebhookEvent::tryFrom( $data['event_type'] );
     149
     150        if ( null === $type ) {
     151            $error = new FlexException( 'Cannot handle event type' );
     152            $this->logger->error( $error->getMessage(), $context );
     153            sentry()->captureException( $error );
    142154            return new \WP_REST_Response(
    143155                data: array(
     
    148160        }
    149161
    150         if ( ! isset( $data['object']['checkout_session'] ) ) {
    151             $this->logger->error(
    152                 '[Flex] Webhook Event missing checkout session',
    153                 $context,
    154             );
    155             return new \WP_REST_Response(
    156                 data: array(
    157                     'error' => 'Cannot handle webhook of type ' . esc_html( $data['event_type'] ),
    158                 ),
    159                 status: 422
    160             );
    161         }
    162 
    163         $received = CheckoutSession::from_flex( $data['object']['checkout_session'] );
    164 
    165         $context['checkout_session_id'] = $received->id();
    166 
    167         $order = $received->wc();
    168 
    169         if ( null === $order ) {
    170             $this->logger->error(
    171                 '[Flex] WooCommerce order does not exist for the given checkout_session_id',
    172                 $context,
    173             );
    174             return new \WP_REST_Response(
    175                 data: array(
    176                     'error' => 'WooCommerce order does not exist for the given checkout_session_id',
    177                 ),
    178                 status: 422
    179             );
    180         }
    181 
    182         $context['order_id'] = $order->get_id();
    183 
    184         // If the order has not yet been marked as complete, do so.
    185         if ( OrderStatus::PENDING === $order->get_status() ) {
    186             $order->payment_complete( $received->id() );
    187         }
    188 
    189         $received->apply_to( $order );
    190         $order->save();
     162        if ( WebhookEvent::CHECKOUT_SESSION_COMPLETED === $type ) {
     163            if ( ! isset( $data['object']['checkout_session'] ) || ! is_array( $data['object']['checkout_session'] ) ) {
     164                $error = new FlexException( 'Event missing checkout session' );
     165                $this->logger->error( $error->getMessage(), $context );
     166                sentry()->captureException( $error );
     167                return new \WP_REST_Response(
     168                    data: array(
     169                        'error' => 'Event missing checkout session',
     170                    ),
     171                    status: 422
     172                );
     173            }
     174
     175            $received = CheckoutSession::from_flex( $data['object']['checkout_session'] );
     176
     177            $context['checkout_session_id'] = $received->id();
     178
     179            $order = $received->wc();
     180
     181            if ( null === $order ) {
     182                $error = new FlexException( 'WooCommerce order does not exist for the given checkout_session_id' );
     183                $this->logger->error( $error->getMessage(), $context );
     184                sentry()->captureException( $error );
     185                return new \WP_REST_Response(
     186                    data: array(
     187                        'error' => 'WooCommerce order does not exist for the given checkout_session_id',
     188                    ),
     189                    status: 422
     190                );
     191            }
     192
     193            $context['order_id'] = $order->get_id();
     194
     195            // If the order has not yet been marked as complete, do so.
     196            if ( OrderStatus::PENDING === $order->get_status() ) {
     197                $order->payment_complete( $received->id() );
     198            }
     199
     200            $received->apply_to( $order );
     201            $order->save();
     202        } elseif ( WebhookEvent::REFUND_UPDATED === $type ) {
     203            if ( ! isset( $data['object']['refund'] ) || ! is_array( $data['object']['refund'] ) ) {
     204                $error = new FlexException( 'Event missing refund' );
     205                $this->logger->error( $error->getMessage(), $context );
     206                sentry()->captureException( $error );
     207                return new \WP_REST_Response(
     208                    data: array(
     209                        'error' => 'Event missing refund',
     210                    ),
     211                    status: 422
     212                );
     213            }
     214
     215            $refund = Refund::from_flex( $data['object']['refund'] );
     216
     217            if ( $refund->status()?->failure() ) {
     218                $wc_refund = $refund->wc();
     219                if ( null === $wc_refund ) {
     220                    $error = new FlexException( 'WooCommerce refund does not exist for the given refund_id' );
     221                    $this->logger->error( $error->getMessage(), $context );
     222                    sentry()->captureException( $error );
     223                    return new \WP_REST_Response(
     224                        data: array(
     225                            'error' => 'WooCommerce refund does not exist for the given refund_id',
     226                        ),
     227                        status: 422
     228                    );
     229                }
     230
     231                $order = wc_get_order( $wc_refund->get_parent_id() );
     232
     233                if ( false === $order ) {
     234                    $error = new FlexException( 'WooCommerce order does not exist for the given refund_id' );
     235                    $this->logger->error( $error->getMessage(), $context );
     236                    sentry()->captureException( $error );
     237                    return new \WP_REST_Response(
     238                        data: array(
     239                            'error' => 'WooCommerce order does not exist for the given refund_id',
     240                        ),
     241                        status: 422
     242                    );
     243                }
     244
     245                $note = sprintf(
     246                        // translators: %1$d: refund id.
     247                        // translators: %2$s: amount of the refund.
     248                        // translators: %3$s: status of the refund.
     249                        // translators: %4$s: Flex Refund ID.
     250                    __( 'Refund %1$d in the amount of %2$s resulted in a status of %3$s in Flex (%4$s) and has been deleted.', 'pay-with-flex' ),
     251                    $wc_refund->get_id(),
     252                    $wc_refund->get_formatted_refund_amount(),
     253                    $refund->status()->value,
     254                    $refund->id(),
     255                );
     256
     257                if ( $wc_refund->delete() ) {
     258                    $order->add_order_note( $note );
     259                }
     260            }
     261        }
    191262
    192263        $this->logger->debug(
  • pay-with-flex/trunk/src/PaymentGateway.php

    r3299082 r3306095  
    1212use Automattic\WooCommerce\Enums\OrderStatus;
    1313use Flex\Exception\FlexException;
    14 use Flex\Resource\CheckoutSession;
     14use Flex\Resource\CheckoutSession\CheckoutSession;
     15use Flex\Resource\CheckoutSession\Refund\Refund;
     16use Flex\Resource\ResourceAction;
    1517use Flex\Resource\Webhook;
     18use Sentry\Breadcrumb;
    1619use Sentry\State\Scope;
    1720
     
    2427
    2528    /**
     29     * {@inheritdoc}
     30     *
     31     * @var string
     32     */
     33    public $id = 'flex';
     34
     35    /**
     36     * {@inheritdoc}
     37     *
     38     * @var bool
     39     */
     40    public $has_fields = false;
     41
     42    /**
     43     * {@inheritdoc}
     44     *
     45     * @var array
     46     */
     47    public $supports = array( 'products', 'refunds' );
     48
     49    /**
    2650     * Logger.
    2751     *
     
    3660     */
    3761    public function __construct( bool $actions = true ) {
    38         $this->id                 = 'flex';
     62        $this->logger = wc_get_logger();
     63
     64        $this->init_settings();
     65
     66        if ( did_action( 'init' ) ) {
     67            $this->init();
     68        }
     69
     70        if ( $actions ) {
     71            // Translation cannot be used until after `init`.
     72            if ( ! did_action( 'init' ) ) {
     73                add_action( 'init', array( $this, 'init' ) );
     74            }
     75            // @phpstan-ignore return.void
     76            add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
     77        }
     78    }
     79
     80    /**
     81     * Initialize the Payment Gateway.
     82     */
     83    public function init() {
    3984        $this->title              = __( 'Flex | Pay with HSA/FSA', 'pay-with-flex' );
    4085        $this->method_title       = __( 'Flex', 'pay-with-flex' );
    4186        $this->method_description = __( 'Accept HSA/FSA payments directly in the checkout flow.', 'pay-with-flex' );
    42         $this->has_fields         = false;
    43         $this->logger             = wc_get_logger();
    44 
    4587        $this->init_form_fields();
    46         $this->init_settings();
    47 
    48         if ( $actions ) {
    49             // @phpstan-ignore return.void
    50             add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
    51         }
    5288    }
    5389
     
    74110                'order_id' => $order_id,
    75111            )
     112        );
     113
     114        sentry()->configureScope(
     115            function ( Scope $scope ) use ( $order_id ): void {
     116                $scope->addBreadcrumb(
     117                    new Breadcrumb(
     118                        level: Breadcrumb::LEVEL_INFO,
     119                        type: Breadcrumb::TYPE_DEFAULT,
     120                        category: 'payment',
     121                        message: 'Payment',
     122                        metadata: array(
     123                            'order_id' => $order_id,
     124                        ),
     125                    )
     126                );
     127            },
    76128        );
    77129
     
    176228            );
    177229        }
     230    }
     231
     232    /**
     233     * {@inheritdoc}
     234     *
     235     * @param  int        $order_id Order ID.
     236     * @param  float|null $amount Refund amount.
     237     * @param  string     $reason Refund reason.
     238     * @return bool|\WP_Error True or false based on success, or a WP_Error object.
     239     * @throws FlexException If we can intuit the refund from the amount and reason.
     240     * @throws \Exception When a FlexException is caught.
     241     */
     242    public function process_refund( $order_id, $amount = null, $reason = '' ) {
     243        sentry()->configureScope(
     244            function ( Scope $scope ) use ( $order_id, $amount, $reason ): void {
     245                $scope->addBreadcrumb(
     246                    new Breadcrumb(
     247                        level: Breadcrumb::LEVEL_INFO,
     248                        type: Breadcrumb::TYPE_DEFAULT,
     249                        category: 'refund',
     250                        message: 'Refund',
     251                        metadata: array(
     252                            'order_id' => $order_id,
     253                            'amount'   => $amount,
     254                            'reason'   => $reason,
     255                        ),
     256                    )
     257                );
     258            },
     259        );
     260
     261        try {
     262            $order = wc_get_order( $order_id );
     263
     264            sentry()->configureScope(
     265                function ( Scope $scope ) use ( $order ): void {
     266                    $scope->setContext(
     267                        'Order',
     268                        $order->get_base_data(),
     269                    );
     270                },
     271            );
     272
     273            $checkout_session = CheckoutSession::from_wc( $order );
     274
     275            sentry()->configureScope(
     276                function ( Scope $scope ) use ( $checkout_session ): void {
     277                    $scope->setTags(
     278                        array(
     279                            'checkout_session'           => $checkout_session->id(),
     280                            'checkout_session.test_mode' => wc_bool_to_string( $checkout_session->test_mode() ),
     281                        )
     282                    );
     283
     284                    $scope->setContext( 'Checkout Session', $checkout_session->jsonSerialize() );
     285                },
     286            );
     287
     288            $refunds = $order->get_refunds();
     289
     290            /**
     291             * Retrieve the first refund from the list.
     292             *
     293             * @var \WC_Order_Refund|false
     294             */
     295            $refund = reset( $refunds );
     296
     297            if ( false === $refund ) {
     298                throw new FlexException( 'Unable to retrieve refund' );
     299            }
     300
     301            sentry()->configureScope(
     302                function ( Scope $scope ) use ( $refund ): void {
     303                    $scope->setContext(
     304                        'Refund',
     305                        $refund->get_base_data()
     306                    );
     307                },
     308            );
     309
     310            if ( $refund->get_amount() !== $amount || $refund->get_reason() !== $reason ) {
     311                throw new FlexException( 'Refund amount or reason does not match retrieved refund.' );
     312            }
     313
     314            // Ensure the Webhooks are up to date.
     315            $webhook = Webhook::from_wc( $this );
     316            $webhook->exec( $webhook->needs() );
     317
     318            Refund::from_wc( $refund )->exec( ResourceAction::CREATE );
     319
     320        } catch ( FlexException $previous ) {
     321            $this->logger->error(
     322                $previous->getMessage(),
     323                array_merge(
     324                    $previous->getContext(),
     325                    array(
     326                        'refund_id' => $order_id,
     327                    ),
     328                ),
     329            );
     330
     331            sentry()->captureException(
     332                new \Exception(
     333                    message: 'Refund processing failure',
     334                    previous: $previous,
     335                )
     336            );
     337
     338            if ( true === \WP_DEBUG ) {
     339                // Throw the underlying error message which will be displayed the user.
     340                if ( true === \WP_DEBUG_DISPLAY ) {
     341                    throw $previous;
     342                }
     343
     344                // Log the underlying error message.
     345                error_log( $previous->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     346            }
     347
     348            throw new \Exception(
     349                message: "We're sorry, there was a problem while attempting to processes the refund with Flex. Please try again later.",
     350                previous: $previous, // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
     351            );
     352        }
     353
     354        return true;
    178355    }
    179356
  • pay-with-flex/trunk/src/Resource/Resource.php

    r3299082 r3306095  
    4646     */
    4747    public static function currency_to_unit_amount( int|float|string $value ): int {
     48        if ( is_string( $value ) ) {
     49            // Remove a negative sign, currency symbols, etc.
     50            $currency_symbol = html_entity_decode( get_woocommerce_currency_symbol() );
     51            $value           = trim( $value, $currency_symbol . "- \n\r\t\v\0" );
     52        } else {
     53            $value = abs( $value );
     54        }
     55
    4856        // Split the string based on the decimal separator.
    49         $parts = explode(
    50             wc_get_price_decimal_separator(),
    51             // Convert to a string, remove the currency symbol if there is one, and trim.
    52             trim( str_replace( get_woocommerce_currency_symbol(), '', (string) $value ) )
    53         );
     57        $parts = explode( wc_get_price_decimal_separator(), (string) $value );
    5458
    5559        return intval(
  • pay-with-flex/trunk/src/Resource/ShippingRate.php

    r3296637 r3306095  
    11<?php
    22/**
    3  * Flex Checkout Session Line Item
     3 * Flex Shipping Rate.
    44 *
    55 * @package Flex
  • pay-with-flex/trunk/src/Resource/Webhook.php

    r3296637 r3306095  
    2424    protected const KEY_HASH           = 'webhoook_hash';
    2525    protected const KEY_SIGNING_SECRET = 'webhoook_signing_secret';
    26     protected const EVENTS             = array( 'checkout.session.completed' );
     26
     27    /**
     28     * Events
     29     *
     30     * @var WebhookEvent[]
     31     */
     32    protected $events;
    2733
    2834    /**
     
    3440
    3541    /**
    36      * Creates a checkout session line item
    37      *
    38      * @param string   $url The url the webhooks should subscribe too.
    39      * @param ?string  $id The id of the webhook.
    40      * @param ?string  $signing_secret The signing secret of the webhook.
    41      * @param string[] $events The events the webhook is subscribed too.
    42      * @param ?bool    $test_mode Whether the webhook was created in test mode.
     42     * Creates a webhook.
     43     *
     44     * @param string          $url The url the webhooks should subscribe too.
     45     * @param ?string         $id The id of the webhook.
     46     * @param ?string         $signing_secret The signing secret of the webhook.
     47     * @param ?WebhookEvent[] $events The events the webhook is subscribed too.
     48     * @param ?bool           $test_mode Whether the webhook was created in test mode.
     49     * @throws \LogicException If $events does not consist of instances of `WebhookEvent`.
    4350     */
    4451    public function __construct(
     
    4653        protected ?string $id = null,
    4754        protected ?string $signing_secret = null,
    48         protected array $events = self::EVENTS,
     55        ?array $events = null,
    4956        protected ?bool $test_mode = null,
    50     ) {}
     57    ) {
     58        if ( null !== $events && ! array_all( $events, fn ( $e ) => $e instanceof WebhookEvent ) ) {
     59            throw new \LogicException( 'Webhook::$events must only contain instances of WebhookEvent' );
     60        }
     61
     62        if ( null === $events ) {
     63            $events = WebhookEvent::cases();
     64        }
     65
     66        $this->events = $events;
     67    }
    5168
    5269    /**
     
    114131        $this->id             = $webhook['webhook_id'] ?? $this->id;
    115132        $this->url            = $webhook['url'] ?? $this->url;
    116         $this->events         = $webhook['events'] ?? $this->events;
    117133        $this->signing_secret = $webhook['signing_secret'] ?? $this->signing_secret;
    118134        $this->test_mode      = $webhook['test_mode'] ?? $this->test_mode;
     135
     136        if ( ! empty( $webhook['events'] ) && is_array( $webhook['events'] ) ) {
     137            $this->events = array_reduce(
     138                $webhook['events'],
     139                function ( array $acc, string $value ): array {
     140                    $event = WebhookEvent::tryFrom( $value );
     141                    if ( null !== $event ) {
     142                        $acc[] = $event;
     143                    }
     144                    return $acc;
     145                },
     146                array(),
     147            );
     148        }
    119149    }
    120150
  • pay-with-flex/trunk/vendor/composer/autoload_classmap.php

    r3301881 r3306095  
    2121    'Flex\\PaymentGateway' => $baseDir . '/src/PaymentGateway.php',
    2222    'Flex\\PaymentMethod' => $baseDir . '/src/PaymentMethod.php',
    23     'Flex\\Resource\\CheckoutSession' => $baseDir . '/src/Resource/CheckoutSession.php',
    24     'Flex\\Resource\\CheckoutSessionMode' => $baseDir . '/src/Resource/CheckoutSessionMode.php',
    25     'Flex\\Resource\\CheckoutSessionShippingOptions' => $baseDir . '/src/Resource/CheckoutSessionShippingOptions.php',
    26     'Flex\\Resource\\CheckoutSessionStatus' => $baseDir . '/src/Resource/CheckoutSessionStatus.php',
    27     'Flex\\Resource\\CheckoutSessionTaxRate' => $baseDir . '/src/Resource/CheckoutSessionTaxRate.php',
    28     'Flex\\Resource\\CustomerDefaults' => $baseDir . '/src/Resource/CustomerDefaults.php',
    29     'Flex\\Resource\\LineItem' => $baseDir . '/src/Resource/LineItem.php',
     23    'Flex\\Resource\\CheckoutSession\\CheckoutSession' => $baseDir . '/src/Resource/CheckoutSession/CheckoutSession.php',
     24    'Flex\\Resource\\CheckoutSession\\CustomerDefaults' => $baseDir . '/src/Resource/CheckoutSession/CustomerDefaults.php',
     25    'Flex\\Resource\\CheckoutSession\\LineItem' => $baseDir . '/src/Resource/CheckoutSession/LineItem.php',
     26    'Flex\\Resource\\CheckoutSession\\Mode' => $baseDir . '/src/Resource/CheckoutSession/Mode.php',
     27    'Flex\\Resource\\CheckoutSession\\Refund\\LineItem' => $baseDir . '/src/Resource/CheckoutSession/Refund/LineItem.php',
     28    'Flex\\Resource\\CheckoutSession\\Refund\\Refund' => $baseDir . '/src/Resource/CheckoutSession/Refund/Refund.php',
     29    'Flex\\Resource\\CheckoutSession\\ShippingOptions' => $baseDir . '/src/Resource/CheckoutSession/ShippingOptions.php',
     30    'Flex\\Resource\\CheckoutSession\\Status' => $baseDir . '/src/Resource/CheckoutSession/Status.php',
     31    'Flex\\Resource\\CheckoutSession\\TaxRate' => $baseDir . '/src/Resource/CheckoutSession/TaxRate.php',
    3032    'Flex\\Resource\\Price' => $baseDir . '/src/Resource/Price.php',
    3133    'Flex\\Resource\\Product' => $baseDir . '/src/Resource/Product.php',
     34    'Flex\\Resource\\Refund' => $baseDir . '/src/Resource/Refund.php',
     35    'Flex\\Resource\\RefundStatus' => $baseDir . '/src/Resource/RefundStatus.php',
    3236    'Flex\\Resource\\Resource' => $baseDir . '/src/Resource/Resource.php',
    3337    'Flex\\Resource\\ResourceAction' => $baseDir . '/src/Resource/ResourceAction.php',
     
    3539    'Flex\\Resource\\ShippingRate' => $baseDir . '/src/Resource/ShippingRate.php',
    3640    'Flex\\Resource\\Webhook' => $baseDir . '/src/Resource/Webhook.php',
     41    'Flex\\Resource\\WebhookEvent' => $baseDir . '/src/Resource/WebhookEvent.php',
    3742    'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php',
    3843    'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php',
  • pay-with-flex/trunk/vendor/composer/autoload_static.php

    r3301881 r3306095  
    9999        'Flex\\PaymentGateway' => __DIR__ . '/../..' . '/src/PaymentGateway.php',
    100100        'Flex\\PaymentMethod' => __DIR__ . '/../..' . '/src/PaymentMethod.php',
    101         'Flex\\Resource\\CheckoutSession' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession.php',
    102         'Flex\\Resource\\CheckoutSessionMode' => __DIR__ . '/../..' . '/src/Resource/CheckoutSessionMode.php',
    103         'Flex\\Resource\\CheckoutSessionShippingOptions' => __DIR__ . '/../..' . '/src/Resource/CheckoutSessionShippingOptions.php',
    104         'Flex\\Resource\\CheckoutSessionStatus' => __DIR__ . '/../..' . '/src/Resource/CheckoutSessionStatus.php',
    105         'Flex\\Resource\\CheckoutSessionTaxRate' => __DIR__ . '/../..' . '/src/Resource/CheckoutSessionTaxRate.php',
    106         'Flex\\Resource\\CustomerDefaults' => __DIR__ . '/../..' . '/src/Resource/CustomerDefaults.php',
    107         'Flex\\Resource\\LineItem' => __DIR__ . '/../..' . '/src/Resource/LineItem.php',
     101        'Flex\\Resource\\CheckoutSession\\CheckoutSession' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/CheckoutSession.php',
     102        'Flex\\Resource\\CheckoutSession\\CustomerDefaults' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/CustomerDefaults.php',
     103        'Flex\\Resource\\CheckoutSession\\LineItem' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/LineItem.php',
     104        'Flex\\Resource\\CheckoutSession\\Mode' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/Mode.php',
     105        'Flex\\Resource\\CheckoutSession\\Refund\\LineItem' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/Refund/LineItem.php',
     106        'Flex\\Resource\\CheckoutSession\\Refund\\Refund' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/Refund/Refund.php',
     107        'Flex\\Resource\\CheckoutSession\\ShippingOptions' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/ShippingOptions.php',
     108        'Flex\\Resource\\CheckoutSession\\Status' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/Status.php',
     109        'Flex\\Resource\\CheckoutSession\\TaxRate' => __DIR__ . '/../..' . '/src/Resource/CheckoutSession/TaxRate.php',
    108110        'Flex\\Resource\\Price' => __DIR__ . '/../..' . '/src/Resource/Price.php',
    109111        'Flex\\Resource\\Product' => __DIR__ . '/../..' . '/src/Resource/Product.php',
     112        'Flex\\Resource\\Refund' => __DIR__ . '/../..' . '/src/Resource/Refund.php',
     113        'Flex\\Resource\\RefundStatus' => __DIR__ . '/../..' . '/src/Resource/RefundStatus.php',
    110114        'Flex\\Resource\\Resource' => __DIR__ . '/../..' . '/src/Resource/Resource.php',
    111115        'Flex\\Resource\\ResourceAction' => __DIR__ . '/../..' . '/src/Resource/ResourceAction.php',
     
    113117        'Flex\\Resource\\ShippingRate' => __DIR__ . '/../..' . '/src/Resource/ShippingRate.php',
    114118        'Flex\\Resource\\Webhook' => __DIR__ . '/../..' . '/src/Resource/Webhook.php',
     119        'Flex\\Resource\\WebhookEvent' => __DIR__ . '/../..' . '/src/Resource/WebhookEvent.php',
    115120        'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php',
    116121        'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php',
  • pay-with-flex/trunk/vendor/composer/installed.json

    r3301881 r3306095  
    629629        {
    630630            "name": "symfony/options-resolver",
    631             "version": "v7.2.0",
    632             "version_normalized": "7.2.0.0",
     631            "version": "v7.3.0",
     632            "version_normalized": "7.3.0.0",
    633633            "source": {
    634634                "type": "git",
    635635                "url": "https://github.com/symfony/options-resolver.git",
    636                 "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50"
    637             },
    638             "dist": {
    639                 "type": "zip",
    640                 "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
    641                 "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50",
     636                "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca"
     637            },
     638            "dist": {
     639                "type": "zip",
     640                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca",
     641                "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca",
    642642                "shasum": ""
    643643            },
     
    646646                "symfony/deprecation-contracts": "^2.5|^3"
    647647            },
    648             "time": "2024-11-20T11:17:29+00:00",
     648            "time": "2025-04-04T13:12:05+00:00",
    649649            "type": "library",
    650650            "installation-source": "dist",
     
    679679            ],
    680680            "support": {
    681                 "source": "https://github.com/symfony/options-resolver/tree/v7.2.0"
     681                "source": "https://github.com/symfony/options-resolver/tree/v7.3.0"
    682682            },
    683683            "funding": [
  • pay-with-flex/trunk/vendor/composer/installed.php

    r3301881 r3306095  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => 'cc4788f82da3cf84ab509a01c85acd3dd14841e4',
     6        'reference' => 'b52bc3dd944a3f0dc9e1ffb085a3e4f95efe9343',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => 'cc4788f82da3cf84ab509a01c85acd3dd14841e4',
     16            'reference' => 'b52bc3dd944a3f0dc9e1ffb085a3e4f95efe9343',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
     
    114114        ),
    115115        'symfony/options-resolver' => array(
    116             'pretty_version' => 'v7.2.0',
    117             'version' => '7.2.0.0',
    118             'reference' => '7da8fbac9dcfef75ffc212235d76b2754ce0cf50',
     116            'pretty_version' => 'v7.3.0',
     117            'version' => '7.3.0.0',
     118            'reference' => 'afb9a8038025e5dbc657378bfab9198d75f10fca',
    119119            'type' => 'library',
    120120            'install_path' => __DIR__ . '/../symfony/options-resolver',
  • pay-with-flex/trunk/vendor/composer/jetpack_autoload_classmap.php

    r3301881 r3306095  
    7575        'path'    => $baseDir . '/src/PaymentMethod.php'
    7676    ),
    77     'Flex\\Resource\\CheckoutSession' => array(
    78         'version' => 'dev-main',
    79         'path'    => $baseDir . '/src/Resource/CheckoutSession.php'
    80     ),
    81     'Flex\\Resource\\CheckoutSessionMode' => array(
    82         'version' => 'dev-main',
    83         'path'    => $baseDir . '/src/Resource/CheckoutSessionMode.php'
    84     ),
    85     'Flex\\Resource\\CheckoutSessionShippingOptions' => array(
    86         'version' => 'dev-main',
    87         'path'    => $baseDir . '/src/Resource/CheckoutSessionShippingOptions.php'
    88     ),
    89     'Flex\\Resource\\CheckoutSessionStatus' => array(
    90         'version' => 'dev-main',
    91         'path'    => $baseDir . '/src/Resource/CheckoutSessionStatus.php'
    92     ),
    93     'Flex\\Resource\\CheckoutSessionTaxRate' => array(
    94         'version' => 'dev-main',
    95         'path'    => $baseDir . '/src/Resource/CheckoutSessionTaxRate.php'
    96     ),
    97     'Flex\\Resource\\CustomerDefaults' => array(
    98         'version' => 'dev-main',
    99         'path'    => $baseDir . '/src/Resource/CustomerDefaults.php'
    100     ),
    101     'Flex\\Resource\\LineItem' => array(
    102         'version' => 'dev-main',
    103         'path'    => $baseDir . '/src/Resource/LineItem.php'
     77    'Flex\\Resource\\CheckoutSession\\CheckoutSession' => array(
     78        'version' => 'dev-main',
     79        'path'    => $baseDir . '/src/Resource/CheckoutSession/CheckoutSession.php'
     80    ),
     81    'Flex\\Resource\\CheckoutSession\\CustomerDefaults' => array(
     82        'version' => 'dev-main',
     83        'path'    => $baseDir . '/src/Resource/CheckoutSession/CustomerDefaults.php'
     84    ),
     85    'Flex\\Resource\\CheckoutSession\\LineItem' => array(
     86        'version' => 'dev-main',
     87        'path'    => $baseDir . '/src/Resource/CheckoutSession/LineItem.php'
     88    ),
     89    'Flex\\Resource\\CheckoutSession\\Mode' => array(
     90        'version' => 'dev-main',
     91        'path'    => $baseDir . '/src/Resource/CheckoutSession/Mode.php'
     92    ),
     93    'Flex\\Resource\\CheckoutSession\\Refund\\LineItem' => array(
     94        'version' => 'dev-main',
     95        'path'    => $baseDir . '/src/Resource/CheckoutSession/Refund/LineItem.php'
     96    ),
     97    'Flex\\Resource\\CheckoutSession\\Refund\\Refund' => array(
     98        'version' => 'dev-main',
     99        'path'    => $baseDir . '/src/Resource/CheckoutSession/Refund/Refund.php'
     100    ),
     101    'Flex\\Resource\\CheckoutSession\\ShippingOptions' => array(
     102        'version' => 'dev-main',
     103        'path'    => $baseDir . '/src/Resource/CheckoutSession/ShippingOptions.php'
     104    ),
     105    'Flex\\Resource\\CheckoutSession\\Status' => array(
     106        'version' => 'dev-main',
     107        'path'    => $baseDir . '/src/Resource/CheckoutSession/Status.php'
     108    ),
     109    'Flex\\Resource\\CheckoutSession\\TaxRate' => array(
     110        'version' => 'dev-main',
     111        'path'    => $baseDir . '/src/Resource/CheckoutSession/TaxRate.php'
    104112    ),
    105113    'Flex\\Resource\\Price' => array(
     
    111119        'path'    => $baseDir . '/src/Resource/Product.php'
    112120    ),
     121    'Flex\\Resource\\Refund' => array(
     122        'version' => 'dev-main',
     123        'path'    => $baseDir . '/src/Resource/Refund.php'
     124    ),
     125    'Flex\\Resource\\RefundStatus' => array(
     126        'version' => 'dev-main',
     127        'path'    => $baseDir . '/src/Resource/RefundStatus.php'
     128    ),
    113129    'Flex\\Resource\\Resource' => array(
    114130        'version' => 'dev-main',
     
    130146        'version' => 'dev-main',
    131147        'path'    => $baseDir . '/src/Resource/Webhook.php'
     148    ),
     149    'Flex\\Resource\\WebhookEvent' => array(
     150        'version' => 'dev-main',
     151        'path'    => $baseDir . '/src/Resource/WebhookEvent.php'
    132152    ),
    133153    'GuzzleHttp\\Psr7\\AppendStream' => array(
     
    808828    ),
    809829    'Symfony\\Component\\OptionsResolver\\Debug\\OptionsResolverIntrospector' => array(
    810         'version' => '7.2.0.0',
     830        'version' => '7.3.0.0',
    811831        'path'    => $vendorDir . '/symfony/options-resolver/Debug/OptionsResolverIntrospector.php'
    812832    ),
    813833    'Symfony\\Component\\OptionsResolver\\Exception\\AccessException' => array(
    814         'version' => '7.2.0.0',
     834        'version' => '7.3.0.0',
    815835        'path'    => $vendorDir . '/symfony/options-resolver/Exception/AccessException.php'
    816836    ),
    817837    'Symfony\\Component\\OptionsResolver\\Exception\\ExceptionInterface' => array(
    818         'version' => '7.2.0.0',
     838        'version' => '7.3.0.0',
    819839        'path'    => $vendorDir . '/symfony/options-resolver/Exception/ExceptionInterface.php'
    820840    ),
    821841    'Symfony\\Component\\OptionsResolver\\Exception\\InvalidArgumentException' => array(
    822         'version' => '7.2.0.0',
     842        'version' => '7.3.0.0',
    823843        'path'    => $vendorDir . '/symfony/options-resolver/Exception/InvalidArgumentException.php'
    824844    ),
    825845    'Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException' => array(
    826         'version' => '7.2.0.0',
     846        'version' => '7.3.0.0',
    827847        'path'    => $vendorDir . '/symfony/options-resolver/Exception/InvalidOptionsException.php'
    828848    ),
    829849    'Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException' => array(
    830         'version' => '7.2.0.0',
     850        'version' => '7.3.0.0',
    831851        'path'    => $vendorDir . '/symfony/options-resolver/Exception/MissingOptionsException.php'
    832852    ),
    833853    'Symfony\\Component\\OptionsResolver\\Exception\\NoConfigurationException' => array(
    834         'version' => '7.2.0.0',
     854        'version' => '7.3.0.0',
    835855        'path'    => $vendorDir . '/symfony/options-resolver/Exception/NoConfigurationException.php'
    836856    ),
    837857    'Symfony\\Component\\OptionsResolver\\Exception\\NoSuchOptionException' => array(
    838         'version' => '7.2.0.0',
     858        'version' => '7.3.0.0',
    839859        'path'    => $vendorDir . '/symfony/options-resolver/Exception/NoSuchOptionException.php'
    840860    ),
    841861    'Symfony\\Component\\OptionsResolver\\Exception\\OptionDefinitionException' => array(
    842         'version' => '7.2.0.0',
     862        'version' => '7.3.0.0',
    843863        'path'    => $vendorDir . '/symfony/options-resolver/Exception/OptionDefinitionException.php'
    844864    ),
    845865    'Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException' => array(
    846         'version' => '7.2.0.0',
     866        'version' => '7.3.0.0',
    847867        'path'    => $vendorDir . '/symfony/options-resolver/Exception/UndefinedOptionsException.php'
    848868    ),
    849869    'Symfony\\Component\\OptionsResolver\\OptionConfigurator' => array(
    850         'version' => '7.2.0.0',
     870        'version' => '7.3.0.0',
    851871        'path'    => $vendorDir . '/symfony/options-resolver/OptionConfigurator.php'
    852872    ),
    853873    'Symfony\\Component\\OptionsResolver\\Options' => array(
    854         'version' => '7.2.0.0',
     874        'version' => '7.3.0.0',
    855875        'path'    => $vendorDir . '/symfony/options-resolver/Options.php'
    856876    ),
    857877    'Symfony\\Component\\OptionsResolver\\OptionsResolver' => array(
    858         'version' => '7.2.0.0',
     878        'version' => '7.3.0.0',
    859879        'path'    => $vendorDir . '/symfony/options-resolver/OptionsResolver.php'
    860880    ),
  • pay-with-flex/trunk/vendor/symfony/options-resolver/CHANGELOG.md

    r3299085 r3306095  
    11CHANGELOG
    22=========
     3
     47.3
     5---
     6
     7 * Support union type in `OptionResolver::setAllowedTypes()` method
     8 * Add `OptionsResolver::setOptions()` and `OptionConfigurator::options()` methods
     9 * Deprecate defining nested options via `setDefault()`, use `setOptions()` instead
    310
    4116.4
    512---
    613
    7 * Improve message with full path on invalid type in nested option
     14 * Improve message with full path on invalid type in nested option
    815
    9166.3
  • pay-with-flex/trunk/vendor/symfony/options-resolver/Debug/OptionsResolverIntrospector.php

    r3299085 r3306095  
    102102        return ($this->get)('deprecated', $option, \sprintf('No deprecation was set for the "%s" option.', $option));
    103103    }
     104
     105    /**
     106     * @return \Closure[]
     107     *
     108     * @throws NoConfigurationException when no nested option is configured
     109     */
     110    public function getNestedOptions(string $option): array
     111    {
     112        return ($this->get)('nested', $option, \sprintf('No nested option was set for the "%s" option.', $option));
     113    }
    104114}
  • pay-with-flex/trunk/vendor/symfony/options-resolver/OptionConfigurator.php

    r3299085 r3306095  
    144144        return $this;
    145145    }
     146
     147    /**
     148     * Defines nested options.
     149     *
     150     * @param \Closure(OptionsResolver $resolver, Options $parent): void $nested
     151     *
     152     * @return $this
     153     */
     154    public function options(\Closure $nested): static
     155    {
     156        $this->resolver->setOptions($this->name, $nested);
     157
     158        return $this;
     159    }
    146160}
  • pay-with-flex/trunk/vendor/symfony/options-resolver/OptionsResolver.php

    r3299085 r3306095  
    6666
    6767    /**
     68     * BC layer. Remove in Symfony 8.0.
     69     *
     70     * @var array<string, true>
     71     */
     72    private array $deprecatedNestedOptions = [];
     73
     74    /**
    6875     * The names of required options.
    6976     */
     
    179186     * sub-classes.
    180187     *
    181      * If you want to define nested options, you can pass a closure with the
    182      * following signature:
    183      *
    184      *     $options->setDefault('database', function (OptionsResolver $resolver) {
    185      *         $resolver->setDefined(['dbname', 'host', 'port', 'user', 'pass']);
    186      *     }
    187      *
    188      * To get access to the parent options, add a second argument to the closure's
    189      * signature:
    190      *
    191      *     function (OptionsResolver $resolver, Options $parent) {
    192      *         // 'default' === $parent['connection']
    193      *     }
    194      *
    195188     * @return $this
    196189     *
     
    227220                $this->defined[$option] = true;
    228221
    229                 // Make sure the option is processed and is not nested anymore
    230                 unset($this->resolved[$option], $this->nested[$option]);
     222                // Make sure the option is processed
     223                unset($this->resolved[$option]);
     224
     225                // BC layer. Remove in Symfony 8.0.
     226                if (isset($this->deprecatedNestedOptions[$option])) {
     227                    unset($this->nested[$option]);
     228                }
    231229
    232230                return $this;
    233231            }
    234232
     233            // Remove in Symfony 8.0.
    235234            if (isset($params[0]) && ($type = $params[0]->getType()) instanceof \ReflectionNamedType && self::class === $type->getName() && (!isset($params[1]) || (($type = $params[1]->getType()) instanceof \ReflectionNamedType && Options::class === $type->getName()))) {
     235                trigger_deprecation('symfony/options-resolver', '7.3', 'Defining nested options via "%s()" is deprecated and will be removed in Symfony 8.0, use "setOptions()" method instead.', __METHOD__);
     236                $this->deprecatedNestedOptions[$option] = true;
     237
    236238                // Store closure for later evaluation
    237239                $this->nested[$option][] = $value;
     
    246248        }
    247249
    248         // This option is not lazy nor nested anymore
    249         unset($this->lazy[$option], $this->nested[$option]);
     250        // This option is not lazy anymore
     251        unset($this->lazy[$option]);
     252
     253        // BC layer. Remove in Symfony 8.0.
     254        if (isset($this->deprecatedNestedOptions[$option])) {
     255            unset($this->nested[$option]);
     256        }
    250257
    251258        // Yet undefined options can be marked as resolved, because we only need
     
    402409    {
    403410        return array_keys($this->defined);
     411    }
     412
     413    /**
     414     * Defines nested options.
     415     *
     416     * @param \Closure(self $resolver, Options $parent): void $nested
     417     *
     418     * @return $this
     419     */
     420    public function setOptions(string $option, \Closure $nested): static
     421    {
     422        if ($this->locked) {
     423            throw new AccessException('Nested options cannot be defined from a lazy option or normalizer.');
     424        }
     425
     426        // Store closure for later evaluation
     427        $this->nested[$option][] = $nested;
     428        $this->defaults[$option] = [];
     429        $this->defined[$option] = true;
     430
     431        // Make sure the option is processed
     432        unset($this->resolved[$option]);
     433
     434        return $this;
    404435    }
    405436
     
    948979        $value = $this->defaults[$option];
    949980
     981        // Resolve the option if the default value is lazily evaluated
     982        if (isset($this->lazy[$option])) {
     983            // If the closure is already being called, we have a cyclic dependency
     984            if (isset($this->calling[$option])) {
     985                throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
     986            }
     987
     988            $this->calling[$option] = true;
     989            try {
     990                foreach ($this->lazy[$option] as $closure) {
     991                    $value = $closure($this, $value);
     992                }
     993            } finally {
     994                unset($this->calling[$option]);
     995            }
     996        }
     997
    950998        // Resolve the option if it is a nested definition
    951999        if (isset($this->nested[$option])) {
     
    9591007            }
    9601008
    961             // The following section must be protected from cyclic calls.
    9621009            $this->calling[$option] = true;
    9631010            try {
     
    9901037        }
    9911038
    992         // Resolve the option if the default value is lazily evaluated
    993         if (isset($this->lazy[$option])) {
    994             // If the closure is already being called, we have a cyclic
    995             // dependency
    996             if (isset($this->calling[$option])) {
    997                 throw new OptionDefinitionException(\sprintf('The options "%s" have a cyclic dependency.', $this->formatOptions(array_keys($this->calling))));
    998             }
    999 
    1000             // The following section must be protected from cyclic
    1001             // calls. Set $calling for the current $option to detect a cyclic
    1002             // dependency
    1003             // BEGIN
    1004             $this->calling[$option] = true;
    1005             try {
    1006                 foreach ($this->lazy[$option] as $closure) {
    1007                     $value = $closure($this, $value);
    1008                 }
    1009             } finally {
    1010                 unset($this->calling[$option]);
    1011             }
    1012             // END
    1013         }
    1014 
    10151039        // Validate the type of the resolved option
    10161040        if (isset($this->allowedTypes[$option])) {
     
    11401164    }
    11411165
    1142     private function verifyTypes(string $type, mixed $value, array &$invalidTypes, int $level = 0): bool
    1143     {
     1166    private function verifyTypes(string $type, mixed $value, ?array &$invalidTypes = null, int $level = 0): bool
     1167    {
     1168        $type = trim($type);
     1169        $allowedTypes = $this->splitOutsideParenthesis($type);
     1170        if (\count($allowedTypes) > 1) {
     1171            foreach ($allowedTypes as $allowedType) {
     1172                if ($this->verifyTypes($allowedType, $value)) {
     1173                    return true;
     1174                }
     1175            }
     1176
     1177            if (\is_array($invalidTypes) && (!$invalidTypes || $level > 0)) {
     1178                $invalidTypes[get_debug_type($value)] = true;
     1179            }
     1180
     1181            return false;
     1182        }
     1183
     1184        $type = $allowedTypes[0];
     1185        if (str_starts_with($type, '(') && str_ends_with($type, ')')) {
     1186            return $this->verifyTypes(substr($type, 1, -1), $value, $invalidTypes, $level);
     1187        }
     1188
    11441189        if (\is_array($value) && str_ends_with($type, '[]')) {
    11451190            $type = substr($type, 0, -2);
     
    11591204        }
    11601205
    1161         if (!$invalidTypes || $level > 0) {
     1206        if (\is_array($invalidTypes) && (!$invalidTypes || $level > 0)) {
    11621207            $invalidTypes[get_debug_type($value)] = true;
    11631208        }
    11641209
    11651210        return false;
     1211    }
     1212
     1213    /**
     1214     * @return list<string>
     1215     */
     1216    private function splitOutsideParenthesis(string $type): array
     1217    {
     1218        $parts = [];
     1219        $currentPart = '';
     1220        $parenthesisLevel = 0;
     1221
     1222        $typeLength = \strlen($type);
     1223        for ($i = 0; $i < $typeLength; ++$i) {
     1224            $char = $type[$i];
     1225
     1226            if ('(' === $char) {
     1227                ++$parenthesisLevel;
     1228            } elseif (')' === $char) {
     1229                --$parenthesisLevel;
     1230            }
     1231
     1232            if ('|' === $char && 0 === $parenthesisLevel) {
     1233                $parts[] = $currentPart;
     1234                $currentPart = '';
     1235            } else {
     1236                $currentPart .= $char;
     1237            }
     1238        }
     1239
     1240        if ('' !== $currentPart) {
     1241            $parts[] = $currentPart;
     1242        }
     1243
     1244        return $parts;
    11661245    }
    11671246
Note: See TracChangeset for help on using the changeset viewer.