Plugin Directory

Changeset 3391546


Ignore:
Timestamp:
11/07/2025 03:36:36 AM (5 months ago)
Author:
withflex
Message:

Add 3.1.12

Location:
pay-with-flex/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • pay-with-flex/trunk/pay-with-flex.php

    r3390698 r3391546  
    44 * Plugin Name:      Flex HSA/FSA Payments
    55 * Description:      Accept HSA/FSA payments directly in the checkout flow.
    6  * Version:          3.1.11
     6 * Version:          3.1.12
    77 * Plugin URI:       https://wordpress.org/plugins/pay-with-flex/
    88 * Author:           Flex
     
    518518}
    519519/**
    520  * React to the payment method being enabled.
    521  */
    522 function payment_method_enabled(): void {
    523     $gateway = payment_gateway();
    524     // Refresh the settings from the database so we are working with the latest version.
    525     $gateway->init_settings();
    526     // If no API key is present, then there is nothing to do.
    527     if ( empty( $gateway->api_key() ) ) {
    528         return;
    529     }
    530     // Check to see if the webhook needs to be updated.
    531     $webhook = Webhook::from_wc( $gateway );
    532     if ( $webhook->can( $webhook->needs() ) ) {
    533         flex_update_webhook_async();
    534     }
    535     for ( $i = 1;; $i++ ) {
     520 * Enqueues a page of products to be synced.
     521 *
     522 * @param int $page The page number to enqueue.
     523 * @param int $retries The number of retries that have been attempted.
     524 *
     525 * @throws \Exception If the product fails to be updated.
     526 */
     527function flex_product_sync_spawn( int $page, int $retries = 0 ): void {
     528    flex_enqueue_async_action( hook: 'flex_product_sync', args: array( $page, $retries ), retries: $retries );
     529}
     530/**
     531 * Syncs a page of products with Flex.
     532 *
     533 * @param int $page The page number to sync.
     534 * @param int $retries The number of retries that have been attempted.
     535 *
     536 * @throws FlexException If the API key is not set.
     537 * @throws \Throwable Any caught exceptions.
     538 */
     539function flex_product_sync( int $page, int $retries = 0 ): void {
     540    try {
     541        $gateway = payment_gateway();
     542        if ( empty( $gateway->api_key() ) ) {
     543            throw new FlexException( 'API Key is not set' );
     544        }
    536545        /**
    537546         * Fetch all of the products we support.
     
    541550        $products = wc_get_products(
    542551            array(
    543                 'paged' => $i,
    544                 'type'  => array_merge( Product::WC_TYPES, Price::WC_TYPES ),
     552                'page' => $page,
     553                'type' => array_merge( Product::WC_TYPES, Price::WC_TYPES ),
    545554            )
    546555        );
    547         if ( empty( $products ) ) {
    548             break;
    549         }
    550556        // Enqueue all of them to be updated.
    551557        foreach ( $products as $product ) {
    552558            wc_update_product( $product->get_id(), $product );
    553559        }
     560    } catch ( \Throwable $previous ) {
     561        flex_product_sync_spawn( $page, $retries + 1 );
     562        throw $previous;
     563    }
     564}
     565add_action( hook_name: 'flex_product_sync', callback: __NAMESPACE__ . '\flex_product_sync' );
     566/**
     567 * React to the payment method being enabled.
     568 */
     569function payment_method_enabled(): void {
     570    $gateway = payment_gateway();
     571    // Refresh the settings from the database so we are working with the latest version.
     572    $gateway->init_settings();
     573    // If no API key is present, then there is nothing to do.
     574    if ( empty( $gateway->api_key() ) ) {
     575        return;
     576    }
     577    // Check to see if the webhook needs to be updated.
     578    $webhook = Webhook::from_wc( $gateway );
     579    if ( $webhook->can( $webhook->needs() ) ) {
     580        flex_update_webhook_async();
     581    }
     582    $result = wc_get_products(
     583        array(
     584            'type'     => array_merge( Product::WC_TYPES, Price::WC_TYPES ),
     585            'paginate' => \true,
     586        )
     587    );
     588    for ( $i = 1; $i <= $result->max_num_pages; $i++ ) {
     589        flex_product_sync_spawn( $i );
    554590    }
    555591}
  • pay-with-flex/trunk/readme.txt

    r3390698 r3391546  
    44Requires at least: 6.8
    55Tested up to: 6.8
    6 Stable tag: 3.1.11
     6Stable tag: 3.1.12
    77Requires PHP: 8.1
    88License: GPLv3 or later
     
    5555
    5656== Changelog ==
     57
     58= 3.1.12 =
     59* Fixed a bug that prevented products and prices from being created if they already exist under a different account.
     60* Changed the plugin and payment method activation behavior. Product & Price syncing is now spawned asyncoursly which significantly improves the plugin activation speed.
    5761
    5862= 3.1.11 =
  • pay-with-flex/trunk/src/Resource/Price.php

    r3352167 r3391546  
    1212use Automattic\WooCommerce\Enums\ProductType;
    1313use Flex\Exception\FlexException;
     14use Flex\Exception\FlexResponseException;
    1415/**
    1516 * Flex Price
     
    205206     * @param ResourceAction $action The action to perform.
    206207     *
    207      * @throws FlexException If anything goes wrong.
     208     * @throws FlexException If the response is malformed.
     209     * @throws FlexResponseException Rethrows exception if it cannot be handled.
    208210     */
    209211    public function exec( \Flex\Resource\ResourceAction $action ): void {
     
    223225            $existing = new self( id: $this->id, active: \false, product: $this->product );
    224226            // Retrieve the existing price so we do not drop any existing values on re-creation.
    225             $data = $this->remote_request( '/v1/prices/' . $this->id );
    226             if ( isset( $data['price'] ) && is_array( $data['price'] ) ) {
    227                 // Remove fields that we no longer care about.
    228                 unset( $data['price']['price_id'] );
    229                 unset( $data['price']['price'] );
    230                 $price = array_merge( $data['price'], $this->jsonSerialize() );
    231             }
    232         }
    233         $data = $this->remote_request(
    234             match ( $action ) {
    235             \Flex\Resource\ResourceAction::CREATE => '/v1/prices',
    236             \Flex\Resource\ResourceAction::UPDATE => '/v1/prices/' . $this->id,
    237             },
    238             array(
    239                 'method' => 'POST',
    240                 'flex'   => array( 'data' => array( 'price' => $price ) ),
    241             )
    242         );
    243         if ( ! isset( $data['price'] ) ) {
    244             throw new FlexException( 'Missing price in response.' );
    245         }
    246         $this->extract( $data['price'] );
     227            try {
     228                $data = $this->remote_request( '/v1/prices/' . $this->id );
     229                if ( isset( $data['price'] ) && is_array( $data['price'] ) ) {
     230                    // Remove fields that we no longer care about.
     231                    unset( $data['price']['price_id'] );
     232                    unset( $data['price']['price'] );
     233                    $price = array_merge( $data['price'], $this->jsonSerialize() );
     234                }
     235            } catch ( FlexResponseException $e ) {
     236                if ( $e->code() !== 404 ) {
     237                    throw $e;
     238                }
     239                $existing = null;
     240            }
     241        }
     242        try {
     243            $data = $this->remote_request(
     244                match ( $action ) {
     245                \Flex\Resource\ResourceAction::CREATE => '/v1/prices',
     246                \Flex\Resource\ResourceAction::UPDATE => '/v1/prices/' . $this->id,
     247                },
     248                array(
     249                    'method' => 'POST',
     250                    'flex'   => array( 'data' => array( 'price' => $price ) ),
     251                )
     252            );
     253            if ( ! isset( $data['price'] ) ) {
     254                throw new FlexException( 'Missing price in response.' );
     255            }
     256            $this->extract( $data['price'] );
     257        } catch ( FlexResponseException $e ) {
     258            // Ensure the price exists before trying again.
     259            if ( $e->code() === 422 ) {
     260                $prev = $this->product->id();
     261                $this->product->exec( \Flex\Resource\ResourceAction::REFRESH );
     262                // If the product ids no longer match then the product was re-created.
     263                if ( $this->product->id() !== $prev ) {
     264                    $this->exec( $action );
     265                    return;
     266                }
     267            }
     268            if ( \Flex\Resource\ResourceAction::CREATE === $action ) {
     269                throw $e;
     270            }
     271            // Recreate the price.
     272            if ( $e->code() === 404 ) {
     273                $this->exec( \Flex\Resource\ResourceAction::CREATE );
     274                return;
     275            }
     276            throw $e;
     277        }
    247278        if ( null !== $this->wc ) {
    248279            $this->apply_to( $this->wc );
  • pay-with-flex/trunk/src/Resource/Product.php

    r3352167 r3391546  
    1212use Automattic\WooCommerce\Enums\ProductType;
    1313use Flex\Exception\FlexException;
     14use Flex\Exception\FlexResponseException;
    1415/**
    1516 * Flex Product
     
    185186        return match ( $action ) {
    186187            \Flex\Resource\ResourceAction::CREATE => \true,
    187             \Flex\Resource\ResourceAction::UPDATE => null !== $this->id,
     188            \Flex\Resource\ResourceAction::UPDATE, \Flex\Resource\ResourceAction::REFRESH => null !== $this->id,
    188189            default => \false,
    189190        };
     
    194195     * @param ResourceAction $action The action to perform.
    195196     *
    196      * @throws FlexException If anything goes wrong.
     197     * @throws FlexException If the response is malformed.
     198     * @throws FlexResponseException Rethrows exception if it cannot be handled.
    197199     */
    198200    public function exec( \Flex\Resource\ResourceAction $action ): void {
     
    206208            $existing = new self( name: $this->name, id: $this->id, active: \false );
    207209            // Retrieve the existing product so we do not drop any existing values on re-creation.
    208             $data = $this->remote_request( '/v1/products/' . $this->id );
    209             if ( isset( $data['product'] ) && is_array( $data['product'] ) ) {
    210                 // Remove fields that we no longer care about.
    211                 unset( $data['product']['product_id'] );
    212                 $product = array_merge( $data['product'], $this->jsonSerialize() );
    213             }
    214         }
    215         $data = $this->remote_request(
    216             match ( $action ) {
    217             \Flex\Resource\ResourceAction::CREATE => '/v1/products',
    218             \Flex\Resource\ResourceAction::UPDATE => '/v1/products/' . $this->id,
    219             },
    220             array(
    221                 'method' => match ( $action ) {
    222                 \Flex\Resource\ResourceAction::CREATE => 'POST',
    223                 \Flex\Resource\ResourceAction::UPDATE => 'PATCH',
     210            try {
     211                $data = $this->remote_request( '/v1/products/' . $this->id );
     212                if ( isset( $data['product'] ) && is_array( $data['product'] ) ) {
     213                    // Remove fields that we no longer care about.
     214                    unset( $data['product']['product_id'] );
     215                    $product = array_merge( $data['product'], $this->jsonSerialize() );
     216                }
     217            } catch ( FlexResponseException $e ) {
     218                if ( $e->code() !== 404 ) {
     219                    throw $e;
     220                }
     221                $existing = null;
     222            }
     223        }
     224        try {
     225            $data = $this->remote_request(
     226                match ( $action ) {
     227                \Flex\Resource\ResourceAction::CREATE => '/v1/products',
     228                \Flex\Resource\ResourceAction::UPDATE, \Flex\Resource\ResourceAction::REFRESH => '/v1/products/' . $this->id,
    224229                },
    225                 'flex'   => array( 'data' => array( 'product' => $product ) ),
    226             )
    227         );
    228         if ( ! isset( $data['product'] ) ) {
    229             throw new FlexException( 'Missing product in response.' );
    230         }
    231         $this->extract( $data['product'] );
     230                array(
     231                    'method' => match ( $action ) {
     232                    \Flex\Resource\ResourceAction::CREATE => 'POST',
     233                    \Flex\Resource\ResourceAction::UPDATE => 'PATCH',
     234                    \Flex\Resource\ResourceAction::REFRESH => 'GET',
     235                    },
     236                    'flex'   => match ( $action ) {
     237                                    \Flex\Resource\ResourceAction::CREATE, \Flex\Resource\ResourceAction::UPDATE => array( 'data' => array( 'product' => $product ) ),
     238                                    \Flex\Resource\ResourceAction::REFRESH => array(),
     239                    },
     240                )
     241            );
     242            if ( ! isset( $data['product'] ) ) {
     243                throw new FlexException( 'Missing product in response.' );
     244            }
     245            $this->extract( $data['product'] );
     246        } catch ( FlexResponseException $e ) {
     247            if ( \Flex\Resource\ResourceAction::CREATE === $action ) {
     248                throw $e;
     249            }
     250            if ( $e->code() !== 404 ) {
     251                throw $e;
     252            }
     253            // If an update or refresh was being performed and the API returned a 404, then re-create it.
     254            $this->exec( \Flex\Resource\ResourceAction::CREATE );
     255            return;
     256        }
    232257        if ( null !== $this->wc ) {
    233258            $this->apply_to( $this->wc );
  • pay-with-flex/trunk/src/Resource/Resource.php

    r3390698 r3391546  
    8989        $span    = sentry()->getSpan();
    9090        if ( null !== $span ) {
    91             $headers['traceparent'] = $span->toW3CTraceparent();
     91            $headers['baggage']      = $span->toBaggage();
     92            $headers['sentry-trace'] = $span->toTraceparent();
    9293        }
     94        $method = $args['method'] ?? 'GET';
    9395        /**
    9496         * The metadata to add to the breadcrumb
     
    9698         */
    9799        $meta = array(
    98             'method' => $args['method'] ?? 'GET',
     100            'method' => $method,
    99101            'url'    => $base . $path,
    100102        );
     
    120122        $code                = wp_remote_retrieve_response_code( $response );
    121123        $meta['status_code'] = $code;
     124        $body                = wp_remote_retrieve_body( $response );
     125        if ( ! $body ) {
     126            sentry()->addBreadcrumb( new Breadcrumb( category: 'request', level: Breadcrumb::LEVEL_ERROR, type: Breadcrumb::TYPE_HTTP, metadata: $meta ) );
     127            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
     128            throw new FlexResponseException( $response, "Missing Response Body {$method} {$path} {$code}" );
     129        }
    122130        if ( $code < 200 || $code >= 300 ) {
    123131            sentry()->addBreadcrumb( new Breadcrumb( category: 'request', level: Breadcrumb::LEVEL_ERROR, type: Breadcrumb::TYPE_HTTP, metadata: $meta ) );
    124132            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
    125             throw new FlexResponseException( $response, 'Flex responded with a ' . $code );
    126         }
    127         $body = wp_remote_retrieve_body( $response );
    128         if ( ! $body ) {
    129             sentry()->addBreadcrumb( new Breadcrumb( category: 'request', level: Breadcrumb::LEVEL_ERROR, type: Breadcrumb::TYPE_HTTP, metadata: $meta ) );
    130             // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
    131             throw new FlexResponseException( $response, 'Missing response body.' );
     133            throw new FlexResponseException( $response, "Response Failed {$method} {$path} {$code} {$body}" );
    132134        }
    133135        try {
  • pay-with-flex/trunk/vendor/composer/installed.php

    r3390698 r3391546  
    33namespace Flex;
    44
    5 return array('root' => array('name' => '__root__', 'pretty_version' => 'dev-main', 'version' => 'dev-main', 'reference' => '9627e2c3de7c8ca3d3a549b771ff6cdbbba73cf5', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \false), 'versions' => array('__root__' => array('pretty_version' => 'dev-main', 'version' => 'dev-main', 'reference' => '9627e2c3de7c8ca3d3a549b771ff6cdbbba73cf5', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'guzzlehttp/psr7' => array('pretty_version' => '2.8.0', 'version' => '2.8.0.0', 'reference' => '21dc724a0583619cd1652f673303492272778051', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), 'dev_requirement' => \false), 'jean85/pretty-package-versions' => array('pretty_version' => '2.1.1', 'version' => '2.1.1.0', 'reference' => '4d7aa5dab42e2a76d99559706022885de0e18e1a', 'type' => 'library', 'install_path' => __DIR__ . '/../jean85/pretty-package-versions', 'aliases' => array(), 'dev_requirement' => \false), 'psr/http-factory' => array('pretty_version' => '1.1.0', 'version' => '1.1.0.0', 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-factory', 'aliases' => array(), 'dev_requirement' => \false), 'psr/http-factory-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0')), 'psr/http-message' => array('pretty_version' => '2.0', 'version' => '2.0.0.0', 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-message', 'aliases' => array(), 'dev_requirement' => \false), 'psr/http-message-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0')), 'psr/log' => array('pretty_version' => '3.0.2', 'version' => '3.0.2.0', 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => \false), 'ralouphie/getallheaders' => array('pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'type' => 'library', 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 'aliases' => array(), 'dev_requirement' => \false), 'sentry/sentry' => array('pretty_version' => '4.18.0', 'version' => '4.18.0.0', 'reference' => '75f7efb7d435d24767c93d0081b8edf228be5772', 'type' => 'library', 'install_path' => __DIR__ . '/../sentry/sentry', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/deprecation-contracts' => array('pretty_version' => 'v3.6.0', 'version' => '3.6.0.0', 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/options-resolver' => array('pretty_version' => 'v7.3.3', 'version' => '7.3.3.0', 'reference' => '0ff2f5c3df08a395232bbc3c2eb7e84912df911d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/options-resolver', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-php84' => array('pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', 'reference' => 'd8ced4d875142b6a7426000426b8abc631d6b191', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php84', 'aliases' => array(), 'dev_requirement' => \false)));
     5return array('root' => array('name' => '__root__', 'pretty_version' => 'dev-main', 'version' => 'dev-main', 'reference' => '0e52014530a31bb82f7684bbaeaf666d3d730fc1', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \false), 'versions' => array('__root__' => array('pretty_version' => 'dev-main', 'version' => 'dev-main', 'reference' => '0e52014530a31bb82f7684bbaeaf666d3d730fc1', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'guzzlehttp/psr7' => array('pretty_version' => '2.8.0', 'version' => '2.8.0.0', 'reference' => '21dc724a0583619cd1652f673303492272778051', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), 'dev_requirement' => \false), 'jean85/pretty-package-versions' => array('pretty_version' => '2.1.1', 'version' => '2.1.1.0', 'reference' => '4d7aa5dab42e2a76d99559706022885de0e18e1a', 'type' => 'library', 'install_path' => __DIR__ . '/../jean85/pretty-package-versions', 'aliases' => array(), 'dev_requirement' => \false), 'psr/http-factory' => array('pretty_version' => '1.1.0', 'version' => '1.1.0.0', 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-factory', 'aliases' => array(), 'dev_requirement' => \false), 'psr/http-factory-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0')), 'psr/http-message' => array('pretty_version' => '2.0', 'version' => '2.0.0.0', 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-message', 'aliases' => array(), 'dev_requirement' => \false), 'psr/http-message-implementation' => array('dev_requirement' => \false, 'provided' => array(0 => '1.0')), 'psr/log' => array('pretty_version' => '3.0.2', 'version' => '3.0.2.0', 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'dev_requirement' => \false), 'ralouphie/getallheaders' => array('pretty_version' => '3.0.3', 'version' => '3.0.3.0', 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'type' => 'library', 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 'aliases' => array(), 'dev_requirement' => \false), 'sentry/sentry' => array('pretty_version' => '4.18.0', 'version' => '4.18.0.0', 'reference' => '75f7efb7d435d24767c93d0081b8edf228be5772', 'type' => 'library', 'install_path' => __DIR__ . '/../sentry/sentry', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/deprecation-contracts' => array('pretty_version' => 'v3.6.0', 'version' => '3.6.0.0', 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/options-resolver' => array('pretty_version' => 'v7.3.3', 'version' => '7.3.3.0', 'reference' => '0ff2f5c3df08a395232bbc3c2eb7e84912df911d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/options-resolver', 'aliases' => array(), 'dev_requirement' => \false), 'symfony/polyfill-php84' => array('pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', 'reference' => 'd8ced4d875142b6a7426000426b8abc631d6b191', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php84', 'aliases' => array(), 'dev_requirement' => \false)));
Note: See TracChangeset for help on using the changeset viewer.