diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..a04c68e --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,316 @@ +name: QA + +on: [ push, pull_request ] + +env: + COMPOSER_NO_INTERACTION: 1 + WP_TESTS_DB_PASS: password + wp-version: '*' + +jobs: + coding-standards: + name: Coding Standards (PHP ${{ matrix.php-version }}) + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-version: + - 7.4 + + steps: + - uses: actions/checkout@v2 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: bcmath, intl, mbstring, mysql + ini-values: memory_limit=2048M + tools: composer, phpcs + coverage: none + + - name: Composer validation + run: composer validate --strict + + # https://github.com/actions/cache/blob/master/examples.md#php---composer + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Require WordPress + run: composer require --dev --no-update --no-progress --no-suggest roots/wordpress:${{ env.wp-version }} wp-phpunit/wp-phpunit:${{ env.wp-version }} + + - name: Install Composer dependencies + run: composer update --no-progress --no-suggest + + - name: List Composer packages + run: composer show + + - name: PHPLint + run: composer run-script phplint + + - name: PHP Code Sniffer + run: composer run-script phpcs + + - name: PHP Mess Detector + continue-on-error: true + run: composer run-script phpmd + + static-code-analysis: + name: Static Code Analysis (PHP ${{ matrix.php-version }}) + + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: + - 7.4 + + steps: + - uses: actions/checkout@v2 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: bcmath, intl, mbstring, mysql + ini-values: memory_limit=2048M + tools: composer + coverage: none + + # https://github.com/actions/cache/blob/master/examples.md#php---composer + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install Composer dependencies + run: composer update --no-progress --no-suggest + + - name: PHPStan static code analysis + run: composer bin phpstan install && composer run-script phpstan + + tests: + name: Tests — PHP ${{ matrix.php-version }} ${{ matrix.name }} + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + # PHP Compatibility and WordPress Versions: https://make.wordpress.org/core/handbook/references/php-compatibility-and-wordpress-versions/ + - name: WP 5.6 + php-version: 7.4 + wp-version: 5.6.* + - name: WP 5.5 + php-version: 7.4 + wp-version: 5.5.* + # WordPress and PHP 7.4: https://make.wordpress.org/core/2019/10/11/wordpress-and-php-7-4/. + - name: WP 5.4 + php-version: 7.4 + wp-version: 5.4.* + - name: WP 5.3 + php-version: 7.4 + wp-version: 5.3.* + # WordPress 5.2 (or lower) will trigger PHP 7.4 (or higher) errors. + # @link https://make.wordpress.org/core/2019/10/11/wordpress-and-php-7-4/. + - name: WP 5.2 + php-version: 7.3 + wp-version: 5.2.* + - name: WP 5.1 + php-version: 7.3 + wp-version: 5.1.* + - name: WP 5.0 + php-version: 7.3 + wp-version: 5.0.* + - name: WP 4.9 + php-version: 7.3 + wp-version: 4.9.* + # WordPress 4.8 (or lower) will trigger PHP 7.2 (or higher) errors. + # @link https://make.wordpress.org/core/2018/10/15/wordpress-and-php-7-3/ + # @link https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/ + # @link https://make.wordpress.org/core/tag/4-9/?s=7.2 + - name: WP 4.8 + php-version: 7.1 + wp-version: 4.8.* + - name: WP 4.7 + php-version: 7.1 + wp-version: 4.7.* + - name: Multisite + php-version: 7.4 + WP_MULTISITE: 1 + - php-version: 7.4 + - php-version: 7.3 + - php-version: 7.2 + - php-version: 7.1 + - php-version: 7.0 + - php-version: 5.6 + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: false + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: wp_phpunit_tests + ports: + - 3306/tcp + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - uses: actions/checkout@v2 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: bcmath, intl, mbstring, mysql + ini-values: memory_limit=2048M + tools: composer + coverage: none + + # https://github.com/actions/cache/blob/master/examples.md#php---composer + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Start MySQL service + run: sudo service mysql start + + - name: Require WordPress + run: composer require --dev --no-update --no-progress --no-suggest roots/wordpress:${{ matrix.wp-version || env.wp-version }} wp-phpunit/wp-phpunit:${{ matrix.wp-version || env.wp-version }} + + - name: Install Composer dependencies + run: composer update --no-progress --no-suggest + + - name: List Composer packages + run: composer show + + - name: PHPLint + run: composer run-script phplint + + - name: PHPUnit test suite + env: + WP_TESTS_DB_HOST: 127.0.0.1:${{ job.services.mysql.ports['3306'] }} + run: composer run-script phpunit + + code-coverage: + name: Code Coverage (PHP ${{ matrix.php-version }}) + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-version: + - 7.4 + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: false + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: wp_phpunit_tests + ports: + - 3306/tcp + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - uses: actions/checkout@v2 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: bcmath, intl, mbstring, mysql + ini-values: memory_limit=2048M + tools: composer + coverage: xdebug + + # https://github.com/actions/cache/blob/master/examples.md#php---composer + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Start MySQL service + run: sudo service mysql start + + - name: Require WordPress + run: composer require --dev --no-update --no-progress --no-suggest roots/wordpress:${{ env.wp-version }} wp-phpunit/wp-phpunit:${{ env.wp-version }} + + - name: Install Composer dependencies + run: composer update --no-progress --no-suggest + + - name: List Composer packages + run: composer show + + - name: PHPLint + run: composer run-script phplint + + - name: PHPUnit test suite + env: + WP_TESTS_DB_HOST: 127.0.0.1:${{ job.services.mysql.ports['3306'] }} + run: composer run-script phpunit + + - name: Coveralls code coverage + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + composer run-script coveralls + bash <(curl -s https://codecov.io/bash) + + es-sass-linting: + name: Scripts and Styles + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + # https://github.com/actions/cache/blob/master/examples.md#macos-and-ubuntu + - uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Install Node packages + run: npm install + + - name: ESLint + run: npm run-script eslint + + - name: Sass Lint + run: npm run-script sass-lint diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd0d2c..384032d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ This projects adheres to [Semantic Versioning](http://semver.org/) and [Keep a C ## [Unreleased][unreleased] - +## [2.2.1] - 2021-01-14 +- Prevent updating eot if (retry) payment period end date is (before) current eot time. +- Code quality. +- Fix using removed payment data class and multiple status update actions. +- Fix setting subscription next payment date for new subscriptions (removes payment data class). + ## [2.2.0] - 2020-11-09 - Added support for new subscription phases and periods. - Fixed processing list servers for recurring payments. @@ -83,7 +89,8 @@ This projects adheres to [Semantic Versioning](http://semver.org/) and [Keep a C ## 1.0.0 - 2015-01-20 - First release. -[unreleased]: https://github.com/wp-pay-extensions/s2member/compare/2.2.0...HEAD +[unreleased]: https://github.com/wp-pay-extensions/s2member/compare/2.2.1...HEAD +[2.2.1]: https://github.com/wp-pay-extensions/s2member/compare/2.2.0...2.2.1 [2.2.0]: https://github.com/wp-pay-extensions/s2member/compare/2.1.3...2.2.0 [2.1.3]: https://github.com/wp-pay-extensions/s2member/compare/2.1.2...2.1.3 [2.1.2]: https://github.com/wp-pay-extensions/s2member/compare/2.1.1...2.1.2 diff --git a/composer.json b/composer.json index af37f39..a015d73 100644 --- a/composer.json +++ b/composer.json @@ -52,19 +52,19 @@ ], "require": { "php": ">=5.6.20", - "wp-pay/core": "^2.4" + "wp-pay/core": "^2.6" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", "php-coveralls/php-coveralls": "^2.1", "phpcompatibility/php-compatibility": "^9.2", "phpcompatibility/phpcompatibility-wp": "^2.0", "phpmd/phpmd": "^2.7", "phpunit/phpunit": "^5.7 || ^6.0", - "roots/wordpress": "^5.2", + "roots/wordpress": "^5.6", "squizlabs/php_codesniffer": "^3.4", "wp-coding-standards/wpcs": "^2.1", - "wp-phpunit/wp-phpunit": "^5.2", + "wp-phpunit/wp-phpunit": "^5.6", "wpsharks/s2member": "190822" }, "scripts": { diff --git a/package.json b/package.json index 1ab8cb8..0e8508a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "s2member", - "version": "2.2.0", + "version": "2.2.1", "description": "s2Member driver for the WordPress payment processing library.", "repository": { "type": "git", @@ -32,8 +32,8 @@ }, "homepage": "http://www.wp-pay.org/extensions/s2member/", "devDependencies": { - "grunt": "^1.0.4", - "grunt-contrib-jshint": "^2.1.0", + "grunt": "^1.3.0", + "grunt-contrib-jshint": "^3.0.0", "grunt-phpcs": "^0.4.0", "grunt-phplint": "0.1.0", "grunt-phpmd": "^0.1.1", diff --git a/src/Extension.php b/src/Extension.php index 5d489f2..e5b7728 100644 --- a/src/Extension.php +++ b/src/Extension.php @@ -71,8 +71,6 @@ public function setup() { add_action( 'pronamic_payment_status_update_' . $slug, array( __CLASS__, 'update_status' ), 10, 1 ); add_action( 'pronamic_subscription_renewal_notice_' . self::SLUG, array( __CLASS__, 'subscription_renewal_notice' ) ); - add_action( 'pronamic_payment_status_update_' . $slug, array( __CLASS__, 'status_update' ), 10, 2 ); - $option_name = 'pronamic_pay_s2member_signup_email_message'; add_filter( 'default_option_' . $option_name, array( __CLASS__, 'default_option_s2member_signup_email_message' ) ); @@ -155,14 +153,29 @@ public static function default_option_s2member_subscription_renewal_notice_email * @return void */ public static function update_status( Payment $payment ) { + // Check failed recurring payment. + if ( $payment->get_recurring() ) { + switch ( $payment->get_status() ) { + case PaymentStatus::CANCELLED: + case PaymentStatus::EXPIRED: + case PaymentStatus::FAILURE: + $user = get_user_by( 'email', $payment->get_email() ); + + if ( false !== $user ) { + Util::auto_eot_now_user_update( $user ); + } + + return; + } + } + + // Continue with successful payments only. if ( PaymentStatus::SUCCESS !== $payment->get_status() ) { return; } $payment_data = Util::get_payment_data( $payment ); - $data = new PaymentData( $payment_data ); - $email = $payment->get_email(); // Get account from email address. @@ -245,9 +258,9 @@ public static function update_status( Payment $payment ) { } } - $level = $data->get_level(); - $period = $data->get_period(); - $ccaps = $data->get_ccaps(); + $level = $payment_data['level']; + $period = $payment_data['period']; + $ccaps = $payment_data['ccaps']; $capability = 'access_s2member_level' . $level; $role = 's2member_level' . $level; @@ -298,18 +311,29 @@ public static function update_status( Payment $payment ) { $eot_time_current = time(); } - if ( $payment->get_recurring() ) { - add_filter( 'ws_plugin__s2member_eot_grace_time', '__return_zero' ); + // Prevent updating eot if (retry) payment period end date is (before) current eot time. + $should_update_eot = true; - // Calculate EOT time for period from today. - $eot_time_new = c_ws_plugin__s2member_utils_time::auto_eot_time( 0, '', '', $period, 0, $eot_time_current ); + $end_date = $payment->get_end_date(); - remove_filter( 'ws_plugin__s2member_eot_grace_time', '__return_zero' ); - } else { - $eot_time_new = c_ws_plugin__s2member_utils_time::auto_eot_time( $user->ID, '', $period, false, $eot_time_current ); + if ( null !== $end_date && $end_date->getTimestamp() <= $eot_time_current ) { + $should_update_eot = false; } - update_user_option( $user->ID, 's2member_auto_eot_time', $eot_time_new ); + if ( $should_update_eot ) { + if ( $payment->get_recurring() ) { + add_filter( 'ws_plugin__s2member_eot_grace_time', '__return_zero' ); + + // Calculate EOT time for period from today. + $eot_time_new = c_ws_plugin__s2member_utils_time::auto_eot_time( 0, '', '', $period, 0, $eot_time_current ); + + remove_filter( 'ws_plugin__s2member_eot_grace_time', '__return_zero' ); + } else { + $eot_time_new = c_ws_plugin__s2member_utils_time::auto_eot_time( $user->ID, '', $period, false, $eot_time_current ); + } + + update_user_option( $user->ID, 's2member_auto_eot_time', $eot_time_new ); + } } // Subscribe with list servers. @@ -334,58 +358,6 @@ public static function update_status( Payment $payment ) { } } - /** - * Status update. - * - * @param Payment $payment Payment. - * @param bool $can_redirect Can redirect. - * @return void - */ - public static function status_update( Payment $payment, $can_redirect = false ) { - $payment_data = Util::get_payment_data( $payment ); - - $data = new PaymentData( $payment_data ); - - $url = $data->get_normal_return_url(); - - // Get account by email. - $user = get_user_by( 'email', $payment->get_email() ); - - switch ( $payment->status ) { - case PaymentStatus::CANCELLED: - $url = $data->get_cancel_url(); - - if ( $payment->get_recurring() ) { - Util::auto_eot_now_user_update( $user ); - } - - break; - case PaymentStatus::EXPIRED: - case PaymentStatus::FAILURE: - $url = $data->get_error_url(); - - if ( $payment->get_recurring() ) { - Util::auto_eot_now_user_update( $user ); - } - - break; - case PaymentStatus::SUCCESS: - $url = $data->get_success_url(); - - break; - case PaymentStatus::OPEN: - $url = $data->get_normal_return_url(); - - break; - } - - if ( $url && $can_redirect ) { - wp_redirect( $url ); - - exit; - } - } - /** * Send subscription renewal notice * diff --git a/src/PaymentData.php b/src/PaymentData.php deleted file mode 100644 index 278e60c..0000000 --- a/src/PaymentData.php +++ /dev/null @@ -1,196 +0,0 @@ -data = $data; - $this->recurring = false; - $this->subscription = false; - - $user_subscription_id = get_user_option( 's2member_subscr_id', $this->get_user_id() ); - - if ( ! empty( $user_subscription_id ) ) { - $this->subscription = new Subscription( $user_subscription_id ); - } - - if ( ! empty( $data['subscription_id'] ) ) { - $this->subscription = new Subscription( $data['subscription_id'] ); - - $this->recurring = true; - } - } - - public function get_payment_method() { - return $this->data['payment_method']; - } - - public function get_period() { - return $this->data['period']; - } - - public function get_level() { - return $this->data['level']; - } - - public function get_ccaps() { - return $this->data['ccaps']; - } - - public function get_order_id() { - return $this->data['order_id']; - } - - public function get_description() { - $search = array( - '{{order_id}}', - ); - - $replace = array( - $this->get_order_id(), - ); - - return str_replace( $search, $replace, $this->data['description'] ); - } - - public function get_items() { - $items = new Items(); - - $item = new Item(); - $item->set_number( $this->get_order_id() ); - $item->set_description( $this->get_description() ); - $item->set_price( $this->data['cost'] ); - $item->set_quantity( 1 ); - - $items->add_item( $item ); - - return $items; - } - - public function get_source() { - return 's2member'; - } - - public function get_source_id() { - if ( $this->recurring && $this->subscription ) { - $first = $this->subscription->get_first_payment(); - - return $first->get_source_id(); - } - - return $this->data['order_id']; - } - - /** - * Get currency - * - * @see Pronamic_Pay_PaymentDataInterface::get_currency_alphabetic_code() - * @return string - */ - public function get_currency_alphabetic_code() { - return 'EUR'; - } - - public function get_email() { - $email = parent::get_email(); - - if ( filter_has_var( INPUT_POST, 'pronamic_pay_s2member_email' ) ) { - $email = filter_input( INPUT_POST, 'pronamic_pay_s2member_email', FILTER_VALIDATE_EMAIL ); - } - - return $email; - } - - public function get_customer_name() { - $customer_name = parent::get_customer_name(); - - if ( 'Y' === $this->data['recurring'] ) { - $customer_name = $this->get_email(); - } - - return $customer_name; - } - - public function get_address() { - return ''; - } - - public function get_city() { - return ''; - } - - public function get_zip() { - return ''; - } - - /** - * Get subscription. - * - * @return string|bool - */ - public function get_subscription() { - if ( 'Y' !== $this->data['recurring'] ) { - return false; - } - - // Interval - $period = $this->get_period(); - - list( $interval, $interval_period ) = explode( ' ', $period ); - - if ( $this->subscription ) { - $subscription = $this->subscription; - } else { - $subscription = new Subscription(); - - // Phase. - $phase = new SubscriptionPhase( - $subscription, - new \DateTimeImmutable(), - new SubscriptionInterval( 'P' . $interval . $interval_period ), - new TaxedMoney( $this->get_amount()->get_value(), $this->get_currency_alphabetic_code() ) - ); - - $subscription->add_phase( $phase ); - } - - $subscription->set_description( $this->get_description() ); - - return $subscription; - } - - public function get_subscription_id() { - if ( $this->subscription ) { - return $this->subscription->get_id(); - } - - return intval( $this->data['subscription_id'] ); - } -} diff --git a/src/Shortcodes.php b/src/Shortcodes.php index 6a5f19d..aa555de 100644 --- a/src/Shortcodes.php +++ b/src/Shortcodes.php @@ -3,8 +3,19 @@ namespace Pronamic\WordPress\Pay\Extensions\S2Member; use c_ws_plugin__s2member_list_servers; +use Pronamic\WordPress\DateTime\DateTime; +use Pronamic\WordPress\Money\TaxedMoney; +use Pronamic\WordPress\Pay\Address; +use Pronamic\WordPress\Pay\ContactName; use Pronamic\WordPress\Pay\Core\Util as Core_Util; +use Pronamic\WordPress\Pay\Customer; +use Pronamic\WordPress\Pay\Payments\Payment; +use Pronamic\WordPress\Pay\Payments\PaymentLines; use Pronamic\WordPress\Pay\Plugin; +use Pronamic\WordPress\Pay\Subscriptions\Subscription; +use Pronamic\WordPress\Pay\Subscriptions\SubscriptionHelper; +use Pronamic\WordPress\Pay\Subscriptions\SubscriptionInterval; +use Pronamic\WordPress\Pay\Subscriptions\SubscriptionPhase; use Pronamic\WordPress\Pay\Util as Pay_Util; /** @@ -117,8 +128,19 @@ public function shortcode_pay( $atts ) { } } - // Data. - $data = new PaymentData( $atts ); + // Period. + $period = \str_replace( ' ', '', $atts['period'] ); + + if ( ! empty( $period ) ) { + $interval_value = (int) $period; + + $interval = (object) array( + 'value' => $interval_value, + 'unit' => \str_replace( $interval_value, '', $period ), + ); + + $atts['period'] = sprintf( '%d %s', $interval->value, $interval->unit ); + } // Hash. $hash_data = array( @@ -149,7 +171,7 @@ public function shortcode_pay( $atts ) { '', esc_attr( 'pronamic_pay_s2member_email' ), esc_attr( 'pronamic_pay_s2member_email' ), - $data->get_email() + (string) Util::get_user_input_email() ); $output .= ' '; } @@ -214,9 +236,7 @@ public function handle_payment() { } // Data. - $data = new PaymentData( $data ); - - $email = $data->get_email(); + $email = Util::get_user_input_email(); if ( empty( $email ) ) { return; @@ -227,23 +247,236 @@ public function handle_payment() { $gateway = Plugin::get_gateway( $config_id ); - // Start. - $payment = Plugin::start( $config_id, $gateway, $data, $data->get_payment_method() ); + if ( null === $gateway ) { + // Set error message. + $this->error[ $index ] = array( + Plugin::get_default_error_message(), + __( 'The payment gateway could not be found.', 'pronamic_ideal' ), + ); + + return; + } + + /* + * Payment. + */ + $payment = new Payment(); + + $order_id = $data['order_id']; + + $replacements = array( + '{{order_id}}' => $order_id, + ); + + $description = strtr( $data['description'], $replacements ); + + $payment->set_config_id( $config_id ); + $payment->set_description( $description ); + + $payment->method = $data['payment_method']; + $payment->order_id = $order_id; + + // Source. + $payment->set_source( 's2member' ); + $payment->set_source_id( $order_id ); + + // Data. + $user = \wp_get_current_user(); + + $first_name = $user->first_name; + $last_name = $user->last_name; + $user_id = \get_current_user_id(); + + // Name. + $name = null; + + $name_data = array( + $first_name, + $last_name, + ); + + $name_data = array_filter( $name_data ); + + if ( ! empty( $name_data ) ) { + $name = new ContactName(); + + if ( ! empty( $first_name ) ) { + $name->set_first_name( $first_name ); + } + + if ( ! empty( $last_name ) ) { + $name->set_last_name( $last_name ); + } + } + + // Customer. + $customer_data = array( + $name, + $email, + $user_id, + ); + + $customer_data = array_filter( $customer_data ); + + if ( ! empty( $customer_data ) ) { + $customer = new Customer(); + + $customer->set_name( $name ); + $customer->set_email( $email ); + + if ( ! empty( $user_id ) ) { + $customer->set_user_id( (int) $user_id ); + } + + $payment->set_customer( $customer ); + } + + // Billing address. + $address_data = array( + $name, + $email, + ); + + $address_data = array_filter( $address_data ); + + if ( ! empty( $address_data ) ) { + $address = new Address(); + + if ( ! empty( $name ) ) { + $address->set_name( $name ); + } - // Add subscription period to payment. - $subscription = $payment->get_subscription(); + $address->set_email( $email ); - if ( null !== $subscription ) { - $payment->add_period( $subscription->new_period() ); + $payment->set_billing_address( $address ); + } + + // Lines. + $payment->lines = new PaymentLines(); + + $line = $payment->lines->new_line(); + + $price = new TaxedMoney( $data['cost'], 'EUR' ); + + $line->set_name( $description ); + $line->set_quantity( 1 ); + $line->set_unit_price( $price ); + $line->set_total_amount( $price ); + + $payment->set_total_amount( $payment->lines->get_amount() ); + + // Subscription. + if ( 'Y' === $data['recurring'] && isset( $data['period'] ) && ! empty( $data['period'] ) ) { + // Find existing subscription. + $subscription = null; + + $start_date = new \DateTimeImmutable(); + + if ( ! empty( $data['subscription_id'] ) ) { + $subscription = \get_pronamic_subscription( (int) $data['subscription_id'] ); + } + + $user_subscription_id = \get_user_option( 's2member_subscr_id', $user_id ); + + if ( null === $subscription && ! empty( $user_subscription_id ) ) { + $subscription = \get_pronamic_subscription( (int) $user_subscription_id ); + } + + // Cancel active phases. + if ( null !== $subscription ) { + foreach ( $subscription->get_phases() as $phase ) { + // Check if phase has already been completed. + if ( $phase->all_periods_created() ) { + continue; + } + + // Check if phase is already canceled. + $canceled_at = $phase->get_canceled_at(); + + if ( ! empty( $canceled_at ) ) { + continue; + } + + // Set start date for new phases (before setting canceled date). + $next_date = $phase->get_next_date(); + + if ( null !== $next_date ) { + $start_date = $next_date; + } + + // Set canceled date. + $phase->set_canceled_at( new \DateTimeImmutable() ); + } + + $payment->subscription_id = $subscription->get_id(); + } + + // New subscription. + if ( null === $subscription ) { + $subscription = new Subscription(); + } + + // Data. + $subscription->set_description( $description ); + $subscription->set_lines( $payment->get_lines() ); - $subscription->save(); + // Phase. + $period = \str_replace( ' ', '', $data['period'] ); - $payment->save(); + $interval_value = (int) $period; + + $interval = (object) array( + 'value' => $interval_value, + 'unit' => \str_replace( $interval_value, '', $period ), + ); + + $phase = new SubscriptionPhase( + $subscription, + $start_date, + new SubscriptionInterval( 'P' . $interval->value . Core_Util::to_period( $interval->unit ) ), + $price + ); + + $subscription->add_phase( $phase ); + + $period = $subscription->new_period(); + + if ( null !== $period ) { + $payment->add_period( $period ); + } + + // Update existing subscription dates. + if ( null !== $payment->subscription_id ) { + $next_payment_date = $phase->get_next_date(); + + if ( null !== $next_payment_date ) { + $next_payment_date = DateTime::create_from_immutable( $next_payment_date ); + } + + $subscription->set_next_payment_date( $next_payment_date ); + $subscription->set_next_payment_delivery_date( SubscriptionHelper::calculate_next_payment_delivery_date( $subscription ) ); + + $subscription->set_expiry_date( DateTime::create_from_immutable( $start_date ) ); + } + + $payment->subscription = $subscription; + $payment->subscription_source_id = $payment->get_source_id(); + } + + // Start. + try { + $payment = Plugin::start_payment( $payment ); + } catch ( \Exception $e ) { + // Set error message. + $this->error[ $index ] = array( + Plugin::get_default_error_message(), + $e->getMessage(), + ); } - update_post_meta( $payment->get_id(), '_pronamic_payment_s2member_period', $data->get_period() ); - update_post_meta( $payment->get_id(), '_pronamic_payment_s2member_level', $data->get_level() ); - update_post_meta( $payment->get_id(), '_pronamic_payment_s2member_ccaps', $data->get_ccaps() ); + update_post_meta( $payment->get_id(), '_pronamic_payment_s2member_period', $data['period'] ); + update_post_meta( $payment->get_id(), '_pronamic_payment_s2member_level', $data['level'] ); + update_post_meta( $payment->get_id(), '_pronamic_payment_s2member_ccaps', $data['ccaps'] ); // List server opt-in. if ( ! empty( $opt_in ) ) { @@ -261,22 +494,13 @@ public function handle_payment() { continue; } - update_post_meta( $subscription_id, '_pronamic_subscription_s2member_period', $data->get_period() ); - update_post_meta( $subscription_id, '_pronamic_subscription_s2member_level', $data->get_level() ); - update_post_meta( $subscription_id, '_pronamic_subscription_s2member_ccaps', $data->get_ccaps() ); + update_post_meta( $subscription_id, '_pronamic_subscription_s2member_period', $data['period'] ); + update_post_meta( $subscription_id, '_pronamic_subscription_s2member_level', $data['level'] ); + update_post_meta( $subscription_id, '_pronamic_subscription_s2member_ccaps', $data['ccaps'] ); } } - $error = $gateway->get_error(); - - if ( is_wp_error( $error ) ) { - // Set error message. - $this->error[ $index ] = array( Plugin::get_default_error_message() ); - - foreach ( $error->get_error_messages() as $message ) { - $this->error[ $index ][] = $message; - } - } else { + if ( ! \array_key_exists( $index, $this->error ) ) { // Redirect. $gateway->redirect( $payment ); } diff --git a/src/Util.php b/src/Util.php index f0f95c1..471724e 100644 --- a/src/Util.php +++ b/src/Util.php @@ -124,4 +124,27 @@ public static function get_payment_data( Payment $payment ) { 'subscription_id' => $subscription_id, ); } + + /** + * Get email address from logged in user or form input. + * + * @return string|null + */ + public static function get_user_input_email() { + $email = null; + + // Get email from logged in user. + if ( \is_user_logged_in() ) { + $user = \wp_get_current_user(); + + $email = $user->user_email; + } + + // Get email from form input. + if ( \filter_has_var( \INPUT_POST, 'pronamic_pay_s2member_email' ) ) { + $email = (string) \filter_input( \INPUT_POST, 'pronamic_pay_s2member_email', \FILTER_VALIDATE_EMAIL ); + } + + return $email; + } }