: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
public function process_email( $process, $fields, $form_data, $notification_id ) {
if ( ! Helpers::has_stripe_enabled( [ $form_data ] ) ) {
if ( empty( $form_data['settings']['notifications'][ $notification_id ]['stripe'] ) ) {
if ( empty( $this->api->get_payment() ) ) {
if ( ! $this->is_payment_processed ) {
return empty( $this->api->get_error() );
* Update entry details for a successful payment.
* @param array $fields Final/sanitized submitted field data.
* @param array $entry Copy of original $_POST.
* @param array $form_data Form data and settings.
* @param string $entry_id Entry ID.
public function process_entry_data( $fields, $entry, $form_data, $entry_id ) {
$payment = $this->api->get_payment();
if ( empty( $payment->id ) || empty( $entry_id ) ) {
wpforms()->get( 'entry' )->update(
* Get general errors before payment processing.
protected function get_entry_errors() {
// Check for Stripe payment tokens (card token or payment id).
$error = $this->api->get_error();
// Check for Stripe keys.
if ( ! Helpers::has_stripe_keys() ) {
return esc_html__( 'Stripe payment stopped, missing keys.', 'wpforms-lite' );
// Check that, despite how the form is configured, the form and
// entry actually contain payment fields, otherwise no need to proceed.
if ( ! wpforms_has_payment( 'form', $this->form_data ) || ! wpforms_has_payment( 'entry', $this->fields ) ) {
return esc_html__( 'Stripe payment stopped, missing payment fields.', 'wpforms-lite' );
// Check total charge amount.
if ( empty( $this->amount ) || wpforms_sanitize_amount( 0 ) === $this->amount ) {
return esc_html__( 'Stripe payment stopped, invalid/empty amount.', 'wpforms-lite' );
if ( 50 > ( $this->amount * 100 ) ) {
return esc_html__( 'Stripe payment stopped, amount less than minimum charge required.', 'wpforms-lite' );
public function process_payment() {
if ( $this->is_api_errors() ) {
// Proceed to executing the purchase.
if ( ! empty( $this->settings['enable_recurring'] ) || ! empty( $this->settings['recurring']['enable'] ) ) {
$this->process_payment_subscription();
$this->process_payment_single();
* Process a subscription payment for forms with old payments interface.
protected function process_legacy_payment_subscription() {
if ( ! $this->is_recurring_settings_ok( $this->settings['recurring'] ) ) {
$args = $this->get_base_subscription_args();
$args['settings'] = $this->settings['recurring'];
$args['email'] = sanitize_email( $this->fields[ $args['settings']['email'] ]['value'] );
$args['customer_name'] = ! empty( $args['settings']['customer_name'] ) ? sanitize_text_field( $this->fields[ $args['settings']['customer_name'] ]['value'] ) : '';
if ( isset( $args['settings']['customer_address'] ) && $args['settings']['customer_address'] !== '' ) {
$args['customer_address'] = $this->map_address_field( $this->fields[ $args['settings']['customer_address'] ], $args['settings']['customer_address'] );
$this->process_subscription( $args );
// Set payment processing flag.
$this->is_payment_processed = true;
* Process a single payment.
public function process_payment_single() {
$amount_decimals = Helpers::get_decimals_amount();
// Define the basic payment details.
'amount' => $this->amount * $amount_decimals,
'currency' => strtolower( wpforms_get_currency() ),
'form_name' => sanitize_text_field( $this->form_data['settings']['form_title'] ),
'form_id' => $this->form_id,
if ( ! Helpers::is_license_ok() && Helpers::is_application_fee_supported() ) {
$args['application_fee_amount'] = (int) ( round( $this->amount * 0.03, 2 ) * $amount_decimals );
if ( ! empty( $this->settings['payment_description'] ) ) {
$args['description'] = html_entity_decode( $this->settings['payment_description'], ENT_COMPAT, 'UTF-8' );
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'] );
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'] );
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'] );
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'] );
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.
public function process_payment_subscription() {
if ( Helpers::is_legacy_payment_settings( $this->form_data ) ) {
$this->process_legacy_payment_subscription();
$args = $this->get_base_subscription_args();
foreach ( $this->settings['recurring'] as $recurring ) {
if ( ! $this->is_subscription_plan_valid( $recurring ) ) {
$args['email'] = sanitize_email( $this->fields[ $recurring['email'] ]['value'] );
$args['settings'] = $recurring;
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'] );
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 );
if ( ! empty( $this->settings['enable_one_time'] ) ) {
$this->process_payment_single();
esc_html__( 'Stripe Subscription payment stopped validation error.', 'wpforms-lite' ),
* Validate plan before process.
* @param array $plan Plan settings.
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.
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'] ) {
$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.
* @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.
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'] ) ) {
foreach ( $this->fields as $field ) {
if ( empty( $field['type'] ) || $this->api->get_config( 'field_slug' ) !== $field['type'] ) {
// if the field is NOT in array of conditional fields, it's visible.
if ( ! in_array( $field['id'], $this->form_data['conditional_fields'], true ) ) {
// if the field IS in array of conditional fields and marked as visible, it's visible.
if ( ! empty( $field['visible'] ) ) {
* @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();
'type' => [ 'payment', $level ],
'form_id' => $this->form_id,
* Collect errors from API and turn it into form errors.
* @param string $type Payment time (e.g. 'single' or 'subscription').
protected function process_api_error( $type ) {
$message = $this->api->get_error();
if ( empty( $message ) ) {
/* translators: %s - error message. */
esc_html__( 'Payment Error: %s', 'wpforms-lite' ),
$this->display_error( $message );
if ( $type === 'subscription' ) {
$title = esc_html__( 'Stripe subscription payment stopped by error', 'wpforms-lite' );
$title = esc_html__( 'Stripe payment stopped by error', 'wpforms-lite' );
$this->log_error( $title, $this->api->get_exception() );
* @param string $error Error to display.
private function display_error( $error ) {
$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
foreach ( $this->form_data['fields'] as $field ) {
if ( empty( $field['type'] ) || $field_slug !== $field['type'] ) {
if ( ! empty( $field['required'] ) ) {
wpforms()->get( 'process' )->errors[ $this->form_id ]['footer'] = $error;
* Process card error from Stripe API exception and adds rate limit tracking.
* @param ApiErrorException $e Stripe API exception to process.
public function process_card_error( $e ) {
if ( Helpers::get_stripe_mode() === 'test' ) {
if ( ! is_a( $e, '\WPForms\Vendor\Stripe\Exception\CardException' ) ) {
* Allow to filter Stripe process card error.
* @param bool $flag True if any error.
if ( ! apply_filters( 'wpforms_stripe_process_process_card_error', true ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$this->rate_limit->increment_attempts();
* Check if rate limit is under threshold and passes.
protected function is_rate_limit_ok() {
return $this->rate_limit->is_ok();
* Check if any API errors occurs.
protected function is_api_errors() {
$this->api->setup_stripe();
$error = $this->api->get_error();
$this->process_api_error( 'general' );
* Check if recurring settings is configured correctly.
* @param {array} $settings Settings data.