: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace WPForms\Integrations\Stripe;
use Stripe\Exception\ApiErrorException;
use WPForms\Helpers\Transient;
* Stripe payment processing.
* Form Stripe payment settings.
* Sanitized submitted field values and data.
* Form data and settings.
* Whether the payment has been processed.
private $is_payment_processed = false;
* Save matched subscription settings.
private $subscription_settings = [];
* @param Api\ApiInterface $api Api interface.
public function init( $api ) {
private function hooks() {
add_action( 'wpforms_process', [ $this, 'process_entry' ], 10, 3 );
add_action( 'wpforms_process_payment_saved', [ $this, 'process_payment_saved' ], 10, 3 );
add_action( 'wpformsstripe_api_common_set_error_from_exception', [ $this, 'process_card_error' ] );
add_filter( 'wpforms_forms_submission_prepare_payment_data', [ $this, 'prepare_payment_data' ] );
add_filter( 'wpforms_forms_submission_prepare_payment_meta', [ $this, 'prepare_payment_meta' ], 10, 3 );
add_filter( 'wpforms_entry_email_process', [ $this, 'process_email' ], 70, 4 );
add_action( 'wpforms_process_complete', [ $this, 'process_entry_data' ], 10, 4 );
add_filter( 'wpforms_process_bypass_captcha', [ $this, 'bypass_captcha' ] );
* Check if a payment exists with an entry, if so validate and process.
* @param array $fields Final/sanitized submitted field data.
* @param array $entry Copy of original $_POST.
* @param array $form_data Form data and settings.
public function process_entry( $fields, $entry, $form_data ) {
// Check if payment method exists and is enabled.
if ( ! Helpers::has_stripe_enabled( [ $form_data ] ) ) {
$this->form_id = (int) $form_data['id'];
$this->form_data = $form_data;
$this->settings = $form_data['payments']['stripe'];
$this->amount = wpforms_get_total_payment( $this->fields );
$this->rate_limit = new RateLimit();
$this->rate_limit->init();
if ( $this->is_process_entry_error() ) {
if ( $this->is_submitted_payment_data_corrupted( $entry ) ) {
$this->api->set_payment_tokens( $entry );
$error = $this->get_entry_errors();
// Before proceeding, check if any basic errors were detected.
$this->log_error( $error );
$this->display_error( $error );
$this->process_payment();
* Bypass captcha if payment has been processed.
* @param bool $bypass_captcha Whether to bypass captcha.
public function bypass_captcha( $bypass_captcha ) {
return $this->is_payment_processed;
* Check on process entry errors.
protected function is_process_entry_error() {
// Check for processing errors.
if ( ! empty( wpforms()->get( 'process' )->errors[ $this->form_id ] ) || ! $this->is_card_field_visibility_ok() ) {
if ( ! $this->is_rate_limit_ok() ) {
wpforms()->get( 'process' )->errors[ $this->form_id ]['footer'] = esc_html__( 'Unable to process payment, please try again later.', 'wpforms-lite' );
* Add meta for a successful payment.
* @param array $payment_meta Payment meta.
* @param array $fields Final/sanitized submitted field data.
* @param array $form_data Form data and settings.
public function prepare_payment_meta( $payment_meta, $fields, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$payment = $this->api->get_payment();
if ( empty( $payment->id ) ) {
$charge_details = $this->api->get_charge_details( [ 'type', 'name', 'last4', 'brand', 'exp_month', 'exp_year' ] );
$payment_meta['method_type'] = $this->get_payment_type( $charge_details );
$payment_meta['customer_name'] = $this->get_customer_name();
$payment_meta['customer_email'] = $this->get_customer_email();
$subscription = $this->api->get_subscription();
if ( ! empty( $subscription->id ) ) {
$payment_meta['subscription_period'] = sanitize_text_field( $this->subscription_settings['period'] );
if ( ! empty( $charge_details['brand'] ) ) {
$payment_meta['credit_card_method'] = $charge_details['brand'];
if ( ! empty( $charge_details['name'] ) ) {
$payment_meta['credit_card_name'] = $charge_details['name'];
if ( ! empty( $charge_details['last4'] ) ) {
$payment_meta['credit_card_last4'] = $charge_details['last4'];
if ( ! empty( $charge_details['exp_month'] ) && ! empty( $charge_details['exp_year'] ) ) {
$payment_meta['credit_card_expires'] = sprintf( '%s/%s', $charge_details['exp_month'], $charge_details['exp_year'] );
'value' => $payment->object === 'payment_intent' ? sprintf( 'Stripe payment intent created. (Payment Intent ID: %s)', $payment->id ) : 'Stripe payment was created.',
'date' => gmdate( 'Y-m-d H:i:s' ),
$payment_meta['log'] = wp_json_encode( $log );
* Get payment method type.
* @param array $charge_details Get details from a saved Charge object.
private function get_payment_type( $charge_details ) {
if ( empty( $charge_details['last4'] ) ) {
if ( ! empty( $charge_details['type'] ) ) {
return sanitize_text_field( $charge_details['type'] );
* Add payment info for successful payment.
* @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( $payment_id, $fields, $form_data ) {
$payment = $this->api->get_payment();
if ( empty( $payment->id ) ) {
$payment_url = add_query_arg(
'page' => 'wpforms-payments',
'payment_id' => $payment_id,
// Update the Stripe charge metadata to include the Payment ID.
$payment->metadata['payment_id'] = $payment_id;
$payment->metadata['payment_url'] = esc_url_raw( $payment_url );
* Allow to add additional payment metadata to the Stripe payment.
* @param array $additional_meta Additional metadata.
* @param int $payment_id Payment ID.
* @param array $fields Final/sanitized submitted field data.
* @param array $form_data Form data and settings.
$additional_meta = (array) apply_filters( 'wpforms_integrations_stripe_process_additional_metadata', [], $payment_id, $fields, $form_data );
static function( $meta, $key ) use ( &$payment ) {
$payment->metadata[ $key ] = $meta;
$payment->update( $payment->id, $payment->serializeParameters(), Helpers::get_auth_opts() );
$subscription = $this->api->get_subscription();
// Update the Stripe subscription metadata to include the Payment ID.
if ( ! empty( $subscription->id ) ) {
$subscription->metadata['payment_id'] = $payment_id;
$subscription->metadata['payment_url'] = esc_url_raw( $payment_url );
$subscription->update( $subscription->id, $subscription->serializeParameters(), Helpers::get_auth_opts() );
wpforms()->get( 'payment_meta' )->add_log(
'Stripe charge processed. (Charge ID: %1$s)',
isset( $payment->latest_charge ) ? $payment->latest_charge : $payment->id
* Fire when processing is complete.
* @param array $fields Final/sanitized submitted field data.
* @param array $form_data Form data and settings.
* @param int $payment_id Payment ID.
* @param mixed $payment Stripe payment object.
* @param mixed $subscription Stripe subscription object.
* @param mixed $customer Stripe customer object.
do_action( 'wpforms_stripe_process_complete', $fields, $form_data, $payment_id, $payment, $subscription, $this->api->get_customer() ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Add details to payment data.
* @param array $payment_data Payment data args.
public function prepare_payment_data( $payment_data ) {
$payment = $this->api->get_payment();
if ( empty( $payment->id ) ) {
$customer = $this->api->get_customer();
$subscription = $this->api->get_subscription();
$payment_data['status'] = 'processed';
$payment_data['gateway'] = 'stripe';
$payment_data['mode'] = Helpers::get_stripe_mode();
$payment_data['transaction_id'] = sanitize_text_field( $payment->id );
$payment_data['customer_id'] = ! empty( $customer->id ) ? sanitize_text_field( $customer->id ) : '';
$payment_data['title'] = $this->get_payment_title();
if ( ! empty( $subscription->id ) ) {
$payment_data['subscription_id'] = sanitize_text_field( $subscription->id );
$payment_data['subscription_status'] = 'not-synced';
* @return string Payment title.
private function get_payment_title() {
$customer_name = $this->get_customer_name();
$customer_email = $this->get_customer_email();
* @return string Customer name.
private function get_customer_name() {
$customer_name = $this->api->get_customer( 'name' );
$charge_details = $this->api->get_charge_details( [ 'name' ] );
if ( ! empty( $charge_details['name'] ) ) {
return $charge_details['name'];
* @return string Customer email.
private function get_customer_email() {
$customer_email = $this->api->get_customer( 'email' );
$charge_details = $this->api->get_charge_details( [ 'email' ] );
if ( ! empty( $charge_details['email'] ) ) {
return $charge_details['email'];
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( isset( $_POST['wpforms']['payment_link_email'] ) ) {
return sanitize_email( wp_unslash( $_POST['wpforms']['payment_link_email'] ) );
// phpcs:enable WordPress.Security.NonceVerification.Missing
* Logic that helps decide if we should send completed payments notifications.
* @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.