elds'][ $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()->obj( 'payment' )->get_by( 'transaction_id', $entry['payment_intent_id'] ) ) { wpforms()->obj( '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 * wpforms_get_currency_multiplier() ); // Prevent form submission if a mismatch of the payment amount is detected. if ( ! empty( $intent ) && (int) $submitted_amount !== (int) $intent->amount ) { wpforms()->obj( '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; } /** * Maybe create a subscription schedule if the cycles was set. * * @since 1.9.8 * * @param object $subscription Stripe subscription object. */ private function maybe_set_subscription_schedule( object $subscription ): void { if ( empty( $subscription->metadata['cycles'] ) || $subscription->metadata['cycles'] === 'unlimited' || (int) $subscription->metadata['cycles'] < 1 || empty( $subscription->items->data ) ) { return; } try { $schedule = SubscriptionSchedule::create( [ 'from_subscription' => $subscription->id, ], Helpers::get_auth_opts() ); $subscription_item = $subscription->items->data[0]; $schedule::update( $schedule->id, [ 'end_behavior' => 'cancel', 'phases' => [ [ 'start_date' => $subscription_item->current_period_start, 'items' => [ [ 'plan' => $subscription_item->plan->id, ], ], 'iterations' => $subscription->metadata['cycles'], ], ], ], Helpers::get_auth_opts() ); } catch ( \Exception $e ) { wpforms_log( 'Stripe: Unable to create Subscription Schedule.', $e->getMessage(), [ 'type' => [ 'payment', 'error' ], ] ); } } }