d( $this->amount * 0.03, 2 ) * $amount_decimals );
}
// Payment description.
if ( ! empty( $this->settings['payment_description'] ) ) {
$args['description'] = html_entity_decode( $this->settings['payment_description'], ENT_COMPAT, 'UTF-8' );
}
// Receipt email.
if ( isset( $this->settings['receipt_email'] ) && $this->settings['receipt_email'] !== '' && ! empty( $this->fields[ $this->settings['receipt_email'] ]['value'] ) ) {
$args['receipt_email'] = sanitize_email( $this->fields[ $this->settings['receipt_email'] ]['value'] );
}
// Customer email.
if ( isset( $this->settings['customer_email'] ) && $this->settings['customer_email'] !== '' && ! empty( $this->fields[ $this->settings['customer_email'] ]['value'] ) ) {
$args['customer_email'] = sanitize_email( $this->fields[ $this->settings['customer_email'] ]['value'] );
}
// Customer name.
if ( isset( $this->settings['customer_name'] ) && $this->settings['customer_name'] !== '' && ! empty( $this->fields[ $this->settings['customer_name'] ]['value'] ) ) {
$args['customer_name'] = sanitize_text_field( $this->fields[ $this->settings['customer_name'] ]['value'] );
}
// Customer address.
if ( isset( $this->settings['customer_address'] ) && $this->settings['customer_address'] !== '' ) {
$args['customer_address'] = $this->map_address_field( $this->fields[ $this->settings['customer_address'] ], $this->settings['customer_address'] );
}
// Shipping address.
if ( isset( $this->settings['shipping_address'] ) && $this->settings['shipping_address'] !== '' ) {
$args['shipping']['name'] = $args['customer_name'] ?? '';
$args['shipping']['address'] = $this->map_address_field( $this->fields[ $this->settings['shipping_address'] ], $this->settings['shipping_address'] );
}
$this->api->process_single( $args );
// Set payment processing flag.
$this->is_payment_processed = true;
$this->update_credit_card_field_value();
$this->process_api_error( 'single' );
}
/**
* Process a subscription payment.
*
* @since 1.8.2
*/
public function process_payment_subscription() {
if ( Helpers::is_legacy_payment_settings( $this->form_data ) ) {
$this->process_legacy_payment_subscription();
return;
}
$args = $this->get_base_subscription_args();
foreach ( $this->settings['recurring'] as $recurring ) {
if ( ! $this->is_subscription_plan_valid( $recurring ) ) {
continue;
}
$args['email'] = sanitize_email( $this->fields[ $recurring['email'] ]['value'] );
$args['settings'] = $recurring;
// Customer name.
if ( isset( $recurring['customer_name'] ) && $recurring['customer_name'] !== '' && ! empty( $this->fields[ $recurring['customer_name'] ]['value'] ) ) {
$args['customer_name'] = sanitize_text_field( $this->fields[ $recurring['customer_name'] ]['value'] );
}
// Customer address.
if ( isset( $recurring['customer_address'] ) && $recurring['customer_address'] !== '' ) {
$args['customer_address'] = $this->map_address_field( $this->fields[ $recurring['customer_address'] ], $recurring['customer_address'] );
}
$this->process_subscription( $args );
return;
}
if ( ! empty( $this->settings['enable_one_time'] ) ) {
$this->process_payment_single();
return;
}
$this->log_error(
esc_html__( 'Stripe Subscription payment stopped validation error.', 'wpforms-lite' ),
$this->fields,
'conditional_logic'
);
}
/**
* Validate plan before process.
*
* @since 1.8.4
*
* @param array $plan Plan settings.
*
* @return bool
*/
protected function is_subscription_plan_valid( $plan ) {
return ! empty( $plan['email'] ) && $this->is_recurring_settings_ok( $plan );
}
/**
* Update the credit card field value to contain basic details.
*
* @since 1.8.2
*/
public function update_credit_card_field_value() {
foreach ( $this->fields as $field_id => $field ) {
if ( empty( $field['type'] ) || $this->api->get_config( 'field_slug' ) !== $field['type'] ) {
continue;
}
$details = $this->api->get_charge_details( [ 'name', 'last4', 'brand' ] );
if ( ! empty( $details['last4'] ) ) {
$details['last4'] = 'xxxx xxxx xxxx ' . $details['last4'];
}
if ( ! empty( $details['brand'] ) ) {
$details['brand'] = ucfirst( $details['brand'] );
}
$details = is_array( $details ) && ! empty( $details ) ? implode( "\n", array_filter( $details ) ) : '-';
/**
* This filter allows to overwrite a Style Credit Card value in saved entry.
*
* @since 1.8.2
*
* @param array $details Card details.
* @param object $payment Stripe payment objects.
*/
wpforms()->get( 'process' )->fields[ $field_id ]['value'] = apply_filters( 'wpforms_stripe_creditcard_value', $details, $this->api->get_payment() ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
/**
* Check if there is at least one visible (not hidden by conditional logic) card field in the form.
*
* @since 1.8.2
*/
protected function is_card_field_visibility_ok() {
// 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;
}
foreach ( $this->fields as $field ) {
if ( empty( $field['type'] ) || $this->api->get_config( 'field_slug' ) !== $field['type'] ) {
continue;
}
// if the field is NOT in array of conditional fields, it's visible.
if ( ! in_array( $field['id'], $this->form_data['conditional_fields'], true ) ) {
return true;
}
// if the field IS in array of conditional fields and marked as visible, it's visible.
if ( ! empty( $field['visible'] ) ) {
return true;
}
}
return false;
}
/**
* Log payment error.
*
* @since 1.8.2
*
* @param string $title Error title.
* @param string $message Error message.
* @param string $level Error level to add to 'payment' error level.
*/
protected function log_error( $title, $message = '', $level = 'error' ) {
if ( $message instanceof ApiErrorException ) {
$body = $message->getJsonBody();
$message = isset( $body['error']['message'] ) ? $body['error'] : $message->getMessage();
}
wpforms_log(
$title,
$message,
[
'type' => [ 'payment', $level ],
'form_id' => $this->form_id,
]
);
}
/**
* Collect errors from API and turn it into form errors.
*
* @since 1.8.2
*
* @param string $type Payment time (e.g. 'single' or 'subscription').
*/
protected function process_api_error( $type ) {
$message = $this->api->get_error();
if ( empty( $message ) ) {
return;
}
$message = sprintf(
/* translators: %s - error message. */
esc_html__( 'Payment Error: %s', 'wpforms-lite' ),
$message
);
$this->display_error( $message );
if ( $type === 'subscription' ) {
$title = esc_html__( 'Stripe subscription payment stopped by error', 'wpforms-lite' );
} else {
$title = esc_html__( 'Stripe payment stopped by error', 'wpforms-lite' );
}
$this->log_error( $title, $this->api->get_exception() );
}
/**
* Display form error.
*
* @since 1.8.2
*
* @param string $error Error to display.
*/
private function display_error( $error ) {
if ( ! $error ) {
return;
}
$field_slug = $this->api->get_config( 'field_slug' );
// 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.
foreach ( $this->form_data['fields'] as $field ) {
if ( empty( $field['type'] ) || $field_slug !== $field['type'] ) {
continue;
}
if ( ! empty( $field['required'] ) ) {
wpforms()->get( 'process' )->errors[ $this->form_id ]['footer'] = $error;
return;
}
}
}
/**
* Process card error from Stripe API exception and adds rate limit tracking.
*
* @since 1.8.2
*
* @param ApiErrorException $e Stripe API exception to process.
*/
public function process_card_error( $e ) {
if ( Helpers::get_stripe_mode() === 'test' ) {
return;
}
if ( ! is_a( $e, '\WPForms\Vendor\Stripe\Exception\CardException' ) ) {
return;
}
/**
* Allow to filter Stripe process card error.
*
* @since 1.8.2
*
* @param bool $flag True if any error.
*/
if ( ! apply_filters( 'wpforms_stripe_process_process_card_error', true ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
return;
}
$this->rate_limit->increment_attempts();
}
/**
* Check if rate limit is under threshold and passes.
*
* @since 1.8.2
*/
protected function is_rate_limit_ok() {
return $this->rate_limit->is_ok();
}
/**
* Check if any API errors occurs.
*
* @since 1.8.4
*
* @return bool
*/
protected function is_api_errors() {
$this->api->setup_stripe();
$error = $this->api->get_error();
if ( $error ) {
$this->process_api_error( 'general' );
return true;
}
return false;
}
/**
* Check if recurring settings is configured correctly.
*
* @since 1.8.4
*
* @param {array} $settings Settings data.
*
* @return bool
*/
protected function is_recurring_settings_ok( $settings ) {
$error = '';
// Check subscription settings are provided.
if ( empty( $settings['period'] ) || empty( $settings['email'] ) ) {
$error = esc_html__( 'Stripe subscription payment stopped, missing form settings.', 'wpforms-lite' );
}
// Check for required customer email.
if ( ! $error && empty( $this->fields[ $settings['email'] ]['value'] ) ) {
$error = esc_html__( 'Stripe subscription payment stopped, customer email not found.', 'wpforms-lite' );
}
// Before proceeding, check if any basic errors were detected.
if ( $error ) {
$this->log_error( $error );
$this->display_error( $error );
return false;
}
return true;
}
/**
* Process subscription API call.
*
* @since 1.8.4
*
* @param array $args Prepared subscription arguments.
*/
protected function process_subscription( $args ) {
$this->subscription_settings = $args['settings'];
if ( ! Helpers::is_license_ok() && Helpers::is_application_fee_supported() ) {
$args['application_fee_percent'] = 3;
}
$this->api->process_subscription( $args );
// Set payment processing flag.
$this->is_payment_processed = true;
// Update the credit card field value to contain basic details.
$this->update_credit_card_field_value();
$this->process_api_error( 'subscription' );
}
/**
* Get base subscription arguments.
*
* @since 1.8.4
*
* @return array
*/
protected function get_base_subscription_args() {
return [
'form_id' => $this->form_id,
'form_title' => sanitize_text_field( $this->form_data['settings']['form_title'] ),
'amount' => $this->amount * Helpers::get_decimals_amount(),
];
}
/**
* Map WPForms Address field to Stripe format.
*
* @since 1.8.8
*
* @param array $submitted_data Submitted address data.
* @param string $field_id Address field ID.
*
* @return array
*/
private function map_address_field( array $submitted_data, string $field_id ): array {
$line = sanitize_text_field( $submitted_data['address1'] );
$country = '';
if ( isset( $submitted_data['address2'] ) ) {
$line .= ' ' . sanitize_text_field( $submitted_data['address2'] );
}
if ( isset( $submitted_data['country'] ) ) {
$country = sanitize_text_field( $submitted_data['country'] );
} elseif ( $this->form_data['fields'][ $field_id ]['scheme'] !== 'international' ) {
$country = 'US';
}
return [
'line1' => $line,
'state' => isset( $submitted_data['state'] ) ? sanitize_text_field( $submitted_data['state'] ) : '',
'city' => sanitize_text_field( $submitted_data['city'] ),
'postal_code' => sanitize_text_field( $submitted_data['postal'] ),
'country' => $country,
];
}
/**
* Check the submitted payment data whether it was corrupted.
* If so, refund a payment / cancel subscription.
*
* @since 1.8.8.2
*
* @param array $entry Submitted entry data.
*
* @return bool
*/
private function is_submitted_payment_data_corrupted( array $entry ): bool {
// Bail early if there are no payment intents.
if ( empty( $entry['payment_intent_id'] ) ) {
return false;
}
// Get stored corrupted payment intents if exist.
$corrupted_intents = (array) Transient::get( 'corrupted-stripe-intents' );
// We must prevent a processing if payment intent was identified as corrupted.
// Also if the transaction ID exists in DB (transaction ID is unique value).
if ( in_array( $entry['payment_intent_id'], $corrupted_intents, true ) || wpforms()->get( 'payment' )->get_by( 'transaction_id', $entry['payment_intent_id'] ) ) {
wpforms()->get( 'process' )->errors[ $this->form_id ]['footer'] = esc_html__( 'Secondary form submission was declined.', 'wpforms-lite' );
return true;
}
$intent = $this->api->retrieve_payment_intent(
$entry['payment_intent_id'],
[
'expand' => [ 'invoice.subscription' ],
]
);
// Round to the nearest whole number because $this->amount can contain a number close to,
// but slightly under it, due to how it is stored in the memory.
$submitted_amount = round( $this->amount * Helpers::get_decimals_amount() );
// Prevent form submission if a mismatch of the payment amount is detected.
if ( ! empty( $intent ) && (int) $submitted_amount !== (int) $intent->amount ) {
wpforms()->get( 'process' )->errors[ $this->form_id ]['footer'] = esc_html__( 'Irregular activity detected. Your submission has been declined and payment refunded.', 'wpforms-lite' );
$args = [
'reason' => 'fraudulent',
];
// We can't cancel a payment because it's already paid.
// So we can perform a refund only.
$this->api->refund_payment( $entry['payment_intent_id'], $args );
// Cancel subscription if exists.
if ( ! empty( $intent->invoice->subscription ) ) {
$this->api->cancel_subscription( $intent->invoice->subscription->id );
}
// This payment indent is identified as corrupted.
// Store it in order to prevent re-using it (form re-submitting).
if ( ! in_array( $entry['payment_intent_id'], $corrupted_intents, true ) ) {
$corrupted_intents[] = $entry['payment_intent_id'];
Transient::set( 'corrupted-stripe-intents', $corrupted_intents, WEEK_IN_SECONDS );
}
return true;
}
return false;
}
}
Fatal error: Uncaught Error: Class 'WPForms\Integrations\Stripe\Process' not found in /home/candoodesigncom/public_html/wp-content/plugins/wpforms-lite/src/Integrations/Stripe/Stripe.php:69
Stack trace:
#0 /home/candoodesigncom/public_html/wp-content/plugins/wpforms-lite/src/Integrations/Loader.php(78): WPForms\Integrations\Stripe\Stripe->load()
#1 /home/candoodesigncom/public_html/wp-content/plugins/wpforms-lite/src/Integrations/Loader.php(63): WPForms\Integrations\Loader->load_integration(Object(WPForms\Integrations\Stripe\Stripe))
#2 /home/candoodesigncom/public_html/wp-content/plugins/wpforms-lite/src/Integrations/Loader.php(22): WPForms\Integrations\Loader->__construct()
#3 /home/candoodesigncom/public_html/wp-includes/class-wp-hook.php(324): WPForms\Integrations\Loader::get_instance('')
#4 /home/candoodesigncom/public_html/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(Object(WPForms\Providers\Providers), Array)
#5 /home/candoodesigncom/public_html/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
# in /home/candoodesigncom/public_html/wp-content/plugins/wpforms-lite/src/Integrations/Stripe/Stripe.php on line 69