From 6e17e5c24d211dcf844b895d212c9d1016ee22e7 Mon Sep 17 00:00:00 2001
From: AndPicc
Date: Wed, 15 Jun 2022 11:13:17 +0100
Subject: [PATCH] version 4.2.1
---
change_log.txt | 20 +-
class-gf-stripe.php | 580 ++++++++++++++++---
css/plugin_settings.css | 2 +-
includes/class-gf-stripe-api.php | 285 ++++++++-
includes/class-gf-stripe-billing-portal.php | 535 +++++++++++++++++
includes/views/subscription-information.php | 151 +++++
js/admin.js | 127 +++-
js/admin.min.js | 2 +-
js/frontend.js | 2 +-
languages/gravityformsstripe-ar.mo | Bin 469 -> 469 bytes
languages/gravityformsstripe-ca.mo | Bin 938 -> 938 bytes
languages/gravityformsstripe-da_DK.mo | Bin 6548 -> 6548 bytes
languages/gravityformsstripe-de_DE.mo | Bin 23940 -> 23940 bytes
languages/gravityformsstripe-de_DE_formal.mo | Bin 823 -> 823 bytes
languages/gravityformsstripe-en_AU.mo | Bin 8886 -> 8886 bytes
languages/gravityformsstripe-en_GB.mo | Bin 5589 -> 5589 bytes
languages/gravityformsstripe-es_ES.mo | Bin 24327 -> 24327 bytes
languages/gravityformsstripe-fi.mo | Bin 5598 -> 5598 bytes
languages/gravityformsstripe-fr_CA.mo | Bin 6184 -> 6184 bytes
languages/gravityformsstripe-fr_FR.mo | Bin 24709 -> 24709 bytes
languages/gravityformsstripe-he_IL.mo | Bin 25280 -> 25280 bytes
languages/gravityformsstripe-hi_IN.mo | Bin 37469 -> 37469 bytes
languages/gravityformsstripe-hu_HU.mo | Bin 413 -> 413 bytes
languages/gravityformsstripe-it_IT.mo | Bin 24349 -> 24349 bytes
languages/gravityformsstripe-ja.mo | Bin 26372 -> 26372 bytes
languages/gravityformsstripe-nb_NO.mo | Bin 849 -> 849 bytes
languages/gravityformsstripe-nl_BE.mo | Bin 409 -> 409 bytes
languages/gravityformsstripe-nl_NL.mo | Bin 23622 -> 23622 bytes
languages/gravityformsstripe-pt_BR.mo | Bin 24064 -> 24064 bytes
languages/gravityformsstripe-pt_PT.mo | Bin 24038 -> 24038 bytes
languages/gravityformsstripe-ru_RU.mo | Bin 30606 -> 30606 bytes
languages/gravityformsstripe-sv_SE.mo | Bin 24336 -> 24336 bytes
languages/gravityformsstripe-tr_TR.mo | Bin 23394 -> 23394 bytes
languages/gravityformsstripe-zh_CN.mo | Bin 1572 -> 1572 bytes
languages/gravityformsstripe.pot | 498 ++++++++++------
stripe.php | 6 +-
36 files changed, 1931 insertions(+), 277 deletions(-)
create mode 100644 includes/class-gf-stripe-billing-portal.php
create mode 100644 includes/views/subscription-information.php
diff --git a/change_log.txt b/change_log.txt
index 94268b3..2077ace 100644
--- a/change_log.txt
+++ b/change_log.txt
@@ -1,7 +1,19 @@
-### 4.1.1
-- Updated the stripe API SDK methods.
+### 4.2.1
+- Fixed a fatal error which can occur with the Stripe API Client if the add-on isn't connected to a Stripe account.
+
+
+### 4.2 | 2022-02-03
+- Added security enhancements.
+- Added the ability to refund payments from the entry detail page.
+- Added the ability to capture authorized payments from the entry detail page.
+- Added the stripe_customer_portal_link shortcode action to support [self-serve billing portal](https://docs.gravityforms.com/stripe-customer-portal-shortcode-action/).
+- Updated the Stripe API SDK methods.
- Updated the styling for the disconnect alert messaging.
-- Fixed an issue where if future usage is set to 'on_session' and a user enters a 3DS card, they get an infinite loop and can't submit the form.
+- Fixed a javascript error when de-authorizing Stripe account.
+- Fixed an issue with unsaved warning being displayed on settings pages even when there are no changes made.
+- Fixed an issue with the Webhooks Add-On where the payment details in the entry are empty.
+- Fixed an issue where if future usage is set to 'on_session' and a user enters a 3DS card. This addresses an issue that results in an infinite loop on form submission.
+
### 4.1 | 2021-06-30
- Updated error messages when API isn't connected to be more informative.
@@ -11,6 +23,7 @@
- Fixed an issue where the credit card icon overlaps with the placeholder text in the credit card field.
- Fixed an issue where the form fails validation if the plan (price) or product, created by the add-on, is archived in the Stripe account.
+
### 4.0 | 2021-04-21
- Added a warning notice when deprecated credit card field is still being used.
- Added right-to-left language support to the Credit Card field in the form editor in Gravity Forms 2.5.
@@ -30,7 +43,6 @@
- Fixed an issue with the Stripe card field preview in the form editor.
-
### 3.8 | 2020-09-28
- Added support for Gravity Forms 2.5.
- Updated to support 50 metadata mappings.
diff --git a/class-gf-stripe.php b/class-gf-stripe.php
index 77137b4..6dc4e53 100644
--- a/class-gf-stripe.php
+++ b/class-gf-stripe.php
@@ -251,6 +251,16 @@ class GFStripe extends GFPaymentAddOn {
* @var string
*/
protected $_input_container_prefix = '';
+
+ /**
+ * Instance of the billing portal handler.
+ *
+ * @since 4.2
+ *
+ * @var GF_Stripe_Billing_Portal
+ */
+ protected $billing_portal_handler;
+
/**
* Get an instance of this class.
*
@@ -283,6 +293,9 @@ public function pre_init() {
// Run before calling parent method. We don't want to run anything else before displaying thank you page.
add_action( 'wp', array( $this, 'maybe_thankyou_page' ), 5 );
+ // When the manage subscription link is clicked, it should be handled here.
+ add_action( 'wp', array( $this->get_billing_portal_handler(), 'maybe_redirect_logged_in_user_to_self_serve_link' ), 10 );
+
parent::pre_init();
require_once 'includes/class-gf-field-stripe-creditcard.php';
@@ -304,7 +317,6 @@ public function pre_init() {
* @return array The scripts to be enqueued.
*/
public function scripts() {
-
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
$scripts = array(
@@ -348,11 +360,11 @@ public function scripts() {
'handle' => 'gforms_stripe_admin',
'src' => $this->get_base_url() . "/js/admin{$min}.js",
'version' => $this->_version,
- 'deps' => array( 'jquery', 'thickbox', 'stripe.js' ),
+ 'deps' => array( 'jquery', 'thickbox', 'stripe.js', 'wp-a11y' ),
'in_footer' => false,
'enqueue' => array(
array(
- 'admin_page' => array( 'plugin_settings', 'form_settings' ),
+ 'admin_page' => array( 'plugin_settings', 'form_settings', 'entry_view' ),
'tab' => array( $this->_slug, $this->get_short_title() ),
),
),
@@ -371,6 +383,13 @@ public function scripts() {
'apiMode' => $this->get_api_mode( $this->get_settings() ),
'input_container_prefix' => $this->_input_container_prefix,
'input_prefix' => $this->_input_prefix,
+ 'refund' => wp_strip_all_tags( __( 'Are you sure you want to refund this payment?', 'gravityformsstripe' ) ),
+ 'refund_nonce' => wp_create_nonce( 'gf_stripe_refund' ),
+ 'refund_processing' => wp_strip_all_tags( __( 'Processing refund', 'gravityformsstripe' ) ),
+ 'refund_complete' => wp_strip_all_tags( __( 'Transaction successfully refunded', 'gravityformsstripe' ) ),
+ 'capture_confirm' => wp_strip_all_tags( __( 'Are you sure you want to capture this payment?', 'gravityformstripe' ) ),
+ 'capture_processing' => wp_strip_all_tags( __( 'Processing capture', 'gravityformsstripe' ) ),
+ 'capture_complete' => wp_strip_all_tags( __( 'Transaction successfully captured', 'gravityformsstripe' ) ),
),
),
);
@@ -462,6 +481,8 @@ public function init_ajax() {
add_action( 'wp_ajax_gfstripe_update_payment_intent', array( $this, 'update_payment_intent' ) );
add_action( 'wp_ajax_nopriv_gfstripe_get_country_code', array( $this, 'get_country_code' ) );
add_action( 'wp_ajax_gfstripe_get_country_code', array( $this, 'get_country_code' ) );
+ add_action( 'wp_ajax_gfstripe_capture_action', array( $this, 'ajax_capture_payment' ) );
+ add_action( 'wp_ajax_gfstripe_refund', array( $this, 'ajax_refund' ) );
}
/**
@@ -475,6 +496,26 @@ public function init_admin() {
add_action( 'admin_notices', array( $this, 'maybe_display_update_authentication_message' ) );
add_action( 'admin_notices', array( $this, 'maybe_display_deprecated_cc_field_warning' ) );
add_action( 'admin_init', array( $this, 'maybe_update_auth_tokens' ) );
+ add_action( 'gform_payment_details', array( $this, 'maybe_display_capture_button' ), 10, 2 );
+ add_action( 'gform_payment_details', array( $this, 'maybe_display_refund_button' ), 10, 2 );
+ }
+
+ /**
+ * Gets billing portal handler instance if already initialized, otherwise, initialize it.
+ *
+ * @since 4.2
+ *
+ * @return GF_Stripe_Billing_Portal
+ */
+ public function get_billing_portal_handler() {
+
+ if ( $this->billing_portal_handler instanceof GF_Stripe_Billing_Portal === false ) {
+ require_once plugin_dir_path( __FILE__ ) . '/includes/class-gf-stripe-billing-portal.php';
+ $this->billing_portal_handler = new GF_Stripe_Billing_Portal( $this );
+ }
+
+ return $this->billing_portal_handler;
+
}
/**
@@ -563,11 +604,11 @@ public function plugin_settings_fields() {
'label' => esc_html__( 'Stripe Credit Card Field (Elements, SCA-ready)', 'gravityformsstripe' ),
'value' => 'stripe_elements',
'tooltip' => '' . esc_html__( 'Stripe Credit Card Field (Elements)', 'gravityformsstripe' ) . ' ' .
- '' . esc_html__( 'Select this option to use a Credit Card field hosted by Stripe. This option offers the benefit of a streamlined user interface and the security of having the credit card field hosted on Stripe\'s servers. Selecting this option or "Stripe Payment Form" greatly simplifies the PCI compliance application process with Stripe.', 'gravityformsstripe' ) .
- '
' .
- /* translators: 1. Open link tag 2. Close link tag */
- sprintf( esc_html__( 'Stripe Elements is ready for %1$sStrong Customer Authentication%2$s for European customers.', 'gravityformsstripe' ), '', ' ' ) .
- '
',
+ '' . esc_html__( 'Select this option to use a Credit Card field hosted by Stripe. This option offers the benefit of a streamlined user interface and the security of having the credit card field hosted on Stripe\'s servers. Selecting this option or "Stripe Payment Form" greatly simplifies the PCI compliance application process with Stripe.', 'gravityformsstripe' ) .
+ '
' .
+ /* translators: 1. Open link tag 2. Close link tag */
+ sprintf( esc_html__( 'Stripe Elements is ready for %1$sStrong Customer Authentication%2$s for European customers.', 'gravityformsstripe' ), '', ' ' ) .
+ '
',
),
array(
'label' => esc_html__( 'Stripe Payment Form (Stripe Checkout, SCA-ready)', 'gravityformsstripe' ),
@@ -855,7 +896,7 @@ public function get_webhooks_section_description() {
ob_start();
?>
+ onclick="tb_show('Webhook Instructions', '#TB_inline?width=500&inlineId=stripe-webhooks-instructions', '');" onkeypress="tb_show('Webhook Instructions', '#TB_inline?width=500&inlineId=stripe-webhooks-instructions', '');">
@@ -1243,6 +1284,58 @@ public function maybe_update_auth_tokens() {
}
}
+
+ /**
+ * Target of the gform_payment_details hook. If appropriate, will add the Capture button in the payment details box
+ *
+ * @since 4.2
+ *
+ * @param int $form_id Current form ID.
+ * @param array $entry Current Entry Object.
+ */
+ public function maybe_display_capture_button( $form_id, $entry ) {
+ if ( ! $this->should_display_capture_button( $entry ) ) {
+ return;
+ }
+
+ $ajax_data = array(
+ 'nonce' => wp_create_nonce( 'gfstripe_capture_payment' ),
+ 'entry_id' => absint( $entry['id'] ),
+ 'transaction_id' => $entry['transaction_id'],
+ );
+
+ printf(
+ '
+ %2$s
+
+ %3$s
+
+
+
',
+
+ esc_attr( wp_json_encode( $ajax_data ) ),
+ esc_html__( 'Capture Payment', 'gravityformsstripe' ),
+ esc_html__( 'Capturing payment', 'gravityformsstripe' )
+ );
+ }
+
+ /**
+ * Checks if the capture payment button should be displayed or not.
+ *
+ * @since 4.2
+ *
+ * @param array $entry Current entry being processed.
+ *
+ * @return bool Returns true if capture button should be displayed. Returns false otherwise.
+ */
+ private function should_display_capture_button( $entry ) {
+ return (
+ $entry['transaction_type'] === '1'
+ && $this->is_payment_gateway( $entry['id'] )
+ && $entry['payment_status'] == 'Authorized'
+ );
+ }
+
/**
* Add auth_token data when updating plugin settings.
*
@@ -1303,7 +1396,7 @@ public function maybe_display_update_authentication_message( $return = false, $c
if ( $this->requires_reauthentication() ) {
$message = sprintf(
- /* Translators: 1: Open link tag 2: Close link tag */
+ /* Translators: 1: Open link tag 2: Close link tag */
esc_html__( 'You are currently logged in to Stripe using a deprecated authentication method. %1$sRe-authenticate your Stripe account%2$s.', 'gravityformsstripe' ),
'',
' '
@@ -1375,7 +1468,7 @@ public function maybe_display_saved_form_deprecated_cc_field_warning( $saved_for
*/
private function display_deprecated_cc_field_warning( $form ) {
$message = sprintf(
- /* translators: 1: Open strong tag, 2: Close strong tag, 3: Form title, 4: Open link tag, 5: Close link tag. */
+ /* translators: 1: Open strong tag, 2: Close strong tag, 3: Form title, 4: Open link tag, 5: Close link tag. */
esc_html__( '%1$sImportant%2$s: The form %3$s is using a deprecated payment collection method for Stripe that will stop working in a future version. Take action now to continue collecting payments. %4$sLearn more.%5$s', 'gravityformsstripe' ),
'',
' ',
@@ -1906,9 +1999,9 @@ public function settings_setup_fee( $field, $echo = true ) {
'name' => $field['name'] . '_enabled',
'value' => '1',
'onchange' => "if(jQuery(this).prop('checked')){
- jQuery('#{$field['name']}_product').show();
+ jQuery('#{$field['name']}_product').prop('disabled', false);
} else {
- jQuery('#{$field['name']}_product').hide();
+ jQuery('#{$field['name']}_product').prop('disabled', true);
}",
),
),
@@ -1925,12 +2018,15 @@ public function settings_setup_fee( $field, $echo = true ) {
// Prepare setup fee select field settings.
$product_field = array(
- 'name' => $field['name'] . '_product',
- 'type' => 'select',
- 'class' => $is_enabled ? '' : 'hidden',
- 'choices' => $this->get_payment_choices( $form ),
+ 'name' => $field['name'] . '_product',
+ 'type' => 'select',
+ 'choices' => $this->get_payment_choices( $form ),
);
+ if ( ! $is_enabled ) {
+ $product_field['disabled'] = 'disabled';
+ }
+
// Add select field markup to checkbox field markup.
$html .= ' ' . $this->settings_select( $product_field, false );
@@ -2235,6 +2331,7 @@ public function init() {
add_filter( 'gform_pre_submission', array( $this, 'populate_credit_card_last_four' ) );
add_filter( 'gform_field_css_class', array( $this, 'stripe_card_field_css_class' ), 10, 3 );
add_filter( 'gform_submission_values_pre_save', array( $this, 'stripe_card_submission_value_pre_save' ), 10, 3 );
+ add_filter( 'gform_shortcode_stripe_customer_portal_link', array( $this->get_billing_portal_handler(), 'stripe_customer_portal_link_shortcode' ), 10, 3 );
// Supports frontend feeds.
$this->_supports_frontend_feeds = true;
@@ -2386,7 +2483,6 @@ public function register_init_scripts( $form, $field_values, $is_ajax ) {
* @return bool If the script should be enqueued.
*/
public function frontend_script_callback( $form ) {
-
// Starts from 2.6, CC field isn't required when Stripe Checkout enabled.
return $form && $this->has_feed( $form['id'] ) && ( ( ! $this->is_stripe_checkout_enabled() && ( $this->has_stripe_card_field( $form ) || $this->has_credit_card_field( $form ) ) ) );
@@ -2560,7 +2656,7 @@ public function pre_validation( $result, $value, $form, $field ) {
// When a Stripe card is not on the last page, and the form is submitted by the SCA handler,
// the field failed because the Card Holder name is wiped out. In this case we assume it's valid.
$stripe_response = $this->get_stripe_js_response();
- if ( ! empty( $stripe_response ) && substr( $stripe_response->id, 0, 3 ) === 'pi_' && $stripe_response->scaSuccess ) {
+ if ( ! empty( $stripe_response ) && $this->is_payment_intent( $stripe_response->id ) && $stripe_response->scaSuccess ) {
$result['is_valid'] = true;
$result['message'] = '';
}
@@ -2856,9 +2952,26 @@ public function authorize_product( $feed, $submission_data, $form, $entry ) {
// check if stripe_response has a payment id.
// if yes, confirm the payment here.
$stripe_response = $this->get_stripe_js_response();
- if ( substr( $stripe_response->id, 0, 3 ) === 'pi_' ) {
+ if ( $this->is_payment_intent( $stripe_response->id ) ) {
$result = $this->api->get_payment_intent( $stripe_response->id );
if ( ! is_wp_error( $result ) ) {
+ $currency = rgar( $entry, 'currency' );
+ $expected_amount = $this->get_amount_export( $submission_data['payment_amount'], $currency );
+
+ if ( $result->amount !== $expected_amount ) {
+ $this->log_debug( __METHOD__ . sprintf( '(): Amount mismatch. PaymentIntent: %s. Expected: %s. Cancelling PaymentIntent.', GFCommon::to_money( $this->get_amount_import( $result->amount, $currency ), $currency ), GFCommon::to_money( $submission_data['payment_amount'], $currency ) ) );
+
+ $result = $this->api->cancel_payment_intent( $result->id, 'fraudulent' );
+ if ( is_wp_error( $result ) ) {
+ $this->log_error( __METHOD__ . '(): ' . $result->get_error_message() );
+ }
+
+ // Clear the response so a new PI will be created on the next submission attempt.
+ $_POST['stripe_response'] = '';
+
+ return $this->authorization_error( esc_html__( 'Your payment attempt has failed. Please enter your card details and try again.', 'gravityformsstripe' ) );
+ }
+
// Add customer, receipt_email and metadata.
// Change data before sending to Stripe.
$data = $this->get_product_payment_data( $data, $feed, $submission_data, $form, $entry );
@@ -3482,7 +3595,7 @@ public function capture( $auth, $feed, $submission_data, $form, $entry ) {
// check if stripe_response has a payment id.
// if yes, confirm the payment here.
$response = $this->get_stripe_js_response();
- if ( substr( $response->id, 0, 3 ) === 'pi_' ) {
+ if ( $this->is_payment_intent( $response->id ) ) {
$intent = $this->api->get_payment_intent( $response->id );
if ( is_wp_error( $intent ) ) {
@@ -3870,7 +3983,7 @@ public function subscribe( $feed, $submission_data, $form, $entry ) {
$this->log_error( __METHOD__ . '(): ' . $result->get_error_message() );
return $this->authorization_error( $result->get_error_message() );
- }
+ }
} else {
// Prepare customer metadata.
// Starts from 3.0, customers created by Stripe Checkout won't have the `balance` set.
@@ -4177,6 +4290,11 @@ public function checkout_fulfillment( $session, $entry, $feed, $form ) {
$transaction_id = rgar( $session, 'payment_intent' );
}
+ $result = GFAPI::get_entry( rgar( $entry, 'id' ) );
+ if ( ! is_wp_error( $result ) ) {
+ $entry = $result;
+ }
+
if ( method_exists( $this, 'trigger_payment_delayed_feeds' ) ) {
$this->trigger_payment_delayed_feeds( $transaction_id, $feed, $entry, $form );
}
@@ -4199,6 +4317,36 @@ public function checkout_fulfillment( $session, $entry, $feed, $form ) {
}
}
+ /**
+ * If this entry was created by a Stripe Checkout session, executes checkout_fulfillment() function
+ *
+ * @param array $entry Current entry being processed.
+ *
+ * @return bool|WP_Error Returns true if checkout_fulfillment() was called and false if entry was not created via Stripe Checkout. Returns a WP_Error if session can't be retrieved...
+ */
+ public function maybe_checkout_fulfillment( $entry ) {
+
+ $session_id = gform_get_meta( $entry['id'], 'stripe_session_id' );
+ if ( ! $session_id ) {
+ return false;
+ }
+
+ $session = \Stripe\Checkout\Session::retrieve( $session_id );
+ if ( is_wp_error( $session ) ) {
+ $this->log_error( __METHOD__ . '(): A Stripe API error occurs; ' . $session->get_error_message() );
+ return new WP_Error( 'stripe_session_failed', __METHOD__ . '(): A Stripe API error occurs; ' . $session->get_error_message() );
+ }
+
+ $form = GFAPI::get_form( $entry['form_id'] );
+ $feed = $this->get_payment_feed( $entry, $form );
+
+ // Run gform_stripe_fulfillment hook.
+ $this->checkout_fulfillment( $session, $entry, $feed, $form );
+
+ return true;
+
+ }
+
// # STRIPE HELPER FUNCTIONS ---------------------------------------------------------------------------------------
/**
@@ -4809,6 +4957,8 @@ public function include_stripe_api( $mode = null, $settings = null ) {
// Assign the Stripe API object to this instance.
$this->api = $stripe;
+ return $this->api;
+
}
/**
@@ -4957,64 +5107,67 @@ public function callback() {
$entry = GFAPI::get_entry( $entry_id );
- if ( ! $this->is_valid_entry_for_callback( $entry ) ) {
- return $this->get_wrong_feed_wp_error( $entry_id );
- }
+ $payment_status = rgar( $entry, 'payment_status' );
- $action['entry_id'] = $entry_id;
+ // Don't process the webhook if the payment has already been refunded in the dashboard.
+ if ( $payment_status !== 'Refunded' ) {
+ if ( ! $this->is_valid_entry_for_callback( $entry ) ) {
+ return $this->get_wrong_feed_wp_error( $entry_id );
+ }
- if ( $event->data->object->captured ) {
- $action['type'] = 'refund_payment';
- $action['amount'] = $this->get_amount_import( rgars( $event, 'data/object/amount_refunded' ), $entry['currency'] );
- } else {
- $action['type'] = 'void_authorization';
+ $action['entry_id'] = $entry_id;
+
+ if ( $event->data->object->captured ) {
+
+ $action['type'] = 'refund_payment';
+ $action['amount'] = $this->get_amount_import(
+ rgars( $event, 'data/object/amount_refunded' ),
+ $entry['currency']
+ );
+ } else {
+ $action['type'] = 'void_authorization';
+ }
}
break;
case 'charge.captured':
- if ( $this->is_stripe_checkout_enabled() ) {
- $action['transaction_id'] = rgars( $event, 'data/object/payment_intent' );
+ // Getting transaction ID. Use Payment Intent if one is set. Otherwhise use Charge ID.
+ $action['transaction_id'] = rgars( $event, 'data/object/payment_intent' ) ? rgars( $event, 'data/object/payment_intent' ) : rgars( $event, 'data/object/id' );
+ $entry_id = $this->get_entry_by_transaction_id( $action['transaction_id'] );
- $entry_id = $this->get_entry_by_transaction_id( $action['transaction_id'] );
-
- if ( $entry_id ) {
- $entry = GFAPI::get_entry( $entry_id );
-
- if ( ! $this->is_valid_entry_for_callback( $entry ) ) {
- return $this->get_wrong_feed_wp_error( $entry_id );
- }
+ // Abort if entry can't be found.
+ if ( ! $entry_id ) {
+ return $this->get_entry_not_found_wp_error( 'transaction', $action, $event );
+ }
+ $entry = GFAPI::get_entry( $entry_id );
- $payment_status = rgar( $entry, 'payment_status' );
+ $is_processing = get_option( "gform_stripe_capturing_{$action['transaction_id']}" );
- if ( $payment_status === 'Authorized' ) {
- $form = GFAPI::get_form( $entry['form_id'] );
- $feed = $this->get_payment_feed( $entry, $form );
+ // Abort if transaction is not 'Authorized' or capture process is currently taking place.
+ if ( rgar( $entry, 'payment_status' ) !== 'Authorized' || $is_processing ) {
+ $action['abort_callback'] = true;
+ break;
+ }
- // Get session.
- $session_id = gform_get_meta( $entry_id, 'stripe_session_id' );
- $session = $this->api->get_checkout_session( $session_id );
+ // Abort if entry doesn't match this callback.
+ if ( ! $this->is_valid_entry_for_callback( $entry ) ) {
+ return $this->get_wrong_feed_wp_error( $entry_id );
+ }
- if ( is_wp_error( $session ) ) {
- $this->log_error( __METHOD__ . '(): A Stripe API error occurs; ' . $session->get_error_message() );
- } else {
- // Mark authorized payment as Paid.
- $this->log_debug( __METHOD__ . '(): Charge has been captured for entry #' . $entry_id . '. Mark is as paid.' );
+ $checkout_fulfillment_result = $this->maybe_checkout_fulfillment( $entry );
+ if ( is_wp_error( $checkout_fulfillment_result ) ) {
+ return $checkout_fulfillment_result;
+ }
- $action['entry_id'] = $entry_id;
- $action['type'] = 'complete_payment';
- $action['amount'] = $this->get_amount_import( rgars( $event, 'data/object/amount' ), $entry['currency'] );
+ // Completing payment. Marking entry as Paid.
+ $action['entry_id'] = $entry_id;
+ $action['type'] = 'complete_payment';
+ $action['amount'] = $this->get_amount_import( rgars( $event, 'data/object/amount' ), $entry['currency'] );
- // Run gform_stripe_fulfillment hook.
- $this->checkout_fulfillment( $session, $entry, $feed, $form );
- }
- }
- }
- }
break;
case 'customer.subscription.deleted':
-
$action['subscription_id'] = rgars( $event, 'data/object/id' );
$entry_id = $this->get_entry_by_transaction_id( $action['subscription_id'] );
if ( ! $entry_id ) {
@@ -6003,10 +6156,10 @@ public function stripe_checkout_redirect_scripts( $entry, $form ) {
// frame, Safari can only perform the redirection from a top-level frame.
// For regular embedded forms the wrapper is harmless.
?>
-
-
get_current_form();
- }
+ }
return $this->get_stripe_card_field( $form ) !== false;
}
@@ -6235,7 +6388,7 @@ public function get_stripe_card_field( $form ) {
* @return array $fields
*/
public function billing_info_fields() {
- $fields = array(
+ $fields = array(
array(
'name' => 'address_line1',
'label' => __( 'Address', 'gravityformsstripe' ),
@@ -6328,9 +6481,9 @@ public function get_setting( $setting_name, $default_value = '', $settings = fal
* @param int $field_id ID of the field being deleted.
*/
public function before_delete_field( $form_id, $field_id ) {
- parent::before_delete_field( $form_id, $field_id );
+ parent::before_delete_field( $form_id, $field_id );
- $form = GFAPI::get_form( $form_id );
+ $form = GFAPI::get_form( $form_id );
if ( $this->has_stripe_card_field( $form ) ) {
$field = $this->get_stripe_card_field( $form );
@@ -6650,6 +6803,71 @@ public function update_payment_intent() {
}
}
+ /**
+ * AJAX helper function to capture a payment that has been previously authorized.
+ *
+ * @since 4.2
+ */
+ public function ajax_capture_payment() {
+
+ check_ajax_referer( 'gfstripe_capture_payment', 'nonce' );
+
+ $entry_id = absint( rgpost( 'entry_id' ) );
+ $transaction_id = sanitize_text_field( rgpost( 'transaction_id' ) );
+
+ // Make sure we have the right entry.
+ $entry = GFAPI::get_entry( $entry_id );
+ if ( is_wp_error( $entry ) ) {
+ wp_send_json_error( esc_html__( 'Unable to find entry.', 'gravityformsstripe' ) );
+ }
+
+ // Make sure we have a payment feed.
+ $form = GFAPI::get_form( $entry['form_id'] );
+ $feed = $this->get_payment_feed( $entry, $form );
+ if ( is_wp_error( $feed ) ) {
+ wp_send_json_error( esc_html__( 'Unable to find payment feed.', 'gravityformsstripe' ) );
+ }
+
+ // Including Stripe API.
+ $result = $this->include_stripe_api_for_entry( $entry_id );
+ if ( is_wp_error( $result ) ) {
+ wp_send_json_error( esc_html__( 'Could not initialize Stripe API.', 'gravityformsstripe' ) . ' ' . $result->get_error_message() );
+ return;
+ }
+
+ $result = $this->capture_payment( $transaction_id );
+ if ( ! $result['is_captured'] ) {
+ wp_send_json_error( $result['message'] );
+ return;
+ }
+
+ // Set processing flag so that webhook does not also try to process this transaction.
+ update_option( "gform_stripe_capturing_{$transaction_id}", true );
+
+ // Mark authorized payment as Paid in Gravity Forms.
+ $this->log_debug( __METHOD__ . '(): Payment has been captured for entry #' . $entry_id . '. Mark it as paid.' );
+
+ // Updating entry. Firing hooks.
+ $this->complete_payment(
+ $entry,
+ array(
+ 'type' => 'complete_payment',
+ 'transaction_id' => $transaction_id,
+ 'amount' => $this->get_amount_import( $result['amount'], $entry['currency'] ),
+ )
+ );
+
+ // For transactions made via Stripe Checkout, run checkout_fulfillment().
+ $this->maybe_checkout_fulfillment( $entry );
+
+ // Cleaning up flag since processing is complete.
+ delete_option( "gform_stripe_capturing_{$transaction_id}" );
+
+ // Returning success message.
+ wp_send_json_success();
+ }
+
+
/**
* Turn country into two digits for Stripe Elements.
*
@@ -6670,6 +6888,69 @@ public function get_country_code() {
wp_send_json_success( array( 'code' => $code ) );
}
+ /**
+ * Refund a payment.
+ *
+ * @since 4.2
+ */
+ public function ajax_refund() {
+ check_ajax_referer( 'gf_stripe_refund', 'nonce' );
+
+ $transaction_id = sanitize_text_field( wp_unslash( empty( $_POST['transaction_id'] ) ? '' : $_POST['transaction_id'] ) );
+ $payment_intent = $this->is_payment_intent( $transaction_id );
+ $entry_id = sanitize_text_field( wp_unslash( empty( $_POST['entry_id'] ) ? '' : $_POST['entry_id'] ) );
+
+ // Make sure we have the right entry.
+ $entry = GFAPI::get_entry( $entry_id );
+ if ( is_wp_error( $entry ) ) {
+ wp_send_json_error( array( 'message' => __( 'Unable to find entry.', 'gravityformsstripe' ) ) );
+ }
+
+ // Make sure we have a payment feed.
+ $form = GFAPI::get_form( $entry['form_id'] );
+ $feed = $this->get_payment_feed( $entry, $form );
+ if ( is_wp_error( $feed ) ) {
+ wp_send_json_error( array( 'message' => __( 'Unable to find payment feed.', 'gravityformsstripe' ) ) );
+ }
+
+ // Load the Stripe API library.
+ if ( $this->is_feed_stripe_connect_enabled( $feed['id'] ) ) {
+ $this->include_stripe_api( $this->get_api_mode( $feed['meta'], $feed['id'] ), $feed['meta'] );
+ } else {
+ $this->include_stripe_api();
+ }
+
+ // Get the payment.
+ if ( $payment_intent ) {
+ $payment = $this->api->get_payment_intent( $transaction_id );
+ } else {
+ $payment = $this->api->get_charge( $transaction_id );
+ }
+
+ if ( ! $payment ) {
+ wp_send_json_error( array( 'message' => __( 'Unable to find payment on Stripe', 'gravityformsstripe' ) ) );
+ }
+
+ // Send the refund to Stripe.
+ $this->log_debug( __METHOD__ . sprintf( '(): Processing refund for transaction %s for entry #%d.', $transaction_id, $entry['id'] ) );
+ $refund = $this->api->create_refund( $transaction_id, $payment_intent );
+ if ( is_wp_error( $refund ) ) {
+ $this->log_error( __METHOD__ . '(): Unable to refund payment; ' . $refund->get_error_message() );
+ wp_send_json_error( array( 'message' => $refund->get_error_message() ) );
+ }
+
+ // Save the refund details.
+ $action = array(
+ 'payment_status' => 'Refunded',
+ 'transaction_type' => 'refund',
+ 'transaction_id' => $refund->id,
+ 'amount' => GFCommon::to_money( $this->get_amount_import( $refund->amount ) ),
+ );
+ $this->refund_payment( $entry, $action );
+
+ wp_send_json_success();
+ }
+
/**
* Alter product feeds payment data before sent with Stripe API.
*
@@ -6844,7 +7125,7 @@ public function get_authentication_state_action() {
}
/**
- * Retreives stripe account display name.
+ * Retrieves stripe account display name.
*
* @param \Stripe\Account $stripe_account Stripe account object.
*
@@ -6858,4 +7139,155 @@ private function get_stripe_display_name( $stripe_account ) {
return ! empty( $display_name ) ? $display_name : esc_html__( 'Unnamed account', 'gravityformsstripe' );
}
+ /**
+ * Captures a payment using the appropriate Stripe API
+ *
+ * @param string $transaction_id Stripe transaction ID.
+ *
+ * @since 4.2
+ *
+ * @return array The result of the capture operation in the following format:
+ * - is_captured (bool) Whether or not the payment is now captured (it could have been captured during this operation or have already been captured before).
+ * - message (string) Error or success message.
+ */
+ private function capture_payment( $transaction_id ) {
+
+ $is_payment_intent = $this->is_payment_intent( $transaction_id );
+
+ $payment_object = $is_payment_intent ? $this->api->get_payment_intent( $transaction_id ) : $this->api->get_charge( $transaction_id );
+ $object_label = $is_payment_intent ? 'payment intent' : 'charge';
+ if ( is_wp_error( $payment_object ) ) {
+ $this->log_error( __METHOD__ . '(): Cannot retrieve ' . $object_label . ' data from Stripe for transaction_id: ' . $transaction_id . '. error: ' . $payment_object->get_error_message() );
+
+ return array(
+ 'is_captured' => false,
+ 'message' => esc_html__( 'Could not retrieve payment information from Stripe.', 'gravityformsstripe' ),
+ );
+ }
+
+ // Abort if payment has already been captured.
+ if ( $this->is_payment_captured( $payment_object ) ) {
+ return array(
+ 'is_captured' => true,
+ 'message' => esc_html__( 'Payment has already been captured.', 'gravityformsstripe' ),
+ 'amount' => $this->get_captured_amount( $payment_object ),
+ );
+ }
+
+ // Capturing payment.
+ $captured_payment_object = $is_payment_intent ? $this->api->capture_payment_intent( $payment_object ) : $this->api->capture_charge( $payment_object );
+ $this->log_debug( __METHOD__ . '(): Capturing payment using ' . $object_label . ' API. transaction_id: ' . $transaction_id );
+
+ if ( is_wp_error( $captured_payment_object ) || ! $this->is_payment_captured( $captured_payment_object ) ) {
+
+ $wp_error = is_wp_error( $captured_payment_object ) ? $captured_payment_object : new WP_Error( 'could_not_capture', esc_html__( 'Unable to capture payment', 'gravityformsstripe' ) );
+ $this->log_error( __METHOD__ . '(): Cannot capture ' . $object_label . '. transaction_id: ' . $transaction_id . '. error: ' . $wp_error->get_error_message() );
+
+ return array(
+ 'is_captured' => false,
+ 'message' => $wp_error->get_error_message(),
+ );
+ }
+
+ return array(
+ 'is_captured' => true,
+ 'message' => esc_html__( 'Payment captured successfully.', 'gravityformsstripe' ),
+ 'amount' => $this->get_captured_amount( $captured_payment_object ),
+ );
+ }
+
+ /**
+ * Returns whether or not the specified payment object (Payment Intent or Charge) has been captured.
+ *
+ * @param array $payment_object A Payment Intent or Charge object.
+ *
+ * @since 4.2
+ *
+ * @return bool True if payment has been captured. False otherwise.
+ */
+ private function is_payment_captured( $payment_object ) {
+ return $payment_object['object'] == 'payment_intent' ? $payment_object->status === 'succeeded' : $payment_object->captured;
+ }
+
+ /**
+ * Returns the captured amount for the specified payment object (Payment Intent or Charge).
+ *
+ * @param array $payment_object A Payment Intent or Charge object.
+ *
+ * @since 4.2
+ *
+ * @return float The amount captured
+ */
+ private function get_captured_amount( $payment_object ) {
+ return $payment_object['object'] == 'payment_intent' ? $payment_object->amount_received : $payment_object->amount;
+ }
+
+ /**
+ * Includes the Stripe API using the global Stripe account configured in settings or the account specific to the Feed associated with the entry.
+ *
+ * @param int $entry_id Current entry ID.
+ *
+ * @since 4.2
+ *
+ * @return bool|WP_Error True if the API could be included. WP_Error otherwise.
+ */
+ private function include_stripe_api_for_entry( $entry_id ) {
+
+ // Getting feeds associated with the entry.
+ $feed_ids = $this->get_feeds_by_entry( $entry_id );
+ if ( empty( $feed_ids ) ) {
+ $this->log_error( __METHOD__ . '(): Cannot retrieve Stripe feed for entry_id: ' . $entry_id );
+
+ return new WP_Error( 'feed_not_found', esc_html__( 'Feed associated with entry could not be found.', 'gravityformsstripe' ) );
+ }
+ $feed_id = $feed_ids[0];
+
+ // Include Stripe API library.
+ if ( $this->is_feed_stripe_connect_enabled( $feed_id ) ) {
+ $feed = $this->get_feed( $feed_id );
+ $this->include_stripe_api( $this->get_api_mode( $feed['meta'], $feed['id'] ), $feed['meta'] );
+ } else {
+ $this->include_stripe_api();
+ }
+
+ return true;
+ }
+
+ /**
+ * Display the "Refund" button in the product details box on the entry page, if needed
+ *
+ * @since 4.2
+ *
+ * @param int $form_id The id of the form.
+ * @param array $entry The entry we're viewing.
+ */
+ public function maybe_display_refund_button( $form_id, $entry ) {
+ if ( $entry['payment_status'] !== 'Paid' || ! $this->is_payment_gateway( $entry['id'] ) ) {
+ return;
+ }
+
+ printf(
+ '%5$s %6$s
+
',
+ esc_attr( $entry['transaction_id'] ),
+ esc_attr( $entry['payment_amount'] ),
+ esc_attr( $entry['currency'] ),
+ esc_attr( $entry['id'] ),
+ esc_html__( 'Refund Payment', 'gravityformsstripe' ),
+ esc_html__( 'Sending refund request', 'gravityformsstripe' )
+ );
+ }
+
+ /**
+ * Helper function that determines if a transaction ID was created by the Payment Intent API
+ *
+ * @param string $transaction_id Current transaction ID.
+ *
+ * @since 4.2
+ *
+ * @return bool Returns true if the specified transaction ID was created by the Payment Intent API. Returns false otherwise.
+ */
+ private function is_payment_intent( $transaction_id ) {
+ return substr( $transaction_id, 0, 3 ) === 'pi_';
+ }
}
diff --git a/css/plugin_settings.css b/css/plugin_settings.css
index 8808c79..11c00a3 100644
--- a/css/plugin_settings.css
+++ b/css/plugin_settings.css
@@ -43,4 +43,4 @@
#trialPeriod {
width: 4rem;
text-align: center;
-}
\ No newline at end of file
+}
diff --git a/includes/class-gf-stripe-api.php b/includes/class-gf-stripe-api.php
index 00ad9a1..cd69c14 100644
--- a/includes/class-gf-stripe-api.php
+++ b/includes/class-gf-stripe-api.php
@@ -34,17 +34,29 @@ class GF_Stripe_API {
*/
protected $stripe_client;
+ /**
+ * Null or an instance of the Gravity Forms Stripe Add-On.
+ *
+ * @since 4.3
+ *
+ * @var GFStripe
+ */
+ protected $addon;
+
/**
* Initialize Stripe API library.
*
- * @since 3.4
+ * @since 3.4
+ * @since 4.3 Added the $addon param.
*
- * @param string $api_key Stripe API key.
+ * @param string $api_key Stripe API key.
+ * @param null|GFStripe $addon Null or an instance of the Gravity Forms Stripe Add-On.
*/
- public function __construct( $api_key ) {
+ public function __construct( $api_key, $addon = null ) {
$this->api_key = $api_key;
$this->api_version = '2020-03-02';
+ $this->addon = empty( $addon ) ? gf_stripe() : $addon;
// If Stripe class does not exist, load Stripe API library.
if ( ! class_exists( '\Stripe\Stripe' ) ) {
@@ -52,7 +64,8 @@ public function __construct( $api_key ) {
}
require_once 'deprecated.php';
- $this->stripe_client = new \Stripe\StripeClient( $api_key );
+ $this->init_client();
+
// Set Stripe API key.
Stripe\Stripe::setApiKey( $api_key );
// Set API version.
@@ -65,6 +78,28 @@ public function __construct( $api_key ) {
}
+ /**
+ * Initializes the StripeClient with the API key.
+ *
+ * @since 4.3
+ */
+ private function init_client() {
+ if ( ! class_exists( '\Stripe\StripeClient' ) ) {
+ $reflector = new ReflectionClass( \Stripe\Stripe::class );
+ $this->addon->log_debug( __METHOD__ . '(): \Stripe\StripeClient does not exist. An older or incomplete version of the Stripe PHP SDK is being included from ' . dirname( $reflector->getFileName() ) );
+
+ return;
+ }
+
+ if ( empty( $this->api_key ) ) {
+ $this->addon->log_debug( __METHOD__ . '(): Unable to initialize the Stripe API Client. The add-on is not connected to a Stripe account.' );
+
+ return;
+ }
+
+ $this->stripe_client = new \Stripe\StripeClient( $this->api_key );
+ }
+
/**
* Get Stripe account info.
*
@@ -73,6 +108,10 @@ public function __construct( $api_key ) {
* @return bool|WP_Error|\Stripe\Account Return WP_Error if exceptions thrown.
*/
public function get_account() {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
// Attempt to retrieve account details.
return $this->stripe_client->accounts->retrieve();
@@ -95,6 +134,10 @@ public function get_account() {
* @return Stripe\Charge|WP_Error
*/
public function create_charge( $charge_meta ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->charges->create( $charge_meta );
} catch ( \Exception $e ) {
@@ -113,6 +156,10 @@ public function create_charge( $charge_meta ) {
* @return \Stripe\Charge|WP_Error
*/
public function get_charge( $transaction_id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->charges->retrieve( $transaction_id );
} catch ( \Exception $e ) {
@@ -131,6 +178,10 @@ public function get_charge( $transaction_id ) {
* @return \Stripe\Charge|WP_Error
*/
public function save_charge( $charge ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->charges->update( $charge->id, $charge->serializeParameters() );
} catch ( \Exception $e ) {
@@ -149,6 +200,10 @@ public function save_charge( $charge ) {
* @return \Stripe\Charge|WP_Error
*/
public function capture_charge( $charge ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->charges->capture( $charge->id, $charge->serializeParameters() );
} catch ( \Exception $e ) {
@@ -167,6 +222,10 @@ public function capture_charge( $charge ) {
* @return Stripe\Plan|WP_Error
*/
public function create_plan( $plan_meta ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->plans->create( $plan_meta );
} catch ( \Exception $e ) {
@@ -184,6 +243,10 @@ public function create_plan( $plan_meta ) {
* @return bool|\Stripe\Plan|WP_Error
*/
public function get_plan( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->plans->retrieve( $id, array( 'expand' => array( 'product' ) ) );
} catch ( \Exception $e ) {
@@ -206,6 +269,27 @@ public function get_plan( $id ) {
}
}
+ /**
+ * Get the Stripe Product.
+ *
+ * @since 4.2
+ *
+ * @param string $id The Stripe Product ID.
+ *
+ * @return bool|\Stripe\Product|WP_Error
+ */
+ public function get_product( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
+ try {
+ return $this->stripe_client->products->retrieve( $id );
+ } catch ( \Exception $e ) {
+ return $this->get_error( $e );
+ }
+ }
+
/**
* Create the Stripe Customer.
*
@@ -216,6 +300,10 @@ public function get_plan( $id ) {
* @return \Stripe\Customer|WP_Error
*/
public function create_customer( $customer_meta ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->customers->create( $customer_meta );
} catch ( \Exception $e ) {
@@ -233,6 +321,10 @@ public function create_customer( $customer_meta ) {
* @return \Stripe\Customer|WP_Error
*/
public function get_customer( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->customers->retrieve( $id );
} catch ( \Exception $e ) {
@@ -250,6 +342,10 @@ public function get_customer( $id ) {
* @return \Stripe\Customer|WP_Error
*/
public function save_customer( $customer ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->customers->update( $customer->id, $customer->serializeParameters() );
} catch ( \Exception $e ) {
@@ -268,6 +364,10 @@ public function save_customer( $customer ) {
* @return \Stripe\Customer|WP_Error
*/
public function update_customer( $id, $meta ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->customers->update( $id, $meta );
} catch ( \Exception $e ) {
@@ -285,6 +385,10 @@ public function update_customer( $id, $meta ) {
* @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
*/
public function create_payment_intent( $data ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->paymentIntents->create( $data );
} catch ( \Exception $e ) {
@@ -302,6 +406,10 @@ public function create_payment_intent( $data ) {
* @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
*/
public function get_payment_intent( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->paymentIntents->retrieve( $id );
} catch ( \Exception $e ) {
@@ -320,6 +428,10 @@ public function get_payment_intent( $id ) {
* @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
*/
public function update_payment_intent( $id, $data ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->paymentIntents->update( $id, $data );
} catch ( \Exception $e ) {
@@ -337,6 +449,10 @@ public function update_payment_intent( $id, $data ) {
* @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
*/
public function confirm_payment_intent( $intent ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->paymentIntents->confirm( $intent->id, $intent->serializeParameters() );
} catch ( \Exception $e ) {
@@ -354,6 +470,10 @@ public function confirm_payment_intent( $intent ) {
* @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
*/
public function save_payment_intent( $intent ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->paymentIntents->update( $intent->id, $intent->serializeParameters() );
} catch ( \Exception $e ) {
@@ -371,6 +491,10 @@ public function save_payment_intent( $intent ) {
* @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
*/
public function capture_payment_intent( $intent ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->paymentIntents->capture( $intent->id, $intent->serializeParameters() );
} catch ( \Exception $e ) {
@@ -378,6 +502,34 @@ public function capture_payment_intent( $intent ) {
}
}
+ /**
+ * Cancels the payment intent.
+ *
+ * @since 4.2
+ *
+ * @param string $id The payment intent id.
+ * @param string $reason The optional reason for cancelling. Possible values are duplicate, fraudulent, requested_by_customer, or abandoned.
+ *
+ * @return \Stripe\PaymentIntent|WP_Error Return WP_Error if exceptions thrown.
+ */
+ public function cancel_payment_intent( $id, $reason = '' ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
+ $params = array();
+
+ if ( ! empty( $reason ) ) {
+ $params['cancellation_reason'] = $reason;
+ }
+
+ try {
+ return $this->stripe_client->paymentIntents->cancel( $id, $params );
+ } catch ( \Exception $e ) {
+ return $this->get_error( $e );
+ }
+ }
+
/**
* Create the checkout session.
*
@@ -388,6 +540,10 @@ public function capture_payment_intent( $intent ) {
* @return \Stripe\Checkout\Session|WP_Error
*/
public function create_checkout_session( $data ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->checkout->sessions->create( $data );
} catch ( \Exception $e ) {
@@ -405,6 +561,10 @@ public function create_checkout_session( $data ) {
* @return \Stripe\Checkout\Session|WP_Error
*/
public function get_checkout_session( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->checkout->sessions->retrieve( $id );
} catch ( \Exception $e ) {
@@ -422,6 +582,10 @@ public function get_checkout_session( $id ) {
* @return \Stripe\Coupon|WP_Error
*/
public function get_coupon( $coupon ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->coupons->retrieve( $coupon );
} catch ( \Exception $e ) {
@@ -439,6 +603,10 @@ public function get_coupon( $coupon ) {
* @return \Stripe\Subscription|WP_Error
*/
public function create_subscription( $meta ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->subscriptions->create( $meta );
} catch ( \Exception $e ) {
@@ -456,6 +624,10 @@ public function create_subscription( $meta ) {
* @return \Stripe\Subscription|WP_Error
*/
public function get_subscription( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->subscriptions->retrieve( $id );
} catch ( \Exception $e ) {
@@ -474,6 +646,10 @@ public function get_subscription( $id ) {
* @return \Stripe\Subscription|WP_Error
*/
public function update_subscription( $id, $meta ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->subscriptions->update( $id, $meta );
} catch ( \Exception $e ) {
@@ -491,6 +667,10 @@ public function update_subscription( $id, $meta ) {
* @return \Stripe\Subscription|WP_Error
*/
public function save_subscription( $subscription ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->subscriptions->update( $subscription->id, $subscription->serializeParameters() );
} catch ( \Exception $e ) {
@@ -508,6 +688,10 @@ public function save_subscription( $subscription ) {
* @return \Stripe\Subscription|WP_Error
*/
public function cancel_subscription( $subscription ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->subscriptions->cancel( $subscription->id );
} catch ( \Exception $e ) {
@@ -525,6 +709,10 @@ public function cancel_subscription( $subscription ) {
* @return \Stripe\Invoice|WP_Error
*/
public function get_invoice( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->invoices->retrieve( $id );
} catch ( \Exception $e ) {
@@ -543,6 +731,10 @@ public function get_invoice( $id ) {
* @return \Stripe\Invoice|WP_Error
*/
public function pay_invoice( $invoice, $params = array() ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->invoices->pay( $invoice->id, $params );
} catch ( \Exception $e ) {
@@ -560,6 +752,10 @@ public function pay_invoice( $invoice, $params = array() ) {
* @return \Stripe\InvoiceItem|WP_Error
*/
public function add_invoice_item( $params = array() ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->invoiceItems->create( $params );
} catch ( \Exception $e ) {
@@ -577,6 +773,10 @@ public function add_invoice_item( $params = array() ) {
* @return \Stripe\Event|WP_Error
*/
public function get_event( $id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return $this->stripe_client->events->retrieve( $id );
} catch ( \Exception $e ) {
@@ -596,6 +796,10 @@ public function get_event( $id ) {
* @return \Stripe\Event|WP_Error
*/
public function construct_event( $body, $sig_header, $endpoint_secret ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
try {
return \Stripe\Webhook::constructEvent( $body, $sig_header, $endpoint_secret );
} catch ( \Exception $e ) {
@@ -604,6 +808,63 @@ public function construct_event( $body, $sig_header, $endpoint_secret ) {
}
}
+
+ /**
+ * Create a billing portal link for the provided customer id.
+ *
+ * @since 4.2
+ *
+ * @param string $customer_id The customer id.
+ *
+ * @return string|WP_Error
+ */
+ public function get_billing_portal_link( $customer_id ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
+ try {
+
+ $response = $this->stripe_client->billingPortal->sessions->create(
+ array(
+ 'customer' => $customer_id,
+ 'return_url' => get_site_url(),
+ )
+ );
+
+ return $response->url;
+
+ } catch ( \Stripe\Exception\ApiErrorException $e ) {
+
+ return $this->get_error( $e );
+
+ }
+ }
+
+ /**
+ * Refund a payment.
+ *
+ * @since 4.2
+ *
+ * @param string $transaction_id The transaction ID to refund
+ * @param boolean $payment_intent Whether the payment was created with the payment intents API (true) or charges API (false)
+ *
+ * @return \Stripe\Refund|WP_Error
+ */
+ public function create_refund( $transaction_id, $payment_intent ) {
+ if ( empty( $this->stripe_client ) ) {
+ return $this->get_client_error();
+ }
+
+ $key = $payment_intent ? 'payment_intent' : 'charge';
+
+ try {
+ return $this->stripe_client->refunds->create( [ $key => $transaction_id ] );
+ } catch ( \Exception $e ) {
+ return $this->get_error( $e );
+ }
+ }
+
/**
* Get the exception and return WP_Error.
*
@@ -625,4 +886,20 @@ private function get_error( $e ) {
return new WP_Error( $e->getError()->code, $e->getError()->message );
}
}
+
+ /**
+ * Return the WP_Error for when the Stripe API client wasn't initialized.
+ *
+ * @since 4.3
+ *
+ * @return WP_Error
+ */
+ private function get_client_error() {
+ if ( ! class_exists( '\Stripe\StripeClient' ) ) {
+ return new WP_Error( 'stripe_client_missing', __( 'The Stripe API Client is missing. The theme or another plugin is including an older or incomplete version of the Stripe PHP SDK.', 'gravityformsstripe' ) );
+ }
+
+ return new WP_Error( 'stripe_client_not_connected', __( 'The Stripe API Client is not initialized. Please connect the add-on to a Stripe account.', 'gravityformsstripe' ) );
+ }
+
}
diff --git a/includes/class-gf-stripe-billing-portal.php b/includes/class-gf-stripe-billing-portal.php
new file mode 100644
index 0000000..9e8f75b
--- /dev/null
+++ b/includes/class-gf-stripe-billing-portal.php
@@ -0,0 +1,535 @@
+addon = $addon;
+
+ }
+
+ /**
+ * Create a billing portal link for the Stripe customer created by the given entry.
+ *
+ * @since 4.2
+ *
+ * @param array $entry The entry that created the Stripe customer.
+ *
+ * @return string|bool
+ */
+ public function get_entry_link( $entry ) {
+ $customer_id = gform_get_meta( rgar( $entry, 'id' ), 'stripe_customer_id' );
+ if ( empty( $customer_id ) ) {
+ $this->addon->log_debug( __METHOD__ . '(): No stripe_customer_id for entry #' . rgar( $entry, 'id' ) );
+
+ return '';
+ }
+
+ $api = $this->get_api_for_entry( $entry );
+
+ $link = $api->get_billing_portal_link( $customer_id );
+
+ if ( is_wp_error( $link ) ) {
+ $this->addon->log_error( __METHOD__ . '(): Unable to get portal link' . $link->get_error_message() );
+ return false;
+ }
+
+ return $link;
+ }
+
+ /**
+ * Displays the stripe self serve billing portal links for the logged in user.
+ *
+ * @since 4.2
+ *
+ * @param string $shortcode_string The full shortcode string.
+ * @param array $attributes The attributes within the shortcode.
+ * @param string $content The content of the shortcode, if available.
+ *
+ * @return string
+ */
+ public function stripe_customer_portal_link_shortcode( $string, $attributes, $content ) {
+
+ extract(
+ shortcode_atts(
+ array(
+ 'show_inactive' => true,
+ 'id' => 0,
+ 'redirect_url' => '',
+ ),
+ $attributes
+ )
+ );
+
+ $form_id = $id && (int) $id > 0 ? $id : 0;
+
+ return $this->build_shortcode_ui( $form_id, $show_inactive, $redirect_url );
+
+ }
+
+ /**
+ * Build the markup for the shortcode given the provided attributes.
+ *
+ * @since 4.2
+ *
+ * @param integer $form_id If provided only the subscriptions for this form will be outputted.
+ * @param bool $show_inactive Whether to show the inactive subscriptions or not.
+ * @param string $redirect_url Where to redirect the user to login.
+ *
+ * @return string
+ */
+ protected function build_shortcode_ui( $form_id, $show_inactive, $redirect_url ) {
+ $this->form_id = $form_id;
+ $this->show_inactive = $show_inactive;
+
+ if ( ! $this->get_user() ) {
+
+ return $this->get_redirect_script( $redirect_url );
+
+ }
+
+ $subscriptions = $this->get_user_subscriptions();
+
+ return $this->get_subscriptions_management_markup( $subscriptions );
+
+ }
+
+ /**
+ * Prints the required assets for styling and handling errors.
+ *
+ * To display and handle the errors correctly we need the filtered styles for the form and only two scripts.
+ *
+ * @since 4.2
+ *
+ * @param array $entry The entry used to get the form to print the assets for.
+ */
+ protected function print_assets( $entry ) {
+
+ if ( ! class_exists( 'GFFormDisplay' ) ) {
+ require_once GFCommon::get_base_path() . '/form_display.php';
+ }
+
+ $form = GFAPI::get_form( rgar( $entry, 'form_id' ) );
+ $assets = \GFFormDisplay::get_form_enqueue_assets( $form );
+
+ foreach ( $assets as $asset ) {
+
+ // To get the handle name we need to make it accessible as it is a protected property.
+ $reflection = new ReflectionClass( $asset );
+ $handle_property = $reflection->getProperty( 'handle' );
+ $handle_property->setAccessible( true );
+ $asset_handle = $handle_property->getValue( $asset );
+
+ // we only need styles, wp-a11y for accessibility, jQuery for some dom handling.
+ if ( $asset instanceof GF_Script_Asset === true && ! in_array( $asset_handle, array( 'wp-a11y', 'jquery' ) ) ) {
+ continue;
+ }
+
+ $asset->print_asset();
+
+ }
+
+ }
+
+ /**
+ * Returns the no subscriptions found message.
+ *
+ * @since 4.2
+ *
+ * @return string
+ */
+ private function get_no_subscriptions_found_message() {
+ $message = __( 'You don\'t have any subscriptions.', 'gravityformsstripe' );
+
+ /**
+ * Filters the no subscriptions found message.
+ *
+ * @since 4.2
+ *
+ * @param string $message The message to filter.
+ */
+ return gf_apply_filters( array( 'gform_stripe_no_subscriptions_found_message', $this->form_id ), $message );
+ }
+
+ /**
+ * Outputs JS code to redirect the user if not logged in.
+ *
+ * @since 4.2
+ *
+ * @param string $redirect_url where to redirect the user to login.
+ *
+ * @return string
+ */
+ private function get_redirect_script( $redirect_url = '' ) {
+
+ $current_page_url = \RGFormsModel::get_current_page_url();
+
+ if ( ! $redirect_url ) {
+ $redirect_url = wp_login_url( $current_page_url );
+ } else {
+ $redirect_url = add_query_arg(
+ array(
+ 'redirect_to' => urlencode( $current_page_url ),
+ ),
+ $redirect_url
+ );
+ }
+
+ return '
+
+
+
+ ';
+ }
+
+ /**
+ * Checks if a request to redirect the logged in user to the stripe billing patrol is present, creates the link and redirects the user to it.
+ *
+ * @since 4.2
+ */
+ public function maybe_redirect_logged_in_user_to_self_serve_link() {
+
+ if (
+ is_user_logged_in()
+ && rgpost( 'gforms_stripe_entry_id' )
+ && rgpost( 'gforms_stripe_customer_id' )
+ && wp_verify_nonce( rgpost( 'gforms_stripe_self_serve_link_nonce' ), 'gforms_stripe_self_serve_link_' . rgpost( 'gforms_stripe_customer_id' ) )
+ ) {
+
+ $entry = GFAPI::get_entry( rgpost( 'gforms_stripe_entry_id' ) );
+ $link = $this->get_entry_link( $entry );
+
+ if ( $link ) {
+ wp_redirect( $link );
+ } else {
+ $this->print_assets( $entry );
+ $this->shortcode_errors[] = array(
+ 'message' => __( 'There was an error generating your Customer Portal link.', 'gravityformsstripe' ),
+ 'subscription' => array(
+ 'entry_id' => $entry['id'],
+ 'title' => rgpost( 'gforms_stripe_subscription_title' ),
+ ),
+ );
+ }
+ }
+
+ }
+
+
+ /**
+ * Passes the subscriptions information to the view and returns the generated markup
+ *
+ * @since 4.2
+ *
+ * @param array $subscriptions Subscription information for a user.
+ *
+ * @return string
+ */
+ private function get_subscriptions_management_markup( $subscriptions ) {
+
+ $no_subscriptions_found_message = $this->get_no_subscriptions_found_message();
+ $errors = $this->shortcode_errors;
+
+ $markup = '';
+ ob_start();
+ include 'views/subscription-information.php';
+ $markup .= ob_get_clean();
+
+ /**
+ * Filters the subscriptions details markup.
+ *
+ * @since 4.2
+ *
+ * @param string $markup The list markup.
+ * @param array $subscriptions {
+ * Array of subscription information.
+ *
+ * @type string $plan_title The subscription plan title.
+ * @type string $currency The subscription currency.
+ * @type integer $start_date The subscription start date.
+ * @type double $price The subscription price.
+ * @type string $frequency The subscription frequency.
+ * @type string $status The subscription status.
+ * }
+ * @param integer $form_id The form ID to get the subscriptions for.
+ */
+ return gf_apply_filters( array( 'gform_stripe_subscriptions_self_serve_markup', $this->form_id ), $markup, $subscriptions, $this->form_id );
+ }
+
+
+ /**
+ * Returns the current logged in user.
+ *
+ * @since 4.2
+ *
+ * @return false|WP_User
+ */
+ private function get_user() {
+
+ if ( $this->user ) {
+ return $this->user;
+ }
+
+ if ( is_user_logged_in() ) {
+
+ $this->user = wp_get_current_user();
+
+ return $this->user;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Returns the Stripe subscription object that is associated for a specific entry.
+ *
+ * @since 4.2
+ *
+ * @param array $entry The entry to get the subscription object for.
+ *
+ * @return \Stripe\Subscription|WP_Error
+ */
+ private function get_stripe_subscription_for_entry( $entry ) {
+
+ $api = $this->get_api_for_entry( $entry );
+ $subscription_id = rgar( $entry, 'transaction_id' );
+ $subscription = $api->get_subscription( $subscription_id );
+
+ if ( is_wp_error( $subscription ) ) {
+ $this->addon->log_debug( __METHOD__ . '(): Failed to retrieve Stripe subscription object for entry ' . rgar( $entry, 'id' ) . ', error : ' . $subscription->get_error_message() );
+ }
+
+ return $subscription;
+
+ }
+
+ /**
+ * Retrieves the stripe product object for a subscription that is linked to a certain entry.
+ *
+ * @since 4.2
+ *
+ * @param integer $entry The entry we are getting the product for.
+ * @param string $product_id The product ID.
+ *
+ * @return Stripe\Product|WP_Error
+ */
+ private function get_stripe_subscription_product_for_entry( $entry, $product_id ) {
+
+ $api = $this->get_api_for_entry( $entry );
+ $product = $api->get_product( $product_id );
+
+ if ( is_wp_error( $product ) ) {
+ $this->addon->log_debug( __METHOD__ . '(): Failed to retrieve Stripe product ' . $product_id . ', error : ' . $subscription->get_error_message() );
+ }
+
+ return $product;
+ }
+
+ /**
+ * Looks for and returns the IDs of all the stripe entries that have a subscription made with the provided email address.
+ *
+ * Any subscription requires a customer_id, which is created before the subscription itself is created and is saved to the entry's meta with the stripe_customer_id key.
+ * We are using this key and the email to find the subscription entry(s), where the customer_id(s) is saved, which is needed to generate the link.
+ *
+ * @since 4.2
+ *
+ * @param string $email The email address to use for the search.
+ *
+ * @return array A list of entry IDs that have subscriptions made by the provided email address.
+ */
+ private function get_email_entry_ids( $email ) {
+
+ $entry_ids = GFAPI::get_entry_ids(
+ $this->form_id,
+ array(
+ 'transaction_type' => 2,
+ 'is_fulfilled' => 1,
+ 'field_filters' => array(
+ array(
+ 'value' => $email,
+ ),
+ array(
+ 'key' => 'payment_gateway',
+ 'value' => 'gravityformsstripe',
+ ),
+ array(
+ 'key' => 'stripe_customer_id',
+ 'operator' => '<>',
+ 'value' => '',
+ ),
+ ),
+ )
+ );
+
+ return $entry_ids;
+
+ }
+
+ /**
+ * Gets all the subscriptions and their details for the provided email.
+ *
+ * @since 4.2
+ *
+ * @param string $email The email address to use for the search.
+ *
+ * @return array The subscriptions found.
+ */
+ private function get_email_subscriptions( $email ) {
+
+ $subscriptions = array();
+ $entry_ids = $this->get_email_entry_ids( $email );
+
+ foreach ( $entry_ids as $entry_id ) {
+
+ $entry = GFAPI::get_entry( $entry_id );
+
+ $stripe_subscription = $this->get_stripe_subscription_for_entry( $entry );
+
+ if (
+ is_wp_error( $stripe_subscription )
+ || ( $this->show_inactive !== true && $stripe_subscription->status === 'canceled' )
+ ) {
+ continue;
+ }
+
+ $stripe_product = $this->get_stripe_subscription_product_for_entry( $entry, $stripe_subscription->plan->product );
+ $product_name = __( 'Subscription', 'gravityformsstripe' );
+ if ( ! is_wp_error( $stripe_product ) ) {
+ $product_name = $stripe_product->name;
+ }
+
+ $subscription['plan_title'] = $product_name;
+ $subscription['currency'] = strtoupper( $stripe_subscription->plan->currency );
+ $subscription['start_date'] = $stripe_subscription->start_date;
+ $subscription['price'] = $this->addon->get_amount_import( $stripe_subscription->plan->amount, $subscription['currency'] );
+ $subscription['frequency'] = $stripe_subscription->plan->interval;
+ $subscription['status'] = $stripe_subscription->status;
+
+ if ( $stripe_subscription->cancel_at_period_end !== false && $stripe_subscription->current_period_end > time() ) {
+ /* Translators: 1: Subscription end date */
+ $subscription['status'] .= ' ' . sprintf( __( 'until %s', 'gravityformsstripe' ), date( 'M, jS Y', $stripe_subscription->current_period_end ) );
+ }
+
+ if ( $stripe_subscription->status === 'trialing' ) {
+ /* Translators: 1: subscription first billing period date after trial period is over */
+ $subscription['frequency'] .= ' ' . sprintf( __( 'starting from %s', 'gravityformsstripe' ), date( 'M, jS Y', $stripe_subscription->trial_end ) );
+ }
+
+ $subscription['customer_id'] = gform_get_meta( rgar( $entry, 'id' ), 'stripe_customer_id' );
+
+ $subscriptions[ $entry_id ] = $subscription;
+
+ }
+
+ return $subscriptions;
+ }
+
+ /**
+ * Gets all the subscriptions and their details for the user.
+ *
+ * @since 4.2
+ *
+ * @return array The subscriptions found or an empty array if no subscriptions were found.
+ */
+ private function get_user_subscriptions() {
+ $user = $this->get_user();
+ if ( ! is_a( $user, 'WP_User' ) || empty( $user->user_email ) ) {
+ return array();
+ }
+
+ return $this->get_email_subscriptions( $user->user_email );
+ }
+
+ /**
+ * Initializes an API object using the settings found in the feed or the general settings if no feeds found or if no settings were saved in the feed.
+ *
+ * Stripe has a feature that allows admins to connect to a different stripe account on feed level.
+ *
+ * @since 4.2
+ *
+ * @param array $entry The entry to get the API object for.
+ *
+ * @return GF_Stripe_API
+ */
+ private function get_api_for_entry( $entry ) {
+
+ $feed = $this->addon->get_payment_feed( $entry );
+
+ if ( ! empty( $feed ) && $this->addon->is_feed_stripe_connect_enabled( $feed['id'] ) ) {
+ $api = $this->addon->include_stripe_api( $this->addon->get_api_mode( $feed['meta'], $feed['id'] ), $feed['meta'] );
+ } else {
+ $api = $this->addon->include_stripe_api();
+ }
+
+ return $api;
+ }
+
+}
diff --git a/includes/views/subscription-information.php b/includes/views/subscription-information.php
new file mode 100644
index 0000000..218fd76
--- /dev/null
+++ b/includes/views/subscription-information.php
@@ -0,0 +1,151 @@
+
+
+
diff --git a/js/admin.js b/js/admin.js
index 3a04988..738f23f 100644
--- a/js/admin.js
+++ b/js/admin.js
@@ -30,6 +30,9 @@ window.GFStripeAdmin = null;
this.maybeLockAccountSettings();
this.bindWebhookAlert();
+
+ this.bindRefund();
+ this.bindCapture();
}
this.validateKey = function(keyName, key){
@@ -195,7 +198,7 @@ window.GFStripeAdmin = null;
alert(response.data.message);
}
- $button.removeAttr('disabled');
+ deauthButton.removeAttr('disabled');
}
});
}
@@ -256,7 +259,7 @@ window.GFStripeAdmin = null;
// Use the built-in "beforeunload" event to throw the confirmation when redirecting.
window.addEventListener('beforeunload', function (e) {
- if ( self.accountSettingsLocked || $('.error.below-h2').length ) {
+ if ( self.accountSettingsLocked ) {
// Cancel the event
e.preventDefault();
// Chrome requires returnValue to be set
@@ -275,6 +278,126 @@ window.GFStripeAdmin = null;
}
}
+
+ /**
+ * Handles refund button click and sends a refund ajax request.
+ *
+ * @since 1.3 //@todo - version
+ */
+ this.bindRefund = function() {
+ $( '.stripe-refund' ).on(
+ 'click',
+ function( e ) {
+ e.preventDefault();
+ var refundButton = $( '.stripe_refund' ),
+ refundWaitContainer = $( '#refund_wait_container' ),
+ transactionId = $( this ).data( 'tid' );
+ entryId = $( this ).data( 'lid' );
+ if ( ! window.confirm( gforms_stripe_admin_strings.refund ) ) {
+ return false;
+ }
+
+ // Set disabled state.
+ refundButton.prop( 'disabled', true );
+ refundWaitContainer.fadeIn();
+ wp.a11y.speak( gforms_stripe_admin_strings.refund_processing, 'assertive' );
+
+ $.ajax(
+ {
+ async: true,
+ url: ajaxurl,
+ dataType: 'json',
+ method: 'POST',
+ data: {
+ action: 'gfstripe_refund',
+ transaction_id: transactionId,
+ entry_id: entryId,
+ nonce: gforms_stripe_admin_strings.refund_nonce,
+ },
+ success: function( response ) {
+ if ( response.success ) {
+ window.location.reload();
+ } else {
+ wp.a11y.speak( response.data.message, 'assertive' );
+ $( '.gform_stripe_refund_alert' ).show().html( response.data.message );
+ }
+ },
+ complete: function() {
+ wp.a11y.speak( gforms_stripe_admin_strings.refund_complete, 'assertive' );
+ refundButton.prop( 'disabled', false );
+ refundWaitContainer.hide();
+ },
+ }
+ ).fail(
+ function( jqXHR, textStatus, error ) {
+ window.alert( error );
+ refundButton.prop( 'disabled', false );
+ refundWaitContainer.hide();
+ }
+ );
+ }
+ );
+ };
+
+ this.bindCapture = function() {
+ $( '.stripe-capture' ).on( 'click keypress',
+ function( e ) {
+
+ e.preventDefault();
+
+ var captureButton = $( '.stripe-capture' ),
+ captureWaitContainer = $( '#capture_wait_container' );
+ errorContainer = $( '.gform_stripe_capture_alert' );
+
+ if ( ! window.confirm( gforms_stripe_admin_strings.capture_confirm ) ) {
+ return false;
+ }
+
+ // Set disabled state
+ captureWaitContainer.fadeIn();
+ captureButton.prop( 'disabled', true );
+ errorContainer.hide();
+ wp.a11y.speak( gforms_stripe_admin_strings.capture_processing, 'assertive' );
+
+ var requestData = captureButton.data( 'ajax' );
+
+ $.ajax( {
+ url: ajaxurl,
+ method: 'POST',
+ data: {
+ action: 'gfstripe_capture_action',
+ transaction_id: requestData.transaction_id,
+ entry_id: requestData.entry_id,
+ nonce: requestData.nonce,
+ },
+ success: function( response ) {
+
+ if ( response.success ) {
+ wp.a11y.speak( gforms_stripe_admin_strings.capture_complete, 'assertive' );
+ // Success. Reload page.
+ window.location.reload();
+ } else {
+ wp.a11y.speak( response.data, 'assertive' );
+ errorContainer.show().html( response.data );
+
+ captureButton.prop( 'disabled', false );
+ captureWaitContainer.hide();
+ }
+ }
+ }
+ ).fail(
+ function( jqXHR, textStatus, error ) {
+ wp.a11y.speak( error, 'assertive' );
+ errorContainer.show().html( error );
+
+ captureButton.prop( 'disabled', false );
+ captureWaitContainer.hide();
+ }
+ );
+ }
+ );
+ };
+
this.init();
};
diff --git a/js/admin.min.js b/js/admin.min.js
index 2a6d3e6..439bbdf 100644
--- a/js/admin.min.js
+++ b/js/admin.min.js
@@ -1 +1 @@
-window.GFStripeAdmin=null,function(r){function t(){var o=this;this.accountSettingsLocked=!1,this.deauthActionable=!1,this.inputContainerPrefix=gforms_stripe_admin_strings.input_container_prefix,this.inputPrefix=gforms_stripe_admin_strings.input_prefix,this.liveDependencySupported=gforms_stripe_admin_strings.liveDependencySupported,this.apiMode=gforms_stripe_admin_strings.apiMode,this.init=function(){this.initKeyStatus("live_publishable_key"),this.initKeyStatus("live_secret_key"),this.initKeyStatus("test_publishable_key"),this.initKeyStatus("test_secret_key"),this.bindDeauthorize(),this.liveDependencySupported||this.bindAPIModeChange(),this.maybeLockAccountSettings(),this.bindWebhookAlert()},this.validateKey=function(t,e){0!=e.length?(r("#"+t).val(e.trim()),this.setKeyStatusIcon(t," "),"live_publishable_key"==t||"test_publishable_key"==t?this.validatePublishableKey(t,e):this.validateSecretKey(t,e)):this.setKeyStatus(t,"")},this.validateSecretKey=function(e,t){r.post(ajaxurl,{action:"gf_validate_secret_key",keyName:e,key:t,nonce:gforms_stripe_admin_strings.ajax_nonce},function(t){"valid"==(t=t.trim())?o.setKeyStatus(e,"1"):"invalid"==t?o.setKeyStatus(e,"0"):o.setKeyStatusIcon(e,gforms_stripe_admin_strings.validation_error)})},this.validatePublishableKey=function(i,t){this.setKeyStatusIcon(i," "),cc={number:"4916433572511762",exp_month:"01",exp_year:(new Date).getFullYear()+1,cvc:"111",name:"Test Card"},Stripe.setPublishableKey(t),Stripe.card.createToken(cc,function(t,e){200!=t&&(400!=t&&402!=t||"live_publishable_key"!=i)?o.setKeyStatus(i,"0"):o.setKeyStatus(i,"1")})},this.initKeyStatus=function(t){var e=r("#"+t+"_is_valid"),i=r("#"+t);0':"0"==e&&(i=' '),this.setKeyStatusIcon(t,i)},this.setKeyStatusIcon=function(t,e){var i=r("#"+t+"_status_icon");0 "+e+"")},this.bindDeauthorize=function(){r(".gform_stripe_deauth_button").on("click",function(t){if(t.preventDefault(),o.accountSettingsLocked)return window.location.reload(),!1;var e=r("#gform_stripe_deauth_button"),i=r("#deauth_scope"),n=gforms_stripe_admin_strings.disconnect,s=r(this).data("mode"),a=r(this).data("fid");if(o.deauthActionable){t=r("#"+s+"_deauth_scope0").is(":checked")?"site":"account",n="site"==t&&""!==a?n.feed:n[t];if(!confirm(n))return!1;e.attr("disabled","disabled"),r.ajax({async:!1,url:ajaxurl,dataType:"json",method:"POST",data:{action:"gfstripe_deauthorize",scope:t,fid:a,id:r(this).data("id"),mode:s,nonce:gforms_stripe_admin_strings.ajax_nonce},success:function(t){t.success?window.location.reload():alert(t.data.message),$button.removeAttr("disabled")}})}else r(".gform_stripe_deauth_button").eq(0).hide(),""!==a&&r(".connected_to_stripe_text").hide(),i.show(0,function(){o.deauthActionable=!0})})},this.bindAPIModeChange=function(){""!==this.apiMode&&void 0!==this.apiMode||(this.apiMode="live",r("#api_mode0").prop("checked",!0));var e="live"===this.apiMode?"test":"live";r("#"+this.inputContainerPrefix+this.apiMode+"_auth_token").show(),r("#"+this.inputContainerPrefix+e+"_auth_token").hide(),r('#tab_gravityformsstripe input[name="'+this.inputPrefix+'_api_mode"]').on("click",function(t){o.apiMode=r(this).val(),e="live"===o.apiMode?"test":"live",r("#"+o.inputContainerPrefix+e+"_auth_token").hide(),r("#"+o.inputContainerPrefix+o.apiMode+"_auth_token").show()})},this.maybeLockAccountSettings=function(){r("#"+this.inputContainerPrefix+"connected_to").siblings("#"+this.inputContainerPrefix+"api_mode, #"+this.inputContainerPrefix+"live_auth_token, #"+this.inputContainerPrefix+"test_auth_token").hide(),r("#gform_stripe_change_account").on("click",function(){var t;r(this).data("disabled")?alert(gforms_stripe_admin_strings.switch_account_disabled_message):(r("#"+o.inputContainerPrefix+"api_mode").show(),t="live"===o.apiMode?"test":"live",r("#"+o.inputContainerPrefix+t+"_auth_token").hide(),r("#"+o.inputContainerPrefix+o.apiMode+"_auth_token").show(),r(this).off("click").addClass("disabled"))}),r("table.gforms_form_settings").on("change","input, select",function(){var t=r(this).attr("name");t!==o.inputPrefix+"_api_mode"&&"deauth_scope"!==t&&t!==o.inputPrefix+"_transactionType"&&(o.accountSettingsLocked=!0)}),r("#gform-settings-save").on("click",function(){r(".error.below-h2").remove(),o.accountSettingsLocked=!1}),window.addEventListener("beforeunload",function(t){(o.accountSettingsLocked||r(".error.below-h2").length)&&(t.preventDefault(),t.returnValue="")})},this.bindWebhookAlert=function(){r("#gform_stripe_change_account").length&&""===r("#"+this.apiMode+"_signing_secret").val()&&(r("#webhooks_enabled").focus(),r([document.documentElement,document.body]).animate({scrollTop:r("#"+o.inputContainerPrefix+"api_mode").offset().top+20},1e3))},this.init()}r(document).ready(function(){GFStripeAdmin=new t})}(jQuery);
\ No newline at end of file
+window.GFStripeAdmin=null,function(o){function e(){var r=this;this.accountSettingsLocked=!1,this.deauthActionable=!1,this.inputContainerPrefix=gforms_stripe_admin_strings.input_container_prefix,this.inputPrefix=gforms_stripe_admin_strings.input_prefix,this.liveDependencySupported=gforms_stripe_admin_strings.liveDependencySupported,this.apiMode=gforms_stripe_admin_strings.apiMode,this.init=function(){this.initKeyStatus("live_publishable_key"),this.initKeyStatus("live_secret_key"),this.initKeyStatus("test_publishable_key"),this.initKeyStatus("test_secret_key"),this.bindDeauthorize(),this.liveDependencySupported||this.bindAPIModeChange(),this.maybeLockAccountSettings(),this.bindWebhookAlert(),this.bindRefund(),this.bindCapture()},this.validateKey=function(e,t){0!=t.length?(o("#"+e).val(t.trim()),this.setKeyStatusIcon(e," "),"live_publishable_key"==e||"test_publishable_key"==e?this.validatePublishableKey(e,t):this.validateSecretKey(e,t)):this.setKeyStatus(e,"")},this.validateSecretKey=function(t,e){o.post(ajaxurl,{action:"gf_validate_secret_key",keyName:t,key:e,nonce:gforms_stripe_admin_strings.ajax_nonce},function(e){"valid"==(e=e.trim())?r.setKeyStatus(t,"1"):"invalid"==e?r.setKeyStatus(t,"0"):r.setKeyStatusIcon(t,gforms_stripe_admin_strings.validation_error)})},this.validatePublishableKey=function(i,e){this.setKeyStatusIcon(i," "),cc={number:"4916433572511762",exp_month:"01",exp_year:(new Date).getFullYear()+1,cvc:"111",name:"Test Card"},Stripe.setPublishableKey(e),Stripe.card.createToken(cc,function(e,t){200!=e&&(400!=e&&402!=e||"live_publishable_key"!=i)?r.setKeyStatus(i,"0"):r.setKeyStatus(i,"1")})},this.initKeyStatus=function(e){var t=o("#"+e+"_is_valid"),i=o("#"+e);0':"0"==t&&(i=' '),this.setKeyStatusIcon(e,i)},this.setKeyStatusIcon=function(e,t){var i=o("#"+e+"_status_icon");0 "+t+"")},this.bindDeauthorize=function(){o(".gform_stripe_deauth_button").on("click",function(e){if(e.preventDefault(),r.accountSettingsLocked)return window.location.reload(),!1;var t=o("#gform_stripe_deauth_button"),i=o("#deauth_scope"),n=gforms_stripe_admin_strings.disconnect,s=o(this).data("mode"),a=o(this).data("fid");if(r.deauthActionable){e=o("#"+s+"_deauth_scope0").is(":checked")?"site":"account",n="site"==e&&""!==a?n.feed:n[e];if(!confirm(n))return!1;t.attr("disabled","disabled"),o.ajax({async:!1,url:ajaxurl,dataType:"json",method:"POST",data:{action:"gfstripe_deauthorize",scope:e,fid:a,id:o(this).data("id"),mode:s,nonce:gforms_stripe_admin_strings.ajax_nonce},success:function(e){e.success?window.location.reload():alert(e.data.message),t.removeAttr("disabled")}})}else o(".gform_stripe_deauth_button").eq(0).hide(),""!==a&&o(".connected_to_stripe_text").hide(),i.show(0,function(){r.deauthActionable=!0})})},this.bindAPIModeChange=function(){""!==this.apiMode&&void 0!==this.apiMode||(this.apiMode="live",o("#api_mode0").prop("checked",!0));var t="live"===this.apiMode?"test":"live";o("#"+this.inputContainerPrefix+this.apiMode+"_auth_token").show(),o("#"+this.inputContainerPrefix+t+"_auth_token").hide(),o('#tab_gravityformsstripe input[name="'+this.inputPrefix+'_api_mode"]').on("click",function(e){r.apiMode=o(this).val(),t="live"===r.apiMode?"test":"live",o("#"+r.inputContainerPrefix+t+"_auth_token").hide(),o("#"+r.inputContainerPrefix+r.apiMode+"_auth_token").show()})},this.maybeLockAccountSettings=function(){o("#"+this.inputContainerPrefix+"connected_to").siblings("#"+this.inputContainerPrefix+"api_mode, #"+this.inputContainerPrefix+"live_auth_token, #"+this.inputContainerPrefix+"test_auth_token").hide(),o("#gform_stripe_change_account").on("click",function(){var e;o(this).data("disabled")?alert(gforms_stripe_admin_strings.switch_account_disabled_message):(o("#"+r.inputContainerPrefix+"api_mode").show(),e="live"===r.apiMode?"test":"live",o("#"+r.inputContainerPrefix+e+"_auth_token").hide(),o("#"+r.inputContainerPrefix+r.apiMode+"_auth_token").show(),o(this).off("click").addClass("disabled"))}),o("table.gforms_form_settings").on("change","input, select",function(){var e=o(this).attr("name");e!==r.inputPrefix+"_api_mode"&&"deauth_scope"!==e&&e!==r.inputPrefix+"_transactionType"&&(r.accountSettingsLocked=!0)}),o("#gform-settings-save").on("click",function(){o(".error.below-h2").remove(),r.accountSettingsLocked=!1}),window.addEventListener("beforeunload",function(e){r.accountSettingsLocked&&(e.preventDefault(),e.returnValue="")})},this.bindWebhookAlert=function(){o("#gform_stripe_change_account").length&&""===o("#"+this.apiMode+"_signing_secret").val()&&(o("#webhooks_enabled").focus(),o([document.documentElement,document.body]).animate({scrollTop:o("#"+r.inputContainerPrefix+"api_mode").offset().top+20},1e3))},this.bindRefund=function(){o(".stripe-refund").on("click",function(e){e.preventDefault();var n=o(".stripe_refund"),s=o("#refund_wait_container"),e=o(this).data("tid");if(entryId=o(this).data("lid"),!window.confirm(gforms_stripe_admin_strings.refund))return!1;n.prop("disabled",!0),s.fadeIn(),wp.a11y.speak(gforms_stripe_admin_strings.refund_processing,"assertive"),o.ajax({async:!0,url:ajaxurl,dataType:"json",method:"POST",data:{action:"gfstripe_refund",transaction_id:e,entry_id:entryId,nonce:gforms_stripe_admin_strings.refund_nonce},success:function(e){e.success?window.location.reload():(wp.a11y.speak(e.data.message,"assertive"),o(".gform_stripe_refund_alert").show().html(e.data.message))},complete:function(){wp.a11y.speak(gforms_stripe_admin_strings.refund_complete,"assertive"),n.prop("disabled",!1),s.hide()}}).fail(function(e,t,i){window.alert(i),n.prop("disabled",!1),s.hide()})})},this.bindCapture=function(){o(".stripe-capture").on("click keypress",function(e){e.preventDefault();var n=o(".stripe-capture"),s=o("#capture_wait_container");if(errorContainer=o(".gform_stripe_capture_alert"),!window.confirm(gforms_stripe_admin_strings.capture_confirm))return!1;s.fadeIn(),n.prop("disabled",!0),errorContainer.hide(),wp.a11y.speak(gforms_stripe_admin_strings.capture_processing,"assertive");e=n.data("ajax");o.ajax({url:ajaxurl,method:"POST",data:{action:"gfstripe_capture_action",transaction_id:e.transaction_id,entry_id:e.entry_id,nonce:e.nonce},success:function(e){e.success?(wp.a11y.speak(gforms_stripe_admin_strings.capture_complete,"assertive"),window.location.reload()):(wp.a11y.speak(e.data,"assertive"),errorContainer.show().html(e.data),n.prop("disabled",!1),s.hide())}}).fail(function(e,t,i){wp.a11y.speak(i,"assertive"),errorContainer.show().html(i),n.prop("disabled",!1),s.hide()})})},this.init()}o(document).ready(function(){GFStripeAdmin=new e})}(jQuery);
\ No newline at end of file
diff --git a/js/frontend.js b/js/frontend.js
index c4f8fbe..fe634a7 100644
--- a/js/frontend.js
+++ b/js/frontend.js
@@ -648,4 +648,4 @@ window.GFStripe = null;
}
-})(jQuery);
\ No newline at end of file
+})(jQuery);
diff --git a/languages/gravityformsstripe-ar.mo b/languages/gravityformsstripe-ar.mo
index bb99014b81309288f68369b6a724aa5017b51f35..3298efec1ded1be684942a8261e747e7be0e07d4 100644
GIT binary patch
delta 12
Tcmcc0e3f}ZHlxwToJ>XlAKnCJ
delta 12
Tcmcc0e3f}ZHlyLjoJ>XlAK3(D
diff --git a/languages/gravityformsstripe-ca.mo b/languages/gravityformsstripe-ca.mo
index 556915940fb238c4ca26c00737c35ef9b19c3506..7c6060818934401874d27235b97b3ad4c44563c7 100644
GIT binary patch
delta 12
TcmZ3*zKVUqQbwbV%UGBJ9ry$(
delta 12
TcmZ3*zKVUqQbxm#%UGBJ9rFYz
diff --git a/languages/gravityformsstripe-da_DK.mo b/languages/gravityformsstripe-da_DK.mo
index 65c1836907955f6609447e55e948c5b2eb0f87f8..f84faf223f9c3ca8c0696e6fb7bfeed9bb480e00 100644
GIT binary patch
delta 14
VcmbPYJjHlJAuprR<|1At9sni~1W*6~
delta 14
VcmbPYJjHlJAuprh<|1At9sni^1Wy0}
diff --git a/languages/gravityformsstripe-de_DE.mo b/languages/gravityformsstripe-de_DE.mo
index e7add68bffc7b51a856faaf7aace57a585790942..60ed9e999cfa6e949ee518d0f942b92802cfc643 100644
GIT binary patch
delta 16
XcmZqK&DgS=aYLISqtWJe!@t@9IeG@J
delta 16
XcmZqK&DgS=aYLISqv7Uu!@t@9IdlfC
diff --git a/languages/gravityformsstripe-de_DE_formal.mo b/languages/gravityformsstripe-de_DE_formal.mo
index 12a96cda9f9ef0b19e97248e7c8ced8891eee5e3..1199830c0bbd172011859d5ddc983aa0d95560be 100644
GIT binary patch
delta 12
Tcmdnaww-N4HlxwToYPDI9!mt1
delta 12
Tcmdnaww-N4HlyLjoYPDI9!3O`
diff --git a/languages/gravityformsstripe-en_AU.mo b/languages/gravityformsstripe-en_AU.mo
index c73a9dd17909e6bcd747f04a7983b086d60d2cec..41bafa89116c86749ea9884972db8f9d99491005 100644
GIT binary patch
delta 14
Vcmdnyy3KV%u`r|2<`Q8iegG@e1nU3*
delta 14
Vcmdnyy3KV%u`r|I<`Q8iegG@Y1nK|)
diff --git a/languages/gravityformsstripe-en_GB.mo b/languages/gravityformsstripe-en_GB.mo
index 09fe50843a9d386c786023fd99fbf9f4ad692da9..cf490c93ef356602bdb8954ad86560a6823e06f5 100644
GIT binary patch
delta 14
VcmcbreN}tIPHsk{&AYgzxd1P01w{Y=
delta 14
VcmcbreN}tIPHslS&AYgzxd1O_1w;S<
diff --git a/languages/gravityformsstripe-es_ES.mo b/languages/gravityformsstripe-es_ES.mo
index a2035c4e95df3dc35069a18a108ed001017b6f12..812bdc84d3609def69860cddf23d12aff19d7d31 100644
GIT binary patch
delta 16
XcmZqQ$JoA)aYLISqtWJe!v1z!LF
diff --git a/languages/gravityformsstripe-fr_CA.mo b/languages/gravityformsstripe-fr_CA.mo
index cecac8691f3c99a1fc61bfd3967656df7b3e3e6d..d2f0052284d471cde87e9255a207f3da4f1ec3fe 100644
GIT binary patch
delta 14
VcmZ2su)<)2E)S#8W<8!?+yEv31it_P
delta 14
VcmZ2su)<)2E)S#OW<8!?+yEu|1ikz>%
diff --git a/languages/gravityformsstripe-hu_HU.mo b/languages/gravityformsstripe-hu_HU.mo
index 6b8b2849d20da80d7e88c1bc864427b8dd1c3a7e..9888dc1af17b437dd69ceb42c115c8b1b46ad751 100644
GIT binary patch
delta 12
TcmbQsJePSwHlxwT979F`8lD5b
delta 12
TcmbQsJePSwHlyLj979F`8kqyV
diff --git a/languages/gravityformsstripe-it_IT.mo b/languages/gravityformsstripe-it_IT.mo
index 0aa7f7207dfe88eb084849f1de1ec77a1c374ab9..8f1b0ff7b8e5fc7ea22b0335beeeb5cda1b140dd 100644
GIT binary patch
delta 16
YcmbQck8$ok#tm(Tj7FQ=4X5b>06WwMfdBvi
delta 16
YcmbQck8$ok#tm(TjE0-r4X5b>06WeGfB*mh
diff --git a/languages/gravityformsstripe-ja.mo b/languages/gravityformsstripe-ja.mo
index 5438ec551a0071d10c300a891c2ad3a51cf0330d..bbc8c77eeac95cbb464cef4fb6a41ab8f1ea2277 100644
GIT binary patch
delta 16
XcmZoU$Jla?aYLyQqtWIvqdpS=In)MD
delta 16
XcmZoU$Jla?aYLyQqv7T2L8a@NE
delta 12
TcmbQqJd=4sHlyLj99>2L8aV^8
diff --git a/languages/gravityformsstripe-nl_NL.mo b/languages/gravityformsstripe-nl_NL.mo
index 1fb764b77856352dee99968953be7f0c8b9278ff..26a6f43f7fae7842e3db578d7ba98cb7a0c06a4b 100644
GIT binary patch
delta 16
YcmX@MgYno7#tm(Tj7FQ=4fksS06-=N?EnA(
delta 16
YcmX@MgYno7#tm(TjE0-r4fksS06-uH>;M1&
diff --git a/languages/gravityformsstripe-pt_BR.mo b/languages/gravityformsstripe-pt_BR.mo
index fb976f729de9caad260520ace5cc7fa1be9d17d7..17abcb74c13c2701502c635bbb061d65f36b1c15 100644
GIT binary patch
delta 16
XcmZqJ!`QHgaYLISqtWJe!%7_hI2r~e
delta 16
XcmZqJ!`QHgaYLISqv7Uu!%7_hI1~mX
diff --git a/languages/gravityformsstripe-pt_PT.mo b/languages/gravityformsstripe-pt_PT.mo
index 9678ac2e35def117ae2e34075de6aab748c9c175..b6a65b969225881457bce604d2e7889302c0aea5 100644
GIT binary patch
delta 16
XcmaF1oAKFh#tm(Tj7FQ=4dZnHLs\n"
"Language-Team: Gravity Forms \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2021-11-08T11:04:03+00:00\n"
+"POT-Creation-Date: 2022-03-18T13:29:34+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.5.0\n"
"X-Domain: gravityformsstripe\n"
@@ -31,652 +31,726 @@ msgstr ""
msgid "Gravity Forms"
msgstr ""
-#: class-gf-stripe.php:341
+#: class-gf-stripe.php:353
msgid "The credit card field will initiate once the payment condition is met."
msgstr ""
-#: class-gf-stripe.php:342
+#: class-gf-stripe.php:354
msgid "Please follow the instructions on the screen to validate your card."
msgstr ""
-#: class-gf-stripe.php:361
+#: class-gf-stripe.php:373
msgid "Error validating this key. Please try again later."
msgstr ""
-#: class-gf-stripe.php:362
+#: class-gf-stripe.php:374
msgid "In order to switch accounts, you must first save this feed by clicking the \"Update Settings\" button below"
msgstr ""
-#: class-gf-stripe.php:364
+#: class-gf-stripe.php:376
msgid "Are you sure you want to disconnect from Stripe for this website?"
msgstr ""
-#: class-gf-stripe.php:365
+#: class-gf-stripe.php:377
msgid "Are you sure you want to disconnect from Stripe for this feed?"
msgstr ""
-#: class-gf-stripe.php:366
+#: class-gf-stripe.php:378
msgid "Are you sure you want to disconnect all Gravity Forms sites connected to this Stripe account?"
msgstr ""
-#: class-gf-stripe.php:499
-#: class-gf-stripe.php:5731
+#: class-gf-stripe.php:386
+msgid "Are you sure you want to refund this payment?"
+msgstr ""
+
+#: class-gf-stripe.php:388
+msgid "Processing refund"
+msgstr ""
+
+#: class-gf-stripe.php:389
+msgid "Transaction successfully refunded"
+msgstr ""
+
+#: class-gf-stripe.php:391
+msgid "Processing capture"
+msgstr ""
+
+#: class-gf-stripe.php:392
+msgid "Transaction successfully captured"
+msgstr ""
+
+#: class-gf-stripe.php:540
+#: class-gf-stripe.php:5884
msgid "Access denied."
msgstr ""
-#: class-gf-stripe.php:546
-#: class-gf-stripe.php:1667
+#: class-gf-stripe.php:587
+#: class-gf-stripe.php:1760
msgid "Stripe Account"
msgstr ""
-#: class-gf-stripe.php:553
+#: class-gf-stripe.php:594
msgid "Payment Collection"
msgstr ""
-#: class-gf-stripe.php:558
+#: class-gf-stripe.php:599
msgid "Payment Collection Method"
msgstr ""
-#: class-gf-stripe.php:563
+#: class-gf-stripe.php:604
msgid "Stripe Credit Card Field (Elements, SCA-ready)"
msgstr ""
-#: class-gf-stripe.php:565
+#: class-gf-stripe.php:606
msgid "Stripe Credit Card Field (Elements)"
msgstr ""
-#: class-gf-stripe.php:566
+#: class-gf-stripe.php:607
msgid "Select this option to use a Credit Card field hosted by Stripe. This option offers the benefit of a streamlined user interface and the security of having the credit card field hosted on Stripe's servers. Selecting this option or \"Stripe Payment Form\" greatly simplifies the PCI compliance application process with Stripe."
msgstr ""
#. translators: 1. Open link tag 2. Close link tag
-#: class-gf-stripe.php:569
+#: class-gf-stripe.php:610
msgid "Stripe Elements is ready for %1$sStrong Customer Authentication%2$s for European customers."
msgstr ""
-#: class-gf-stripe.php:573
+#: class-gf-stripe.php:614
msgid "Stripe Payment Form (Stripe Checkout, SCA-ready)"
msgstr ""
-#: class-gf-stripe.php:575
+#: class-gf-stripe.php:616
msgid "Stripe Payment Form"
msgstr ""
-#: class-gf-stripe.php:576
+#: class-gf-stripe.php:617
msgid "Select this option to collect all payment information in a separate page hosted by Stripe. This option is the simplest to implement since it doesn't require a credit card field in your form. Selecting this option or \"Stripe Credit Card Field\" greatly simplifies the PCI compliance application process with Stripe."
msgstr ""
#. translators: 1. Open link tag 2. Close link tag
-#: class-gf-stripe.php:579
+#: class-gf-stripe.php:620
msgid "Stripe Checkout also supports Apple Pay and 3D secure, and is ready for %1$sStrong Customer Authentication%2$s for European customers."
msgstr ""
-#: class-gf-stripe.php:618
-#: class-gf-stripe.php:621
-#: class-gf-stripe.php:976
+#: class-gf-stripe.php:659
+#: class-gf-stripe.php:662
+#: class-gf-stripe.php:1017
msgid "Connected to Stripe as"
msgstr ""
-#: class-gf-stripe.php:621
+#: class-gf-stripe.php:662
msgid "The Stripe account this feed is currently connected to."
msgstr ""
-#: class-gf-stripe.php:625
+#: class-gf-stripe.php:666
msgid "Mode"
msgstr ""
-#: class-gf-stripe.php:630
+#: class-gf-stripe.php:671
msgid "Live"
msgstr ""
-#: class-gf-stripe.php:634
+#: class-gf-stripe.php:675
msgid "Test"
msgstr ""
-#: class-gf-stripe.php:676
-#: class-gf-stripe.php:711
+#: class-gf-stripe.php:717
+#: class-gf-stripe.php:752
msgid "Test Publishable Key"
msgstr ""
-#: class-gf-stripe.php:683
-#: class-gf-stripe.php:716
+#: class-gf-stripe.php:724
+#: class-gf-stripe.php:757
msgid "Test Secret Key"
msgstr ""
-#: class-gf-stripe.php:691
-#: class-gf-stripe.php:721
+#: class-gf-stripe.php:732
+#: class-gf-stripe.php:762
msgid "Live Publishable Key"
msgstr ""
-#: class-gf-stripe.php:698
-#: class-gf-stripe.php:726
+#: class-gf-stripe.php:739
+#: class-gf-stripe.php:767
msgid "Live Secret Key"
msgstr ""
-#: class-gf-stripe.php:738
+#: class-gf-stripe.php:779
msgid "Webhooks Enabled?"
msgstr ""
-#: class-gf-stripe.php:746
+#: class-gf-stripe.php:787
msgid "I have enabled the Gravity Forms webhook URL in my Stripe account."
msgstr ""
-#: class-gf-stripe.php:754
+#: class-gf-stripe.php:795
msgid "Test Signing Secret"
msgstr ""
-#: class-gf-stripe.php:763
+#: class-gf-stripe.php:804
msgid "Live Signing Secret"
msgstr ""
-#: class-gf-stripe.php:830
+#: class-gf-stripe.php:871
msgid "SSL Certificate Required"
msgstr ""
#. Translators: 1: Open link tag 2: Close link tag
-#: class-gf-stripe.php:832
+#: class-gf-stripe.php:873
msgid "Make sure you have an SSL certificate installed and enabled, then %1$sclick here to reload the settings page%2$s."
msgstr ""
-#: class-gf-stripe.php:858
+#: class-gf-stripe.php:899
msgid "View Instructions"
msgstr ""
-#: class-gf-stripe.php:863
+#: class-gf-stripe.php:904
msgid "Click the following link and log in to access your Stripe Webhooks management page:"
msgstr ""
-#: class-gf-stripe.php:867
+#: class-gf-stripe.php:908
msgid "Click the \"Add Endpoint\" button above the list of Webhook URLs."
msgstr ""
-#: class-gf-stripe.php:869
+#: class-gf-stripe.php:910
msgid "Enter the following URL in the \"URL to be called\" field:"
msgstr ""
-#: class-gf-stripe.php:872
+#: class-gf-stripe.php:913
msgid "If offered the choice, select the latest API version."
msgstr ""
-#: class-gf-stripe.php:873
+#: class-gf-stripe.php:914
msgid "Click the \"receive all events\" link."
msgstr ""
-#: class-gf-stripe.php:874
+#: class-gf-stripe.php:915
msgid "Click the \"Add Endpoint\" button to save the webhook."
msgstr ""
-#: class-gf-stripe.php:875
+#: class-gf-stripe.php:916
msgid "Copy the signing secret of the newly created webhook on Stripe and paste to the setting field."
msgstr ""
-#: class-gf-stripe.php:893
+#: class-gf-stripe.php:934
msgid "Select how payment information will be collected. You can select one of the Stripe hosted solutions (Stripe Credit Card or Stripe Checkout) which simplifies the PCI compliance process with Stripe."
msgstr ""
#. translators: Placeholders represent opening and closing link tags.
-#: class-gf-stripe.php:898
+#: class-gf-stripe.php:939
msgid "The Gravity Forms Credit Card Field was deprecated in the Stripe Add-On in version 3.4. Forms that are currently using this field will stop working in a future version. Refer to %1$sthis guide%2$s for more information about this change."
msgstr ""
-#: class-gf-stripe.php:947
+#: class-gf-stripe.php:988
msgid "Click here to authenticate with Stripe"
msgstr ""
-#: class-gf-stripe.php:957
+#: class-gf-stripe.php:998
msgid "You are currently logged in to Stripe using a deprecated authentication method."
msgstr ""
#. Translators: 1: Open strong tag 2: Close strong tag
-#: class-gf-stripe.php:959
+#: class-gf-stripe.php:1000
msgid "%1$sPlease login to your Stripe account via Stripe Connect using the button below.%2$s It is a more secure authentication method and will be required for upcoming features of the Stripe Add-on."
msgstr ""
#. translators: Placeholders represent wrapping link tag.
-#: class-gf-stripe.php:964
+#: class-gf-stripe.php:1005
msgid "%1$sLearn more%2$s about connecting with Stripe."
msgstr ""
-#: class-gf-stripe.php:980
-#: class-gf-stripe.php:998
+#: class-gf-stripe.php:1021
+#: class-gf-stripe.php:1039
msgid "Disconnect your Stripe account"
msgstr ""
#. translators: placeholder represents contextual target, either "feed" or "site".
-#: class-gf-stripe.php:993
+#: class-gf-stripe.php:1034
msgid "Disconnect this %s only"
msgstr ""
-#: class-gf-stripe.php:995
+#: class-gf-stripe.php:1036
msgid "Disconnect all Gravity Forms sites connected to this Stripe account"
msgstr ""
-#: class-gf-stripe.php:1103
+#: class-gf-stripe.php:1144
msgid "%1$s%2$s%3$s"
msgstr ""
-#: class-gf-stripe.php:1118
+#: class-gf-stripe.php:1159
msgid "Switch Accounts"
msgstr ""
#. Translators: 1. Open link tag. 2. Close link tag.
-#: class-gf-stripe.php:1134
+#: class-gf-stripe.php:1175
msgid "Logo can be configured on %1$sStripe's branding page%2$s."
msgstr ""
-#: class-gf-stripe.php:1195
+#: class-gf-stripe.php:1236
msgid "Unable to connect to Stripe due to mismatched state."
msgstr ""
-#: class-gf-stripe.php:1242
+#: class-gf-stripe.php:1283
msgid "Unable to authenticate with Stripe."
msgstr ""
+#: class-gf-stripe.php:1317
+msgid "Capture Payment"
+msgstr ""
+
+#: class-gf-stripe.php:1318
+msgid "Capturing payment"
+msgstr ""
+
#. Translators: 1: Open link tag 2: Close link tag
-#: class-gf-stripe.php:1307
+#: class-gf-stripe.php:1400
msgid "You are currently logged in to Stripe using a deprecated authentication method. %1$sRe-authenticate your Stripe account%2$s."
msgstr ""
#. translators: 1: Open strong tag, 2: Close strong tag, 3: Form title, 4: Open link tag, 5: Close link tag.
-#: class-gf-stripe.php:1379
+#: class-gf-stripe.php:1472
msgid "%1$sImportant%2$s: The form %3$s is using a deprecated payment collection method for Stripe that will stop working in a future version. Take action now to continue collecting payments. %4$sLearn more.%5$s"
msgstr ""
-#: class-gf-stripe.php:1488
+#: class-gf-stripe.php:1581
msgid "You must add a Stripe Card field to your form before creating a feed. Let's go %sadd one%s!"
msgstr ""
-#: class-gf-stripe.php:1515
+#: class-gf-stripe.php:1608
msgid "Customer Information"
msgstr ""
-#: class-gf-stripe.php:1524
-#: class-gf-stripe.php:1527
+#: class-gf-stripe.php:1617
+#: class-gf-stripe.php:1620
msgid "Email"
msgstr ""
-#: class-gf-stripe.php:1527
-#: class-gf-stripe.php:1702
+#: class-gf-stripe.php:1620
+#: class-gf-stripe.php:1795
msgid "You can specify an email field and it will be sent to the Stripe Checkout screen as the customer's email."
msgstr ""
-#: class-gf-stripe.php:1531
+#: class-gf-stripe.php:1624
msgid "Description"
msgstr ""
-#: class-gf-stripe.php:1536
-#: class-gf-stripe.php:1539
+#: class-gf-stripe.php:1629
+#: class-gf-stripe.php:1632
msgid "Coupon"
msgstr ""
-#: class-gf-stripe.php:1539
+#: class-gf-stripe.php:1632
msgid "Select which field contains the coupon code to be applied to the recurring charge(s). The coupon must also exist in your Stripe Dashboard."
msgstr ""
-#: class-gf-stripe.php:1539
+#: class-gf-stripe.php:1632
msgid "If you use Stripe Checkout, the coupon won't be applied to your first invoice."
msgstr ""
-#: class-gf-stripe.php:1549
+#: class-gf-stripe.php:1642
msgid "You will see this data when viewing a customer page."
msgstr ""
-#: class-gf-stripe.php:1551
+#: class-gf-stripe.php:1644
msgid "You will see this data when viewing a payment page."
msgstr ""
-#: class-gf-stripe.php:1558
-#: class-gf-stripe.php:1562
+#: class-gf-stripe.php:1651
+#: class-gf-stripe.php:1655
msgid "Metadata"
msgstr ""
-#: class-gf-stripe.php:1562
+#: class-gf-stripe.php:1655
msgid "You may send custom meta information to Stripe. A maximum of 50 custom keys may be sent. The key name must be 40 characters or less, and the mapped data will be truncated to 500 characters per requirements by Stripe. "
msgstr ""
-#: class-gf-stripe.php:1576
+#: class-gf-stripe.php:1669
msgid "Trial"
msgstr ""
-#: class-gf-stripe.php:1579
+#: class-gf-stripe.php:1672
msgid "Enable a trial period. The user's recurring payment will not begin until after this trial period."
msgstr ""
-#: class-gf-stripe.php:1586
+#: class-gf-stripe.php:1679
msgid "Trial Period"
msgstr ""
-#: class-gf-stripe.php:1592
-#: class-gf-stripe.php:1595
+#: class-gf-stripe.php:1685
+#: class-gf-stripe.php:1688
msgid "days"
msgstr ""
-#: class-gf-stripe.php:1603
-#: class-gf-stripe.php:1606
+#: class-gf-stripe.php:1696
+#: class-gf-stripe.php:1699
msgid "Subscription Name"
msgstr ""
-#: class-gf-stripe.php:1606
+#: class-gf-stripe.php:1699
msgid "Enter a name for the subscription. It will be displayed on the payment form as well as the Stripe dashboard."
msgstr ""
-#: class-gf-stripe.php:1616
+#: class-gf-stripe.php:1709
msgid "Stripe Credit Card Field Settings"
msgstr ""
-#: class-gf-stripe.php:1624
-#: class-gf-stripe.php:1625
+#: class-gf-stripe.php:1717
+#: class-gf-stripe.php:1718
msgid "Billing Information"
msgstr ""
-#: class-gf-stripe.php:1625
+#: class-gf-stripe.php:1718
msgid "Map your Form Fields to the available listed fields. The address information will be sent to Stripe."
msgstr ""
-#: class-gf-stripe.php:1637
+#: class-gf-stripe.php:1730
msgid "Stripe Payment Form Settings"
msgstr ""
-#: class-gf-stripe.php:1638
+#: class-gf-stripe.php:1731
msgid "The following settings control information displayed on the Stripe hosted payment page that is displayed after the form is submitted."
msgstr ""
-#: class-gf-stripe.php:1655
-#: class-gf-stripe.php:1657
+#: class-gf-stripe.php:1748
+#: class-gf-stripe.php:1750
msgid "Stripe Receipt"
msgstr ""
-#: class-gf-stripe.php:1657
+#: class-gf-stripe.php:1750
msgid "Stripe can send a receipt via email upon payment. Select an email field to enable this feature."
msgstr ""
-#: class-gf-stripe.php:1691
+#: class-gf-stripe.php:1784
msgid "Logo"
msgstr ""
-#: class-gf-stripe.php:1696
-#: class-gf-stripe.php:1702
+#: class-gf-stripe.php:1789
+#: class-gf-stripe.php:1795
msgid "Customer Email"
msgstr ""
-#: class-gf-stripe.php:1706
-#: class-gf-stripe.php:1708
+#: class-gf-stripe.php:1799
+#: class-gf-stripe.php:1801
msgid "Billing Address"
msgstr ""
-#: class-gf-stripe.php:1708
+#: class-gf-stripe.php:1801
msgid "When enabled, Stripe Checkout will collect the customer's billing address for you."
msgstr ""
-#: class-gf-stripe.php:1712
-#: class-gf-stripe.php:1905
-#: class-gf-stripe.php:1968
+#: class-gf-stripe.php:1805
+#: class-gf-stripe.php:1998
+#: class-gf-stripe.php:2064
msgid "Enabled"
msgstr ""
-#: class-gf-stripe.php:1716
+#: class-gf-stripe.php:1809
msgid "Disabled"
msgstr ""
-#: class-gf-stripe.php:1838
+#: class-gf-stripe.php:1931
msgid "Do not send receipt"
msgstr ""
-#: class-gf-stripe.php:1844
+#: class-gf-stripe.php:1937
msgid "Do not set customer email"
msgstr ""
-#: class-gf-stripe.php:2058
+#: class-gf-stripe.php:2154
msgid "Please enter a valid number of days."
msgstr ""
-#: class-gf-stripe.php:2112
+#: class-gf-stripe.php:2208
msgid "A field has been mapped to a custom key without a name. Please enter a name for the custom key, remove the metadata item, or return the corresponding drop down to 'Select a Field'."
msgstr ""
-#: class-gf-stripe.php:2115
+#: class-gf-stripe.php:2211
msgid "The name of custom key %s is too long. Please shorten this to 40 characters or less."
msgstr ""
-#: class-gf-stripe.php:2133
+#: class-gf-stripe.php:2229
msgid "Please use the correct webhook signing secret, which should start with \"whsec_\"."
msgstr ""
-#: class-gf-stripe.php:2150
+#: class-gf-stripe.php:2246
msgid "day(s)"
msgstr ""
-#: class-gf-stripe.php:2151
+#: class-gf-stripe.php:2247
msgid "week(s)"
msgstr ""
-#: class-gf-stripe.php:2152
+#: class-gf-stripe.php:2248
msgid "month(s)"
msgstr ""
-#: class-gf-stripe.php:2153
+#: class-gf-stripe.php:2249
msgid "year(s)"
msgstr ""
-#: class-gf-stripe.php:2198
+#: class-gf-stripe.php:2294
msgid "Payment Completed"
msgstr ""
-#: class-gf-stripe.php:2199
+#: class-gf-stripe.php:2295
msgid "Payment Refunded"
msgstr ""
-#: class-gf-stripe.php:2200
+#: class-gf-stripe.php:2296
msgid "Payment Failed"
msgstr ""
-#: class-gf-stripe.php:2201
+#: class-gf-stripe.php:2297
msgid "Subscription Created"
msgstr ""
-#: class-gf-stripe.php:2202
+#: class-gf-stripe.php:2298
msgid "Subscription Canceled"
msgstr ""
-#: class-gf-stripe.php:2203
+#: class-gf-stripe.php:2299
msgid "Subscription Payment Added"
msgstr ""
-#: class-gf-stripe.php:2204
+#: class-gf-stripe.php:2300
msgid "Subscription Payment Failed"
msgstr ""
-#: class-gf-stripe.php:2545
-#: class-gf-stripe.php:2661
-#: class-gf-stripe.php:2702
+#: class-gf-stripe.php:2641
+#: class-gf-stripe.php:2757
+#: class-gf-stripe.php:2798
msgid "Unknown"
msgstr ""
-#: class-gf-stripe.php:2554
+#: class-gf-stripe.php:2650
msgid "Card type (%s) is not supported. Please enter one of the supported credit cards."
msgstr ""
#. Translators: 1. Type of credit card.
-#: class-gf-stripe.php:2706
+#: class-gf-stripe.php:2802
msgid "Card type (%1$s) is not supported. Please enter one of the supported credit cards."
msgstr ""
-#: class-gf-stripe.php:2724
+#: class-gf-stripe.php:2820
msgid "There was a problem with your submission."
msgstr ""
-#: class-gf-stripe.php:2741
-#: class-gf-stripe.php:2766
+#: class-gf-stripe.php:2837
+#: class-gf-stripe.php:2862
msgid "There was a problem with your submission:"
msgstr ""
-#: class-gf-stripe.php:2762
+#: class-gf-stripe.php:2858
msgid "There was a problem with your submission. Please try again later."
msgstr ""
-#: class-gf-stripe.php:2898
-#: class-gf-stripe.php:4573
-msgid "3D Secure authentication is required for this payment. Please follow the instructions on the page to continue."
+#: class-gf-stripe.php:2972
+#: class-gf-stripe.php:3015
+#: class-gf-stripe.php:3955
+#: class-gf-stripe.php:4710
+msgid "Your payment attempt has failed. Please enter your card details and try again."
msgstr ""
-#: class-gf-stripe.php:2902
-#: class-gf-stripe.php:3842
-#: class-gf-stripe.php:4562
-msgid "Your payment attempt has failed. Please enter your card details and try again."
+#: class-gf-stripe.php:3011
+#: class-gf-stripe.php:4721
+msgid "3D Secure authentication is required for this payment. Please follow the instructions on the page to continue."
msgstr ""
-#: class-gf-stripe.php:2904
+#: class-gf-stripe.php:3017
msgid "The payment has been canceled"
msgstr ""
-#: class-gf-stripe.php:3040
+#: class-gf-stripe.php:3153
msgid "Payment with Discounts"
msgstr ""
-#: class-gf-stripe.php:3100
+#: class-gf-stripe.php:3213
msgid "Setup Fee"
msgstr ""
-#: class-gf-stripe.php:3179
+#: class-gf-stripe.php:3292
msgid "Unable to create Stripe Checkout session."
msgstr ""
-#: class-gf-stripe.php:3493
+#: class-gf-stripe.php:3606
msgid "Cannot get payment intent data."
msgstr ""
-#: class-gf-stripe.php:3511
+#: class-gf-stripe.php:3624
msgid "Cannot update payment intent data."
msgstr ""
-#: class-gf-stripe.php:3527
+#: class-gf-stripe.php:3640
msgid "Cannot capture payment intent data."
msgstr ""
-#: class-gf-stripe.php:3532
+#: class-gf-stripe.php:3645
msgid "Cannot capture the payment; the payment intent status is "
msgstr ""
-#: class-gf-stripe.php:3549
-#: class-gf-stripe.php:3584
+#: class-gf-stripe.php:3662
+#: class-gf-stripe.php:3697
msgid "Unable to capture the charge."
msgstr ""
-#: class-gf-stripe.php:3568
+#: class-gf-stripe.php:3681
msgid "Unable to save the charge."
msgstr ""
-#: class-gf-stripe.php:3634
+#: class-gf-stripe.php:3747
msgid "Failed to get the customer ID from Stripe."
msgstr ""
-#: class-gf-stripe.php:4094
-#: class-gf-stripe.php:5076
+#: class-gf-stripe.php:4207
+#: class-gf-stripe.php:5229
msgid "Subscription payment has been paid. Amount: %s. Subscription Id: %s"
msgstr ""
-#: class-gf-stripe.php:4732
+#: class-gf-stripe.php:4880
msgid "Unable to authorize card. No response from Stripe.js."
msgstr ""
-#: class-gf-stripe.php:5040
-#: class-gf-stripe.php:5113
+#: class-gf-stripe.php:5193
+#: class-gf-stripe.php:5266
msgid "Subscription line item not found in request"
msgstr ""
-#: class-gf-stripe.php:5228
+#: class-gf-stripe.php:5381
msgid "Entry %d was not processed by feed %d. Webhook cannot be processed."
msgstr ""
-#: class-gf-stripe.php:5245
+#: class-gf-stripe.php:5398
msgid "Entry for %s id: %s was not found. Webhook cannot be processed."
msgstr ""
-#: class-gf-stripe.php:5309
+#: class-gf-stripe.php:5462
msgid "Invalid request. Webhook could not be processed."
msgstr ""
-#: class-gf-stripe.php:5315
+#: class-gf-stripe.php:5468
msgid "Test webhook succeeded. Your Stripe Account and Stripe Add-On are configured correctly to process webhooks."
msgstr ""
-#: class-gf-stripe.php:5811
+#: class-gf-stripe.php:5964
msgid "Setup fee has been paid."
msgstr ""
-#: class-gf-stripe.php:5813
+#: class-gf-stripe.php:5966
msgid "Trial has been paid."
msgstr ""
-#: class-gf-stripe.php:6008
+#: class-gf-stripe.php:6161
msgid "You're being redirected to the hosted Checkout page on Stripe..."
msgstr ""
-#: class-gf-stripe.php:6071
+#: class-gf-stripe.php:6224
msgid "Entry ID: %d"
msgstr ""
-#: class-gf-stripe.php:6075
+#: class-gf-stripe.php:6228
msgid "Product: %s"
msgid_plural "Products: %s"
msgstr[0] ""
msgstr[1] ""
-#: class-gf-stripe.php:6241
+#: class-gf-stripe.php:6394
msgid "Address"
msgstr ""
-#: class-gf-stripe.php:6247
+#: class-gf-stripe.php:6400
msgid "Address 2"
msgstr ""
-#: class-gf-stripe.php:6253
+#: class-gf-stripe.php:6406
msgid "City"
msgstr ""
-#: class-gf-stripe.php:6259
+#: class-gf-stripe.php:6412
msgid "State"
msgstr ""
-#: class-gf-stripe.php:6265
+#: class-gf-stripe.php:6418
msgid "Zip"
msgstr ""
-#: class-gf-stripe.php:6271
+#: class-gf-stripe.php:6424
msgid "Country"
msgstr ""
-#: class-gf-stripe.php:6398
+#: class-gf-stripe.php:6551
msgid "%1$sYour Gravity Forms Stripe Add-On has been updated to 3.0, and now supports Apple Pay and Strong Customer Authentication (SCA/PSD2).%2$s%3$sNOTE:%4$s Stripe has changed Stripe Checkout from a modal display to a full page, and we have altered some existing Stripe hooks. Carefully review %5$sthis guide%6$s to see if your setup may be affected.%7$s"
msgstr ""
-#: class-gf-stripe.php:6411
+#: class-gf-stripe.php:6564
msgid "%1$sYour Gravity Forms Stripe Add-On has been updated to 3.0, and now supports Apple Pay and Strong Customer Authentication (SCA/PSD2).%2$s%3$sNOTE:%4$s Apple Pay and SCA are only supported by the Stripe Checkout payment collection method. Refer to %5$sthis guide%6$s for more information on payment methods and SCA.%7$s"
msgstr ""
-#: class-gf-stripe.php:6501
+#: class-gf-stripe.php:6654
msgid "%1$sYour Gravity Forms Stripe Add-On has been updated to 3.4, and now supports Strong Customer Authentication (SCA/PSD2).%2$s%3$sRefer to %4$sthis guide%5$s for more information on payment methods and SCA.%6$s"
msgstr ""
-#: class-gf-stripe.php:6512
+#: class-gf-stripe.php:6665
msgid "%1$sYour Gravity Forms Stripe Add-On has been updated to 3.4, and it no longer supports the Gravity Forms Credit Card Field in new forms (current integrations can still work as usual).%2$s%3$sRefer to %4$sthis guide%5$s for more information about this change.%6$s"
msgstr ""
#. translators: PaymentIntent status.
-#. translators: PaymentIntent status.
-#: class-gf-stripe.php:6603
-#: class-gf-stripe.php:6648
+#: class-gf-stripe.php:6756
+#: class-gf-stripe.php:6801
msgid "PaymentIntent status: %s is invalid."
msgstr ""
-#: class-gf-stripe.php:6824
+#: class-gf-stripe.php:6821
+#: class-gf-stripe.php:6906
+msgid "Unable to find entry."
+msgstr ""
+
+#: class-gf-stripe.php:6828
+#: class-gf-stripe.php:6913
+msgid "Unable to find payment feed."
+msgstr ""
+
+#: class-gf-stripe.php:6834
+msgid "Could not initialize Stripe API."
+msgstr ""
+
+#: class-gf-stripe.php:6931
+msgid "Unable to find payment on Stripe"
+msgstr ""
+
+#: class-gf-stripe.php:7105
msgid "We are not able to process your payment request at the moment. Please try again later."
msgstr ""
-#: class-gf-stripe.php:6858
+#: class-gf-stripe.php:7139
msgid "Unnamed account"
msgstr ""
+#: class-gf-stripe.php:7164
+msgid "Could not retrieve payment information from Stripe."
+msgstr ""
+
+#: class-gf-stripe.php:7172
+msgid "Payment has already been captured."
+msgstr ""
+
+#: class-gf-stripe.php:7183
+msgid "Unable to capture payment"
+msgstr ""
+
+#: class-gf-stripe.php:7194
+msgid "Payment captured successfully."
+msgstr ""
+
+#: class-gf-stripe.php:7241
+msgid "Feed associated with entry could not be found."
+msgstr ""
+
+#: class-gf-stripe.php:7276
+msgid "Refund Payment"
+msgstr ""
+
+#: class-gf-stripe.php:7277
+msgid "Sending refund request"
+msgstr ""
+
#: includes/class-gf-field-stripe-creditcard.php:33
msgid "Stripe Card"
msgstr ""
@@ -722,3 +796,53 @@ msgstr ""
#: includes/class-gf-field-stripe-creditcard.php:480
msgid "This page is unsecured. Do not enter a real credit card number! Use this field only for testing purposes. "
msgstr ""
+
+#: includes/class-gf-stripe-api.php:899
+msgid "The Stripe API Client is missing. The theme or another plugin is including an older or incomplete version of the Stripe PHP SDK."
+msgstr ""
+
+#: includes/class-gf-stripe-api.php:902
+msgid "The Stripe API Client is not initialized. Please connect the add-on to a Stripe account."
+msgstr ""
+
+#: includes/class-gf-stripe-billing-portal.php:207
+msgid "You don't have any subscriptions."
+msgstr ""
+
+#: includes/class-gf-stripe-billing-portal.php:274
+msgid "There was an error generating your Customer Portal link."
+msgstr ""
+
+#: includes/class-gf-stripe-billing-portal.php:464
+msgid "Subscription"
+msgstr ""
+
+#. Translators: 1: Subscription end date
+#: includes/class-gf-stripe-billing-portal.php:478
+msgid "until %s"
+msgstr ""
+
+#. Translators: 1: subscription first billing period date after trial period is over
+#: includes/class-gf-stripe-billing-portal.php:483
+msgid "starting from %s"
+msgstr ""
+
+#: includes/views/subscription-information.php:13
+msgid "The following errors occurred."
+msgstr ""
+
+#: includes/views/subscription-information.php:82
+msgid "Status"
+msgstr ""
+
+#: includes/views/subscription-information.php:86
+msgid "Start Date"
+msgstr ""
+
+#: includes/views/subscription-information.php:96
+msgid "per"
+msgstr ""
+
+#: includes/views/subscription-information.php:113
+msgid "Manage subscription"
+msgstr ""
diff --git a/stripe.php b/stripe.php
index cbca051..0db6a94 100644
--- a/stripe.php
+++ b/stripe.php
@@ -3,7 +3,7 @@
* Plugin Name: Gravity Forms Stripe Add-On
* Plugin URI: https://gravityforms.com
* Description: Integrates Gravity Forms with Stripe, enabling end users to purchase goods and services through Gravity Forms.
- * Version: 4.1.1
+ * Version: 4.2.1
* Author: Gravity Forms
* Author URI: https://gravityforms.com
* License: GPL-2.0+
@@ -11,7 +11,7 @@
* Domain Path: /languages
*
* ------------------------------------------------------------------------
- * Copyright 2009 - 2021 Rocketgenius, Inc.
+ * Copyright 2009 - 2022 Rocketgenius, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,7 +30,7 @@
defined( 'ABSPATH' ) || die();
-define( 'GF_STRIPE_VERSION', '4.1.1' );
+define( 'GF_STRIPE_VERSION', '4.2.1' );
// If Gravity Forms is loaded, bootstrap the Stripe Add-On.
add_action( 'gform_loaded', array( 'GF_Stripe_Bootstrap', 'load' ), 5 );