���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home/zhaagvlk/public_html/wp-content/plugins/wpforms-lite/src/Integrations/PayPalCommerce/Process/Process.php
���ѧ٧ѧ�
<?php namespace WPForms\Integrations\PayPalCommerce\Process; use WPForms\Tasks\Meta; use WPForms\Integrations\PayPalCommerce\Connection; use WPForms\Integrations\PayPalCommerce\Helpers; use WPForms\Integrations\PayPalCommerce\PayPalCommerce; /** * PayPal Commerce payment processing. * * @since 1.10.0 */ class Process extends Base { /** * Task name to update subscription payment. * * @since 1.10.0 * * @var string */ private const SUBSCRIPTION_TASK = 'wpforms_paypal_commerce_subscription_payment_data_update'; /** * PayPal Commerce field. * * @since 1.10.0 * * @var array */ protected $field = []; /** * Form submission data ($_POST). * * @since 1.10.0 * * @var array */ protected $entry = []; /** * Main class that communicates with the PayPal Commerce API. * * @since 1.10.0 * * @var Api|\WPFormsPaypalCommerce\Api\Api */ protected $api; /** * Register hooks. * * @since 1.10.0 */ public function hooks(): void { add_action( 'wpforms_process', [ $this, 'process_entry' ], 10, 3 ); add_action( 'wpforms_process_entry_saved', [ $this, 'update_entry_meta' ], 10, 4 ); add_filter( 'wpforms_entry_email_process', [ $this, 'process_email' ], 70, 5 ); add_filter( 'wpforms_forms_submission_prepare_payment_data', [ $this, 'prepare_payment_data' ], 10, 3 ); add_filter( 'wpforms_forms_submission_prepare_payment_meta', [ $this, 'prepare_payment_meta' ], 10, 3 ); add_action( 'wpforms_process_payment_saved', [ $this, 'process_payment_saved' ], 10, 3 ); add_action( self::SUBSCRIPTION_TASK, [ $this, 'update_subscription_data_scheduled_task' ] ); $this->init_hook(); } /** * Validate and process if a payment exists with an entry. * * @since 1.10.0 * * @param array $fields Final/sanitized submitted field data. * @param array $entry Copy of the original $_POST. * @param array $form_data Form data and settings. */ public function process_entry( $fields, array $entry, array $form_data ): void { $fields = (array) $fields; if ( ! Helpers::is_paypal_commerce_enabled( $form_data ) ) { return; } $this->form_data = $form_data; $this->fields = $fields; $this->entry = $entry; $this->form_id = (int) $form_data['id']; $this->amount = $this->get_amount(); $this->field = Helpers::get_paypal_field( $this->fields ); $this->connection = Connection::get(); $this->api = PayPalCommerce::get_api( $this->connection ); if ( is_null( $this->api ) ) { return; } if ( $this->is_submitted_payment_amount_corrupted( $entry ) ) { return; } if ( empty( $entry['fields'][ $this->field['id'] ]['orderID'] ) && empty( $entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ) && empty( $entry['fields'][ $this->field['id'] ]['subscriptionID'] ) ) { $this->display_errors(); $this->maybe_add_conditional_logic_log(); return; } // Before proceeding, check if any basic errors were detected. if ( ! $this->is_form_ok() || ! $this->is_form_processed() ) { $this->display_errors(); return; } if ( ! empty( $entry['fields'][ $this->field['id'] ]['orderID'] ) ) { $this->capture_single(); } if ( ! empty( $entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ) ) { $this->subscription_processor_capture(); } if ( ! empty( $entry['fields'][ $this->field['id'] ]['subscriptionID'] ) ) { $this->activate_subscription(); } // Check and display an error if exists. $this->display_errors(); } /** * Capture a single order. * * @since 1.10.0 */ private function capture_single(): void { // For the Fastlane, use the one-step order. if ( $this->entry['fields'][ $this->field['id'] ]['source'] === 'fastlane' ) { $this->process_fastlane(); return; } $order_response = $this->api->capture( $this->entry['fields'][ $this->field['id'] ]['orderID'] ); if ( $order_response->has_errors() ) { $error_title = esc_html__( 'This payment cannot be processed because there was an error with the capture order API call.', 'wpforms-lite' ); $this->errors[] = $error_title; $this->log_errors( $error_title, $order_response->get_response_message() ); return; } $order_data = $order_response->get_body(); // Validate card payment status. if ( empty( $order_data['purchase_units'][0]['payments']['captures'][0]['status'] ) || $order_data['purchase_units'][0]['payments']['captures'][0]['status'] !== 'COMPLETED' ) { $error_title = esc_html__( 'This payment cannot be processed because it was declined by payment processor.', 'wpforms-lite' ); $this->errors[] = $error_title; $this->log_errors( $error_title, $order_data ); return; } if ( isset( $order_data['payment_source']['card'] ) ) { $card_details = $order_data['payment_source']['card']; $details = [ 'name' => $card_details['name'] ?? '', 'last4' => $card_details['last_digits'] ?? '', 'expiry' => $card_details['expiry'] ?? '', 'brand' => $card_details['brand'] ?? '', ]; wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = implode( "\n", array_filter( $details ) ); return; } $this->set_form_field_value_for_method( $order_data ); } /** * Create order for Fastlane. * * @since 1.10.0 */ private function process_fastlane(): void { $fastlane_token = trim( $this->entry['fields'][ $this->field['id'] ]['orderID'] ); $order_payload = $this->build_fastlane_order_data( $fastlane_token ); $create_order_response = $this->api->create_order( $order_payload ); if ( $create_order_response->has_errors() ) { $error_title = esc_html__( 'This payment cannot be processed because there was an error with the create order API call.', 'wpforms-lite' ); $this->errors[] = $error_title; $this->log_errors( $error_title, $create_order_response->get_response_message() ); return; } $create_order_response_body = $create_order_response->get_body(); // Replace a token with the real Order ID for downstream logic. $this->entry['fields'][ $this->field['id'] ]['orderID'] = sanitize_text_field( $create_order_response_body['id'] ); wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = 'Fastlane'; } /** * Activate subscription. * * @since 1.10.0 */ private function activate_subscription(): void { $subscription_id = $this->entry['fields'][ $this->field['id'] ]['subscriptionID']; $subscription_response = $this->api->activate_subscription( $subscription_id ); if ( $subscription_response->has_errors() ) { $error_title = esc_html__( 'This subscription cannot be activated because there was an error with the activation API call.', 'wpforms-lite' ); $this->errors[] = $error_title; $this->log_errors( $error_title, $subscription_response->get_response_message() ); return; } $subscription_data = $this->get_subscription_data(); if ( empty( $subscription_data['subscriber'] ) ) { wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = 'Checkout'; return; } wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = implode( ' ', array_values( $subscription_data['subscriber']['name'] ) ) . "\n" . $subscription_data['subscriber']['email_address']; } /** * Capture subscription processor order. * * @since 1.10.0 */ private function subscription_processor_capture(): void { $subscription_processor_id = $this->entry['fields'][ $this->field['id'] ]['subscriptionProcessorID']; $subscription_response = $this->api->subscription_processor_capture( $subscription_processor_id ); if ( $subscription_response->has_errors() ) { $error_title = esc_html__( 'This subscription cannot be activated because there was an error with the capture API call.', 'wpforms-lite' ); $this->errors[] = $error_title; $this->log_errors( $error_title, $subscription_response->get_response_message() ); return; } wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = 'Checkout'; } /** * Update entry details and add meta for a successful payment. * * @since 1.10.0 * * @param array $fields Final/sanitized submitted field data. * @param array $entry Copy of the original $_POST. * @param array $form_data Form data and settings. * @param string $entry_id Entry ID. * * @noinspection PhpMissingParamTypeInspection */ public function update_entry_meta( $fields, array $entry, array $form_data, string $entry_id ): void { $fields = (array) $fields; if ( empty( $entry_id ) || $this->errors || ! $this->api || empty( $this->field ) || ( empty( $entry['fields'][ $this->field['id'] ]['orderID'] ) && empty( $entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ) && empty( $entry['fields'][ $this->field['id'] ]['subscriptionID'] ) ) ) { return; } $order_data = $this->get_order_data(); if ( empty( $order_data ) ) { $order_data = $this->get_subscription_processor_data(); } if ( empty( $order_data ) ) { $order_data = $this->get_subscription_data(); } // If we don't have order data, bail. if ( empty( $order_data ) ) { return; } wpforms()->obj( 'entry' )->update( $entry_id, [ 'type' => 'payment', ], '', '', [ 'cap' => false ] ); /** * Fire when entry details and meta were successfully updated. * * @since 1.10.0 * * @param array $fields Final/sanitized submitted field data. * @param array $form_data Form data and settings. * @param string $entry_id Entry ID. * @param array $order_data Response order data. * @param Process $process Process class instance. */ do_action( 'wpforms_paypal_commerce_process_update_entry_meta', $fields, $form_data, $entry_id, $order_data, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Logic that helps decide if we should send completed payments notifications. * * @since 1.10.0 * * @param bool $process Whether to process or not. * @param array $fields Form fields. * @param array $form_data Form data. * @param int $notification_id Notification ID. * @param string $context In which context this email is sent. * * @return bool */ public function process_email( $process, array $fields, array $form_data, int $notification_id, string $context ): bool { if ( ! $process ) { return false; } if ( ! Helpers::is_paypal_commerce_enabled( $form_data ) ) { return (bool) $process; } if ( empty( $form_data['settings']['notifications'][ $notification_id ][ PayPalCommerce::SLUG ] ) ) { return (bool) $process; } if ( empty( $this->entry['fields'][ $this->field['id'] ]['orderID'] ) && empty( $this->entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ) && empty( $this->entry['fields'][ $this->field['id'] ]['subscriptionID'] ) ) { return false; } return ! $this->errors && $this->api; } /** * Get order data. * * @since 1.10.0 * * @return array */ private function get_order_data(): array { // If the payment processing is not allowed, bail. if ( ! $this->is_payment_saving_allowed() ) { return []; } static $order_data; if ( $order_data === null ) { $order_id = $this->entry['fields'][ $this->field['id'] ]['orderID'] ?? null; $order_data = $order_id ? $this->api->get_order( $order_id ) : []; } return $order_data; } /** * Get subscription processor data. * * @since 1.10.0 * * @return array */ private function get_subscription_processor_data(): array { // If the payment processing is not allowed, bail. if ( ! $this->is_payment_saving_allowed() ) { return []; } static $subscription_processor_data; if ( $subscription_processor_data === null ) { $id = $this->entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ?? null; $subscription_processor_data = $id ? $this->api->subscription_processor_get( $id ) : []; } return $subscription_processor_data; } /** * Get Subscription data. * * @since 1.10.0 * * @return array */ private function get_subscription_data(): array { // If the payment processing is not allowed, bail. if ( ! $this->is_payment_saving_allowed() ) { return []; } static $subscription_data; if ( $subscription_data === null ) { $subscription_id = $this->entry['fields'][ $this->field['id'] ]['subscriptionID'] ?? null; $subscription_data = $subscription_id ? $this->api->get_subscription( $subscription_id ) : []; } return $subscription_data; } /** * Add details to payment data. * * @since 1.10.0 * * @param array $payment_data Payment data args. * @param array $fields Form fields. * @param array $form_data Form data. * * @return array * * @noinspection PhpMissingParamTypeInspection */ public function prepare_payment_data( $payment_data, array $fields, array $form_data ): array { $payment_data = (array) $payment_data; // If there are errors or the API is not initialized, return the original payment data. if ( $this->errors || ! $this->api ) { return $payment_data; } // Determine whether this is a one-time payment. $order_data = $this->get_order_data(); if ( ! empty( $order_data ) ) { $payment_data['transaction_id'] = sanitize_text_field( $order_data['purchase_units'][0]['payments']['captures'][0]['id'] ); $payment_data['title'] = $this->get_payment_title( $order_data, $form_data ); return $this->add_generic_payment_data( $payment_data ); } $subscription_processor_data = $this->get_subscription_processor_data(); if ( ! empty( $subscription_processor_data ) ) { $payment_data['transaction_id'] = sanitize_text_field( $subscription_processor_data['purchase_units'][0]['payments']['captures'][0]['id'] ); $payment_data['title'] = $this->get_payment_title( $subscription_processor_data, $form_data ); $payment_data['subscription_status'] = (int) $form_data['payments'][ PayPalCommerce::SLUG ]['recurring'][0]['total_cycles'] === 1 ? 'completed' : 'active'; $payment_data['subscription_id'] = sanitize_text_field( $subscription_processor_data['id'] ); $payment_data['customer_id'] = sanitize_text_field( $subscription_processor_data['payment_source']['paypal']['account_id'] ); return $this->add_generic_payment_data( $payment_data ); } // Determine whether it is a subscription. $subscription_data = $this->get_subscription_data(); if ( ! empty( $subscription_data ) ) { $payment_data['subscription_status'] = $this->get_subscription_total_cycles( $form_data, $subscription_data['plan_id'] ) === 1 ? 'completed' : 'not-synced'; $payment_data['subscription_id'] = sanitize_text_field( $subscription_data['id'] ); $payment_data['customer_id'] = sanitize_text_field( $subscription_data['subscriber']['payer_id'] ); $payment_data['title'] = $this->get_payment_title( $subscription_data, $form_data ); $this->maybe_log_matched_subscriptions( $subscription_data['plan_id'] ); return $this->add_generic_payment_data( $payment_data ); } return $payment_data; } /** * Get Payment title. * * @since 1.10.0 * * @param array $order_data Order data. * @param array $form_data Form data. * * @return string Payment title. */ private function get_payment_title( array $order_data, array $form_data ): string { // Check if the card was used before using the card name field. if ( $this->get_payment_method_type() === 'card' && ! empty( $this->entry['fields'][ $this->field['id'] ]['cardname'] ) ) { return sanitize_text_field( $this->entry['fields'][ $this->field['id'] ]['cardname'] ); } $customer_name = $this->get_customer_name( $order_data, $form_data ); if ( $customer_name ) { return sanitize_text_field( $customer_name ); } // Use the name on the card provided if customer name not available. if ( ! empty( $order_data['payment_source']['card']['name'] ) ) { return sanitize_text_field( $order_data['payment_source']['card']['name'] ); } if ( ! empty( $order_data['subscriber']['email_address'] ) ) { return sanitize_email( $order_data['subscriber']['email_address'] ); } return ''; } /** * Add payment meta for a successful one-time or subscription payment. * * @since 1.10.0 * * @param array $payment_meta Payment meta. * @param array $fields Sanitized submitted field data. * @param array $form_data Form data and settings. * * @return array * * @noinspection PhpMissingParamTypeInspection */ public function prepare_payment_meta( $payment_meta, array $fields, array $form_data ): array { $payment_meta = (array) $payment_meta; // Retrieve order data for one-time payments. $order_data = $this->get_order_data(); if ( ! empty( $order_data ) ) { $payment_meta = $this->add_credit_card_meta( $payment_meta, $order_data ); $payment_meta['method_type'] = sanitize_text_field( $this->get_payment_method_type() ); $payment_meta['log'] = $this->format_payment_log( sprintf( 'PayPal Commerce Order created. (Order ID: %s)', $order_data['id'] ) ); return $payment_meta; } $subscription_processor_data = $this->get_subscription_processor_data(); if ( ! empty( $subscription_processor_data ) ) { $payment_meta['processor_type'] = 'paypal'; $payment_meta['method_type'] = 'checkout'; $payment_meta['subscription_period'] = $subscription_processor_data['interval']; $payment_meta['payer_email'] = sanitize_email( $subscription_processor_data['payment_source']['paypal']['email_address'] ?? '' ); $payment_meta['log'] = $this->format_payment_log( sprintf( 'PayPal Commerce Subscription created. (Subscription ID: %s)', $subscription_processor_data['id'] ) ); return $payment_meta; } // Retrieve subscription data. $subscription_data = $this->get_subscription_data(); if ( ! empty( $subscription_data ) ) { $payment_meta['method_type'] = 'checkout'; $payment_meta['subscription_period'] = $this->get_subscription_period( $form_data, $subscription_data['plan_id'] ); $payment_meta['log'] = $this->format_payment_log( sprintf( 'PayPal Commerce Subscription created. (Subscription ID: %s)', $subscription_data['id'] ) ); return $payment_meta; } // If no order or subscription data was found, return the payment meta. return $payment_meta; } /** * Add payment info for the successful payment. * * @since 1.10.0 * * @param int $payment_id Payment ID. * @param array $fields Final/sanitized submitted field data. * @param array $form_data Form data and settings. */ public function process_payment_saved( int $payment_id, array $fields, array $form_data ): void { // Determine whether this is a subscription payment. $subscription_data = $this->get_subscription_data(); if ( ! empty( $subscription_data ) ) { $this->schedule_subscription_update( $payment_id, $subscription_data['id'] ); return; } // Determine whether this is a subscription processor payment. $subscription_processor_data = $this->get_subscription_processor_data(); if ( ! empty( $subscription_processor_data ) ) { $this->add_processed_log( $payment_id, $subscription_processor_data['purchase_units'][0]['payments']['captures'][0]['id'] ); return; } // Determine whether this is a one-time payment. $order_data = $this->get_order_data(); if ( empty( $order_data ) ) { return; } $this->add_processed_log( $payment_id, $order_data['purchase_units'][0]['payments']['captures'][0]['id'] ); } /** * Schedule update subscription due to some delay in PayPal API. * * @since 1.10.0 * * @param int $payment_id Payment ID. * @param string $subscription_id Subscription ID. */ private function schedule_subscription_update( int $payment_id, string $subscription_id ): void { $tasks = wpforms()->obj( 'tasks' ); $tasks->create( self::SUBSCRIPTION_TASK ) ->params( $payment_id, $subscription_id ) ->once( time() + 60 ) ->register(); } /** * Update subscription transaction ID in task due to some delay in PayPal API. * * @since 1.10.0 * * @param int $meta_id Action meta id. */ public function update_subscription_data_scheduled_task( $meta_id ): void { $meta_id = (int) $meta_id; $params = ( new Meta() )->get( $meta_id ); if ( ! $params ) { return; } [ $payment_id, $subscription_id ] = $params->data; $api = PayPalCommerce::get_api( Connection::get() ); $transactions = $api->get_subscription_transactions( $subscription_id ); $transaction_id = $transactions ? end( $transactions )['id'] : ''; $this->add_processed_log( $payment_id, $transaction_id ); wpforms()->obj( 'payment' )->update( $payment_id, [ 'transaction_id' => $transaction_id ], '', '', [ 'cap' => false ] ); } /** * Add the processed payment log. * * @since 1.10.0 * * @param string $payment_id Payment id. * @param string $transaction_id Transaction id. */ private function add_processed_log( string $payment_id, string $transaction_id ): void { wpforms()->obj( 'payment_meta' )->add_log( $payment_id, sprintf( 'PayPal Commerce Payment processed. (Transaction ID: %s)', $transaction_id ) ); } /** * Return payment log value. * * @since 1.10.0 * * @param string $value Log value. * * @return string */ private function format_payment_log( string $value ): string { return wp_json_encode( [ 'value' => sanitize_text_field( $value ), 'date' => gmdate( 'Y-m-d H:i:s' ), ] ); } /** * Determine the payment method name. * If PayPal, return 'checkout', otherwise 'card'. * * @since 1.10.0 * * @return string */ private function get_payment_method_type(): string { $source = $this->entry['fields'][ $this->field['id'] ]['source'] ?? ''; $process_method = $this->get_supported_process_method( $source ); if ( $process_method ) { return $process_method->get_type(); } return $source === 'paypal' ? 'checkout' : 'card'; } /** * Get Customer name. * * @since 1.10.0 * * @param array $order_data Order data. * @param array $form_data Form data and settings. * * @return string */ private function get_customer_name( array $order_data, array $form_data ): string { if ( ! empty( $order_data['payer']['name'] ) ) { return implode( ' ', array_values( $order_data['payer']['name'] ) ); } if ( ! empty( $order_data['subscriber']['name'] ) ) { return implode( ' ', array_values( $order_data['subscriber']['name'] ) ); } $customer_name = $this->get_customer_title_for_method( $order_data ); if ( $customer_name ) { return $customer_name; } $customer_name = []; $form_settings = $form_data['payments'][ PayPalCommerce::SLUG ]; // Billing first name. if ( ! empty( $this->fields[ $form_settings['name'] ]['first'] ) ) { $customer_name['first_name'] = $this->fields[ $form_settings['name'] ]['first']; } // Billing last name. if ( ! empty( $this->fields[ $form_settings['name'] ]['last'] ) ) { $customer_name['last_name'] = $this->fields[ $form_settings['name'] ]['last']; } if ( empty( $customer_name['first_name'] ) && empty( $customer_name['last_name'] ) && ! empty( $this->fields[ $form_settings['name'] ]['value'] ) ) { $customer_name['first_name'] = $this->fields[ $form_settings['name'] ]['value']; } return implode( ' ', array_values( $customer_name ) ); } /** * Add generic payment data. * * @since 1.10.0 * * @param array $payment_data Payment data. * * @return array */ private function add_generic_payment_data( array $payment_data ): array { $payment_data['status'] = 'processed'; $payment_data['gateway'] = PayPalCommerce::SLUG; $payment_data['mode'] = Helpers::is_sandbox_mode() ? 'test' : 'live'; return $payment_data; } /** * Add credit card meta. * * @since 1.10.0 * * @param array $payment_meta Payment meta. * @param array $order_data Order data. * * @return array */ private function add_credit_card_meta( array $payment_meta, array $order_data ): array { // Bail early if the payment source is not available. if ( empty( $order_data['payment_source'] ) ) { return $payment_meta; } $payment_source = $order_data['payment_source']; // Add the credit card holder name, e.g., John Doe. if ( ! empty( $this->entry['fields'][ $this->field['id'] ]['cardname'] ) ) { $payment_meta['credit_card_name'] = sanitize_text_field( $this->entry['fields'][ $this->field['id'] ]['cardname'] ); } // Add the credit card brand name, e.g., Visa, MasterCard, etc. if ( ! empty( $payment_source['card']['brand'] ) ) { $payment_meta['credit_card_method'] = sanitize_text_field( strtolower( $payment_source['card']['brand'] ) ); } // Add credit card last 4 digits, e.g., 1234, 5678, etc. if ( ! empty( $payment_source['card']['last_digits'] ) ) { $payment_meta['credit_card_last4'] = sanitize_text_field( $payment_source['card']['last_digits'] ); } // Add credit card expiry date, e.g., 2029-11, 2024-10, etc. if ( ! empty( $payment_source['card']['expiry'] ) ) { $payment_meta['credit_card_expires'] = sanitize_text_field( $payment_source['card']['expiry'] ); } return $payment_meta; } /** * Get the subscription period by plan id. * * @since 1.10.0 * * @param array $form_data Form data. * @param string $pp_plan_id Subscription plan id. * * @return string */ private function get_subscription_period( array $form_data, string $pp_plan_id ): string { $plan_setting = $this->get_plan_settings_by_plan_id( $form_data, $pp_plan_id ); return str_replace( '-', '', $plan_setting['recurring_times'] ?? '' ); } /** * Get the subscription total cycles by plan id. * * @since 1.10.0 * * @param array $form_data Form data. * @param string $pp_plan_id Subscription plan id. * * @return int */ private function get_subscription_total_cycles( array $form_data, string $pp_plan_id ): int { $plan_setting = $this->get_plan_settings_by_plan_id( $form_data, $pp_plan_id ); return (int) $plan_setting['total_cycles'] ?? 0; } /** * Get the subscription plan settings by plan id. * * @since 1.10.0 * * @param array $form_data Form data. * @param string $pp_plan_id Subscription plan id. * * @return array */ private function get_plan_settings_by_plan_id( array $form_data, string $pp_plan_id ): array { foreach ( $form_data['payments'][ PayPalCommerce::SLUG ]['recurring'] as $recurring ) { if ( $recurring['pp_plan_id'] !== $pp_plan_id ) { continue; } return $recurring; } return []; } /** * Check if the form has errors before payment processing. * * @since 1.10.0 * * @return bool */ private function is_form_processed(): bool { // Bail in case there are form processing errors. if ( ! empty( wpforms()->obj( 'process' )->errors[ $this->form_id ] ) ) { return false; } return $this->is_card_field_visibility_ok(); } /** * Check if there is at least one visible (not hidden by conditional logic) card field in the form. * * @since 1.10.0 * * @return bool */ private function is_card_field_visibility_ok(): bool { if ( empty( $this->field ) ) { return false; } // If the form contains no fields with conditional logic, the card field is visible by default. if ( empty( $this->form_data['conditional_fields'] ) ) { return true; } // If the field is NOT in the array of conditional fields, it's visible. if ( ! in_array( $this->field['id'], $this->form_data['conditional_fields'], true ) ) { return true; } // If the field IS in the array of conditional fields and marked as visible, it's visible. if ( ! empty( $this->field['visible'] ) ) { return true; } return false; } /** * Display form errors. * * @since 1.10.0 */ private function display_errors(): void { if ( ! $this->errors || ! is_array( $this->errors ) ) { return; } // Check if the form contains a required credit card. If it does // and there was an error, return the error to the user and prevent // the form from being submitted. This should not occur under normal // circumstances. if ( empty( $this->field ) || empty( $this->form_data['fields'][ $this->field['id'] ] ) ) { return; } if ( ! empty( $this->form_data['fields'][ $this->field['id'] ]['required'] ) ) { wpforms()->obj( 'process' )->errors[ $this->form_id ]['footer'] = implode( '<br>', $this->errors ); } } /** * Determine if payment saving allowed, by checking if the form has a payment field, and the API is available. * * @since 1.10.0 * * @return bool */ private function is_payment_saving_allowed(): bool { return ! empty( $this->field ) && $this->api; } /** * Check the submitted payment amount whether it was corrupted. * If so, throw an error and block submission. * * @since 1.10.0 * * @param array $entry Submitted entry data. * * @return bool */ private function is_submitted_payment_amount_corrupted( array $entry ): bool { $amount_corrupted = false; $source = ! empty( $entry['fields'][ $this->field['id'] ]['source'] ) ? $entry['fields'][ $this->field['id'] ]['source'] : ''; // Skip for Fastlane with since the order is not created yet. if ( $source === 'fastlane' ) { return false; } // Check form amount for a single payment. if ( ! empty( $entry['fields'][ $this->field['id'] ]['orderID'] ) ) { $order = $this->api->get_order( $this->entry['fields'][ $this->field['id'] ]['orderID'] ); // Add tax if it has been applied through WP filter. $tax_total = ! empty( $order['purchase_units'][0]['amount']['breakdown']['tax_total']['value'] ) ? (float) $order['purchase_units'][0]['amount']['breakdown']['tax_total']['value'] : 0; $submitted_amount = Helpers::format_amount_for_api_call( (float) $this->amount + $tax_total ); $amount_corrupted = ! empty( $order ) && (float) $submitted_amount !== (float) $order['purchase_units'][0]['amount']['value']; } // Check the form amount for subscription processor payment. if ( ! $amount_corrupted && ! empty( $entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ) ) { $subscription_processor = $this->api->subscription_processor_get( $this->entry['fields'][ $this->field['id'] ]['subscriptionProcessorID'] ); // Add tax if it has been applied through WP filter. $tax_total = ! empty( $subscription_processor['purchase_units'][0]['amount']['breakdown']['tax_total']['value'] ) ? (float) $subscription_processor['purchase_units'][0]['amount']['breakdown']['tax_total']['value'] : 0; $submitted_amount = Helpers::format_amount_for_api_call( (float) $this->amount + $tax_total ); $amount_corrupted = ! empty( $subscription_processor ) && (float) $submitted_amount !== (float) $subscription_processor['purchase_units'][0]['amount']['value']; } // Check form amount for subscription payment. if ( ! $amount_corrupted && ! empty( $entry['fields'][ $this->field['id'] ]['subscriptionID'] ) ) { $subscription = $this->api->get_subscription( $entry['fields'][ $this->field['id'] ]['subscriptionID'], [ 'fields' => 'plan' ] ); $amount_corrupted = ! empty( $subscription ) && (float) $this->amount !== (float) $subscription['plan']['billing_cycles'][0]['pricing_scheme']['fixed_price']['value']; } // Prevent form submission and throw an error. if ( $amount_corrupted ) { wpforms()->obj( 'process' )->errors[ $this->form_id ]['footer'] = esc_html__( 'Irregular activity detected. Your submission has been declined.', 'wpforms-lite' ); return true; } return false; } /** * Log if more than one plan matched on the form submission. * * @since 1.10.0 * * @param string $matched_plan_id Already matched and executed plan. * * @noinspection PhpMissingParamTypeInspection */ protected function maybe_log_matched_subscriptions( $matched_plan_id ): void { } /** * Add a log record if a payment was stopped by conditional logic. * * @since 1.10.0 */ protected function maybe_add_conditional_logic_log(): void { } /** * Build Fastlane order payload to match ProcessSingleAjax::prepare_single_order_data(). * Intentionally skipping billing address as it comes with a single use token. * * @since 1.10.0 * * @param string $fastlane_token Fastlane single-use token. * * @return array */ private function build_fastlane_order_data( string $fastlane_token ): array { $settings = $this->form_data['payments'][ PayPalCommerce::SLUG ] ?? []; $this->currency = $this->get_currency(); $amount_string = Helpers::format_amount_for_api_call( (float) $this->amount ); $is_shipping_address = isset( $settings['shipping_address'] ) && $settings['shipping_address'] !== '' && $this->is_address_field_valid_from_fields( $settings['shipping_address'] ); $order_data = []; $order_data['intent'] = 'CAPTURE'; $order_data['application_context']['shipping_preference'] = $is_shipping_address ? 'SET_PROVIDED_ADDRESS' : 'NO_SHIPPING'; $order_data['application_context']['user_action'] = 'CONTINUE'; $order_data['purchase_units'][0] = [ 'amount' => [ 'value' => $amount_string, 'currency_code' => $this->currency, 'breakdown' => [ 'item_total' => [ 'value' => $amount_string, 'currency_code' => $this->currency, ], 'shipping' => [ 'value' => 0, 'currency_code' => $this->currency, ], ], ], 'description' => $this->get_order_description(), 'items' => $this->get_order_items(), 'shipping' => [ 'name' => [ 'full_name' => '', ], ], ]; if ( $is_shipping_address ) { $order_data['purchase_units'][0]['shipping']['address'] = $this->map_address_field_from_fields( $settings['shipping_address'] ); } // Build the payment source for the card (Fastlane token). $order_data['payment_source']['card'] = [ 'single_use_token' => $fastlane_token, 'attributes' => [ 'vault' => [ 'store_in_vault' => 'ON_SUCCESS', ], ], ]; /** * Allow 3rd-parties to filter Fastlane order data in the Process context. * * @since 1.10.0 * * @param array $order_data Order data. * @param array $form_data Form data. * @param float $amount Order amount. */ return (array) apply_filters( 'wpforms_paypal_commerce_process_fastlane_order_data', $order_data, $this->form_data, (float) $this->amount ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName } /** * Retrieve order items. * * @since 1.10.0 * * @return array */ protected function get_order_items(): array { /** * Filter order items types. * * @since 1.10.0 * * @param array $types The order items types. */ $types = (array) apply_filters( 'wpforms_paypal_commerce_process_single_ajax_get_types', wpforms_payment_fields() ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName $items = []; foreach ( $this->form_data['fields'] as $field_id => $field ) { if ( empty( $field['type'] ) || ! in_array( $field['type'], $types, true ) ) { continue; } // Skip the payment field that is not filled in or hidden by CL. if ( ! isset( $this->entry['fields'][ $field_id ] ) || wpforms_is_empty_string( $this->entry['fields'][ $field_id ] ) ) { continue; } $items = $this->prepare_order_line_item( $items, $field ); } return $items; } /** * Prepare order line item. * * @since 1.10.0 * * @param array $items Items. * @param array $field Field data. * * @return array */ protected function prepare_order_line_item( array $items, array $field ): array { $field_id = absint( $field['id'] ); $quantity = 1; $name = empty( $field['label'] ) ? sprintf( /* translators: %d - Field ID. */ esc_html__( 'Field #%d', 'wpforms-lite' ), $field_id ) : $field['label']; if ( ! empty( $field['enable_quantity'] ) ) { $quantity = isset( $this->entry['quantities'][ $field['id'] ] ) ? (int) $this->entry['quantities'][ $field['id'] ] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing } if ( ! $quantity ) { return $items; } if ( empty( $field['choices'] ) ) { $items[] = [ 'name' => wp_html_excerpt( $name, 124, '...' ), // Limit to 127 characters. 'quantity' => $quantity, 'unit_amount' => [ 'value' => Helpers::format_amount_for_api_call( wpforms_sanitize_amount( $this->entry['fields'][ $field_id ] ) ), 'currency_code' => $this->currency, ], ]; return $items; } $choices = ! is_array( $this->entry['fields'][ $field_id ] ) ? [ $this->entry['fields'][ $field_id ] ] : $this->entry['fields'][ $field_id ]; foreach ( $choices as $choice ) { if ( empty( $field['choices'][ $choice ] ) ) { continue; } $choice_name = empty( $field['choices'][ $choice ]['label'] ) ? sprintf( /* translators: %d - choice ID. */ esc_html__( 'Choice %d', 'wpforms-lite' ), absint( $choice ) ) : $field['choices'][ $choice ]['label']; $items[] = [ 'name' => wp_html_excerpt( $name . ': ' . $choice_name, 124, '...' ), // Limit to 127 characters. 'quantity' => $quantity, 'unit_amount' => [ 'value' => Helpers::format_amount_for_api_call( wpforms_sanitize_amount( $field['choices'][ $choice ]['value'] ) ), 'currency_code' => $this->currency, ], ]; } return $items; } /** * Retrieve the customer title associated with the processing method for the given order data. * * @since 1.10.0 * * @param array $order_data The order data used to determine the processing method. * * @return string */ private function get_customer_title_for_method( array $order_data ): string { $process_method = $this->get_supported_process_method_for_order( $order_data ); if ( ! $process_method ) { return ''; } return $process_method->get_customer_name( $order_data ); } /** * Sets the form field value using the appropriate processing method for the given order data. * * @since 1.10.0 * * @param array $order_data The order data used to determine the processing method and extract the field value. */ private function set_form_field_value_for_method( array $order_data ): void { $process_method = $this->get_supported_process_method_for_order( $order_data ); if ( ! $process_method ) { wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = 'Checkout'; return; } wpforms()->obj( 'process' )->fields[ $this->field['id'] ]['value'] = $process_method->get_form_field_value( $order_data ); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�