namespace WPForms\Integrations\Stripe\Api;
use WPForms\Vendor\Stripe\Mandate;
use WPForms\Vendor\Stripe\SetupIntent;
use WPForms\Vendor\Stripe\PaymentIntent;
use WPForms\Vendor\Stripe\Stripe;
use WPForms\Vendor\Stripe\Subscription;
use WPForms\Vendor\Stripe\Refund;
use WPForms\Vendor\Stripe\Exception\ApiErrorException;
use WPForms\Integrations\Stripe\Fields\StripeCreditCard;
use WPForms\Integrations\Stripe\Fields\PaymentElementCreditCard;
use WPForms\Integrations\Stripe\Helpers;
use WPForms\Helpers\Crypto;
use WPForms\Vendor\Stripe\Charge;
use WPForms\Vendor\Stripe\CountrySpec;
* Stripe PaymentIntents API.
class PaymentIntents extends Common implements ApiInterface {
* Stripe PaymentMethod id received from Elements.
protected $payment_method_id;
* Stripe PaymentIntent id received from Elements.
protected $payment_intent_id;
* Stripe PaymentIntent object.
$this->load_card_field();
private function hooks() {
add_filter( 'wpforms_process_bypass_captcha', [ $this, 'bypass_captcha_on_3dsecure_submit' ], 10, 3 );
* Load Credit Card Field Class.
private function load_card_field() {
if ( Helpers::is_payment_element_enabled() ) {
new PaymentElementCreditCard();
public function set_config() {
'element_locale' => $this->filter_config_element_locale(),
'remote_js_url' => 'https://js.stripe.com/v3/',
'field_slug' => 'stripe-credit-card',
'localize_script' => $localize_script,
if ( Helpers::is_payment_element_enabled() ) {
$this->set_payment_element_config();
$this->set_card_element_config();
* Set API configuration for Payment Element.
private function set_payment_element_config() {
$min = wpforms_get_min_suffix();
* This filter allows to overwrite a Payment element appearance object.
* @link https://stripe.com/docs/elements/appearance-api
* @param array $appearance Appearance object.
$element_style = (array) apply_filters( 'wpforms_integrations_stripe_api_payment_intents_set_element_appearance', [] );
$this->config['localize_script']['element_appearance'] = $element_style;
$this->config['local_js_url'] = WPFORMS_PLUGIN_URL . "assets/js/integrations/stripe/wpforms-stripe-payment-element{$min}.js";
$this->config['local_css_url'] = WPFORMS_PLUGIN_URL . "assets/css/integrations/stripe/wpforms-stripe{$min}.css";
* Set API configuration for Card Element.
private function set_card_element_config() {
* This filter allows to overwrite a Style object, which consists of CSS properties nested under objects.
* @link https://stripe.com/docs/js/appendix/style
* @param array $styles Style object.
$element_style = (array) apply_filters( 'wpforms_stripe_api_payment_intents_set_config_element_style', [] ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$this->config['localize_script']['element_style'] = $element_style;
$this->config['localize_script']['element_classes'] = [
'base' => 'wpforms-stripe-element',
'complete' => 'wpforms-stripe-element-complete',
'empty' => 'wpforms-stripe-element-empty',
'focus' => 'wpforms-stripe-element-focus',
'invalid' => 'wpforms-stripe-element-invalid',
'webkitAutofill' => 'wpforms-stripe-element-webkit-autofill',
$min = wpforms_get_min_suffix();
$this->config['local_js_url'] = WPFORMS_PLUGIN_URL . "assets/js/integrations/stripe/wpforms-stripe-elements{$min}.js";
public function filter_config_element_locale() {
* WPForms Stripe Api payment intent element locale.
* @param string $locale Element locale.
$locale = apply_filters( 'wpforms_stripe_api_payment_intents_filter_config_element_locale', '' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
// Stripe Elements makes its own locale validation, but we add a general sanity check.
return strlen( $locale ) === 2 ? esc_html( $locale ) : 'auto';
* Initial Stripe app configuration.
public function setup_stripe() {
Stripe::setApiVersion( '2019-05-16' );
* Set payment tokens from a submitted form data.
* @param array $entry Copy of original $_POST.
public function set_payment_tokens( $entry ) {
if ( ! empty( $entry['payment_method_id'] ) && empty( $entry['payment_intent_id'] ) ) {
$this->payment_method_id = $entry['payment_method_id'];
if ( ! empty( $entry['payment_intent_id'] ) ) {
$this->payment_intent_id = $entry['payment_intent_id'];
if ( empty( $this->payment_method_id ) && empty( $this->payment_intent_id ) ) {
$this->error = esc_html__( 'Stripe payment stopped, missing both PaymentMethod and PaymentIntent ids.', 'wpforms-lite' );
* Retrieve PaymentIntent object from Stripe.
* @since 1.8.7 Changed method visibility.
* @param string $id PaymentIntent id.
* @param array $args Additional arguments (e.g. 'expand').
* @throws ApiErrorException If the request fails.
* @return PaymentIntent|null
public function retrieve_payment_intent( $id, $args = [] ) {
$defaults = [ 'id' => $id ];
if ( isset( $args['mode'] ) ) {
$auth_opts = [ 'api_key' => Helpers::get_stripe_key( 'secret', $args['mode'] ) ];
$args = wp_parse_args( $args, $defaults );
return PaymentIntent::retrieve( $args, $auth_opts ?? Helpers::get_auth_opts() );
} catch ( Exception $e ) {
$this->handle_exception( $e );
* Process single payment.
* @param array $args Single payment arguments.
* @throws ApiErrorException If the request fails.
public function process_single( $args ) {
if ( $this->payment_method_id ) {
$this->charge_single( $args );
} elseif ( $this->payment_intent_id ) {
$this->finalize_single();
* @since 1.8.8.2 $args param was added.
* @param string $payment_intent_id PaymentIntent id.
* @param array $args Additional arguments (e.g. 'mode', 'metadata', 'reason' ).
public function refund_payment( string $payment_intent_id, array $args = [] ): bool {
$intent = $this->retrieve_payment_intent( $payment_intent_id );
'payment_intent' => $payment_intent_id,
if ( isset( $args['mode'] ) ) {
$auth_opts = [ 'api_key' => Helpers::get_stripe_key( 'secret', $args['mode'] ) ];
$args = wp_parse_args( $args, $defaults );
$refund = Refund::create( $args, $auth_opts ?? Helpers::get_auth_opts() );
} catch ( Exception $e ) {
$this->handle_exception( $e );
* @param string $charge_id Charge id.
public function get_charge( $charge_id ) {
$charge = Charge::retrieve(
} catch ( Exception $e ) {
$this->handle_exception( $e );
* @param string $subscription_id Subscription id.
public function cancel_subscription( $subscription_id ) {
$subscription = Subscription::retrieve(
'metadata' => array_merge(
$subscription->metadata->values(),
'canceled_by' => 'wpforms_dashboard',
} catch ( Exception $e ) {
$this->handle_exception( $e );
* Request a single payment charge to be made by Stripe.
* @param array $args Single payment arguments.
protected function charge_single( $args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
if ( empty( $this->payment_method_id ) ) {
$this->error = esc_html__( 'Stripe payment stopped, missing PaymentMethod id.', 'wpforms-lite' );
'payment_method' => $this->payment_method_id,
'automatic_payment_methods' => [
'allow_redirects' => 'never',
$args = wp_parse_args( $args, $defaults );
if ( isset( $args['customer_email'] ) || isset( $args['customer_name'] ) || isset( $args['customer_phone'] ) ) {
$this->set_customer( $args['customer_email'] ?? '', $args['customer_name'] ?? '', $args['customer_address'] ?? [], $args['customer_phone'] ?? '', $args['customer_metadata'] ?? [] );
$args['customer'] = $this->get_customer( 'id' );
unset( $args['customer_email'], $args['customer_name'], $args['customer_address'], $args['customer_phone'], $args['customer_metadata'] );
$this->intent = PaymentIntent::create( $args, Helpers::get_auth_opts() );
if ( ! in_array( $this->intent->status, [ 'succeeded', 'requires_action', 'requires_confirmation' ], true ) ) {
$this->error = esc_html__( 'Stripe payment stopped. Invalid PaymentIntent status.', 'wpforms-lite' );
if ( $this->intent->status === 'succeeded' ) {
$this->set_bypass_captcha_3dsecure_token( $args );
if ( $this->intent->status === 'requires_confirmation' ) {
$this->request_confirm_payment_ajax( $this->intent );
$this->request_3dsecure_ajax( $this->intent );
} catch ( Exception $e ) {
$this->handle_exception( $e );
* Finalize single payment after 3D Secure authorization is finished successfully.
* @throws ApiErrorException If the request fails.
protected function finalize_single() {