You are here

commerce_stripe_pi.module in Commerce Stripe Payment Intent 7

Payment intent stripe payment integration.

This module provides Stripe (http://stripe.com/) payment gateway integration to Commerce. Commerce Stripe offers a PCI-compliant way to process payments straight from you Commerce shop.

File

commerce_stripe_pi.module
View source
<?php

/**
 * @file
 * Payment intent stripe payment integration.
 *
 * This module provides Stripe (http://stripe.com/) payment gateway integration
 * to Commerce. Commerce Stripe offers a PCI-compliant way to process payments
 * straight from you Commerce shop.
 */
use Stripe\Stripe;
use Stripe\PaymentIntent;
use Stripe\PaymentMethod;
use Stripe\SetupIntent;
use Stripe\Customer;
use Stripe\WebhookEndpoint;
use Stripe\Event;
use Stripe\Error\InvalidRequest;
define('COMMERCE_STRIPE_PI_DEFAULT_INTEGRATION', 'elements');
define('COMMERCE_STRIPE_PI_API_LATEST_TESTED', '2019-07-11');
define('COMMERCE_STRIPE_PI_API_ACCOUNT_DEFAULT', 'Account Default');
define('COMMERCE_STRIPE_PI_API_VERSION_CUSTOM', 'Custom');
define('COMMERCE_STRIPE_PI_JS', 'https://js.stripe.com/v3');
define('COMMERCE_STRIPE_PI_SUCCEEDED', 'succeeded');
define('COMMERCE_STRIPE_PI_REQUIRES_CAPTURE', 'requires_capture');

/**
 * Implements hook_menu().
 */
function commerce_stripe_pi_menu() {
  $items = array();

  // Add a menu item to stripe payment transactions that can be refunded.
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/commerce-stripe-pi-refund'] = array(
    'title' => 'Refund',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_stripe_pi_refund_form',
      3,
      5,
    ),
    'access callback' => 'commerce_stripe_pi_return_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 1,
    'file' => 'includes/commerce_stripe_pi.admin.inc',
  );
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/stripe-pi-capture'] = array(
    'title' => 'Capture',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_stripe_pi_capture_form',
      3,
      5,
    ),
    'access callback' => 'commerce_stripe_pi_capture_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
    'file' => 'includes/commerce_stripe_pi.admin.inc',
  );
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/stripe-pi-void'] = array(
    'title' => 'Void',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_stripe_pi_void_form',
      3,
      5,
    ),
    'access callback' => 'commerce_stripe_pi_void_access',
    'access arguments' => array(
      3,
      5,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'weight' => 2,
    'file' => 'includes/commerce_stripe_pi.admin.inc',
  );
  $items['commerce_stripe_pi/webhook'] = array(
    'page callback' => 'commerce_stripe_pi_webhook',
    'page arguments' => array(
      2,
    ),
    // This will be called by external server, so there is no restrictions.
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Get Stripe async response after payment intent.
 */
function commerce_stripe_pi_webhook() {
  commerce_stripe_pi_load_library();
  $payload = @file_get_contents('php://input');
  $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
  $payment_method = commerce_payment_method_instance_load('commerce_stripe_pi|commerce_payment_commerce_stripe_pi');
  $endpoint_secret = $payment_method['settings']['webhooks']['secret'];
  $event = NULL;
  try {
    if (empty($endpoint_secret)) {
      watchdog('commerce_stripe_pi', 'Webhook: Secret signature is not defined, please get the secret key from stripe dashboard and update your stripe pi payment settings for security reasons.', array(), WATCHDOG_WARNING);
      $event = Event::constructFrom(json_decode($payload, TRUE));
    }
    else {
      $event = \Stripe\Webhook::constructEvent($payload, $sig_header, $endpoint_secret);
    }
  } catch (\UnexpectedValueException $e) {
    watchdog('commerce_stripe_pi', 'Webhook: Invalid payload %payload', array(
      '%payload' => $payload,
    ), WATCHDOG_ERROR);

    // Invalid payload.
    http_response_code(400);
    drupal_exit();
  } catch (\Stripe\Exception\SignatureVerificationException $e) {
    watchdog('commerce_stripe_pi', 'Webhook: Invalid signature %signature for payload %payload', array(
      '%signature' => $sig_header,
      '%payload' => $payload,
    ), WATCHDOG_ERROR);

    // Invalid signature
    http_response_code(400);
    drupal_exit();
  }
  $unexpected = FALSE;

  // Handle the event.
  switch ($event->type) {
    case 'payment_intent.succeeded':
    case 'payment_intent.payment_failed':
      watchdog('commerce_stripe_pi', 'Webhook: Handle event type `%type` for order `%order` with event : %event', array(
        '%type' => $event->type,
        '%order' => isset($event->data->object->metadata['order_id']) ? $event->data->object->metadata['order_id'] : '',
        '%event' => print_r($event, TRUE),
      ), WATCHDOG_DEBUG);

      // Contains a \Stripe\PaymentIntent.
      $paymentIntent = $event->data->object;

      // Get order id from metadata.
      $order_id = $paymentIntent->metadata['order_id'];
      commerce_stripe_pi_create_transaction($order_id, $paymentIntent);
      break;
    default:

      // Unexpected event type.
      $unexpected = TRUE;
  }
  drupal_alter('commerce_stripe_pi_webhook', $event, $unexpected);
  if ($unexpected) {
    watchdog('commerce_stripe_pi', 'Webhook: Unexpected event type `%type` with event : %event', array(
      '%type' => $event->type,
      '%event' => print_r($event, TRUE),
    ), WATCHDOG_DEBUG);
    http_response_code(400);
    drupal_exit();
  }
}

/**
 * Implements hook_libraries_info().
 */
function commerce_stripe_pi_libraries_info() {
  return array(
    'stripe-php' => array(
      'name' => 'Stripe API Client Library for PHP',
      'vendor url' => 'https://stripe.com/',
      'download url' => 'https://github.com/stripe/stripe-php',
      'dependencies' => array(),
      'version arguments' => array(
        'file' => 'VERSION',
        'pattern' => '/((\\d+)\\.(\\d+)\\.(\\d+))/',
        'lines' => 1,
        'cols' => 12,
      ),
      'files' => array(
        'php' => array(
          'init.php',
        ),
      ),
      'callbacks' => array(
        'post-load' => array(
          'commerce_stripe_pi_libraries_postload_callback',
        ),
      ),
    ),
  );
}

/**
 * Post-load callback for the Stripe PHP Library.
 *
 * @param array $library
 *   An array of library information.
 * @param string $version
 *   If the $library array belongs to a certain version, a string containing the
 *   version.
 * @param string $variant
 *   If the $library array belongs to a certain variant, a string containing the
 *   variant name.
 */
function commerce_stripe_pi_libraries_postload_callback(array $library, $version = NULL, $variant = NULL) {
  if (!empty($library['loaded'])) {

    // @todo: Make this a global configuration, since merchants will only have one API key.
    $payment_method = commerce_payment_method_instance_load('commerce_stripe_pi|commerce_payment_commerce_stripe_pi');
    Stripe::setApiKey(trim($payment_method['settings']['secret_key']));

    // If configured to, set the API Version for all requests.
    // Because the default is the version configured in the Stripe
    // Account dashboard, we only set the version if something else
    // has been configured by an administrator.
    $api_version = $payment_method['settings']['commerce_stripe_pi_api_version'];
    if ($api_version !== COMMERCE_STRIPE_PI_API_ACCOUNT_DEFAULT) {
      if ($api_version === COMMERCE_STRIPE_PI_API_VERSION_CUSTOM) {
        $api_version = check_plain($payment_method['settings']['commerce_stripe_pi_api_version_custom']);
      }
      try {
        Stripe::setApiVersion($api_version);
      } catch (InvalidRequest $e) {
        watchdog('stripe_pi', 'Stripe setApiVersion Exception: %error', array(
          '%error' => $e
            ->getMessage(),
        ), WATCHDOG_ERROR);
        drupal_set_message(t('Stripe API Error: @error', array(
          '@error' => $e
            ->getMessage(),
        )), 'error');
      }
    }
  }
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_stripe_pi_commerce_payment_method_info() {
  $payment_methods = array();
  $cardonfile = FALSE;
  $payment_method_rule = rules_config_load('commerce_payment_commerce_stripe_pi');
  if ($payment_method_rule && $payment_method_rule->active) {
    foreach ($payment_method_rule
      ->actions() as $action) {

      // Skip any actions that are not simple rules actions. (i.e. loops)
      if (!$action instanceof RulesAction) {
        continue;
      }
      if (!empty($action->settings['payment_method']['method_id']) && $action->settings['payment_method']['method_id'] === 'commerce_stripe_pi') {
        $cardonfile = !empty($action->settings['payment_method']['settings']['cardonfile']) ? TRUE : FALSE;
        break;
      }
    }
  }
  $payment_methods['commerce_stripe_pi'] = array(
    'title' => t('Stripe_pi'),
    'short_title' => t('Stripe_pi'),
    'display_title' => t('Credit card'),
    'description' => t('Stripe payment gateway'),
    'active' => FALSE,
    'terminal' => TRUE,
    'offsite' => FALSE,
  );

  // Set the cardonfile settings.  We check that the administrator has enabled
  // cardonfile functionality for commerce_stripe_pi; if not, we do not add the
  // cardonfile callbacks that would otherwise be called.
  if ($cardonfile) {

    // Allow charging and deleting saved cards for any Stripe integration
    // method.
    $payment_methods['commerce_stripe_pi']['cardonfile'] = array(
      'charge callback' => 'commerce_stripe_pi_cardonfile_charge',
      'delete callback' => 'commerce_stripe_pi_cardonfile_delete',
      'create form callback' => 'commerce_stripe_pi_cardonfile_create_form',
      'create callback' => 'commerce_stripe_pi_cardonfile_create',
      'update form callback' => 'commerce_stripe_pi_cardonfile_update_form',
      'update callback' => 'commerce_stripe_pi_cardonfile_update',
    );
  }
  return $payment_methods;
}

/**
 * Access callback for processing returns.
 */
function commerce_stripe_pi_return_access($order, $transaction) {

  // Don't allow refunds on non-stripe transactions.
  if ($transaction->payment_method !== 'commerce_stripe_pi') {
    return FALSE;
  }

  // Don't allow refunds on fully refunded transactions.
  if (!empty($transaction->data['stripe']['stripe_charge']['amount_refunded'])) {
    if ($transaction->data['stripe']['stripe_charge']['amount_refunded'] >= $transaction->amount) {
      return FALSE;
    }
  }

  // Only allow refunds on original charge transactions.
  if (!empty($transaction->data['stripe']['stripe_refund'])) {
    return FALSE;
  }

  // Don't allow refunds on AUTH_ONLY transactions.
  if (isset($transaction->remote_status) && $transaction->remote_status === 'AUTH_ONLY') {
    return FALSE;
  }
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Payment method settings form.
 *
 * @param array $settings
 *   Default settings provided from rules.
 *
 * @return array
 *   Settings form array
 */
function commerce_stripe_pi_settings_form(array $settings) {
  $form = array();
  $currencies = commerce_currencies(TRUE);
  $stripe_pi_currency_list = array();
  foreach ($currencies as $currency_code => $currency) {
    $stripe_pi_currency_list[$currency_code] = $currency['name'];
  }
  $form['stripe_pi_currency'] = array(
    '#type' => 'select',
    '#title' => t('Currency'),
    '#options' => $stripe_pi_currency_list,
    '#description' => t('Select the currency that you are using.'),
    '#default_value' => !empty($settings['stripe_pi_currency']) ? $settings['stripe_pi_currency'] : 'USD',
  );
  $form['stripe_pi_charge_mode'] = array(
    '#type' => 'radios',
    '#title' => t('Charge with'),
    '#options' => array(
      'account' => t('Direct account credentials'),
      'platform' => t('Stripe Connect platform'),
    ),
    '#description' => t('Use account if you only have one account. Use Connect if you are billing through the multicustomer platform.'),
    '#default_value' => !empty($settings['stripe_pi_charge_mode']) ? $settings['stripe_pi_charge_mode'] : 'account',
  );
  $form['secret_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Secret Key'),
    '#description' => t('Secret API Key. Get your key from https://stripe.com/'),
    '#default_value' => !empty($settings['secret_key']) ? $settings['secret_key'] : '',
    '#required' => TRUE,
  );
  $form['public_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Publishable Key'),
    '#description' => t('Publishable API Key. Get your key from https://stripe.com/'),
    '#default_value' => !empty($settings['public_key']) ? $settings['public_key'] : '',
    '#required' => TRUE,
  );
  $form['platform_key'] = array(
    '#type' => 'textfield',
    '#title' => t('Platform Key'),
    '#description' => t('If you are using Connect, get your key from your dashboard.'),
    '#default_value' => !empty($settings['platform_key']) ? $settings['platform_key'] : '',
  );
  $form['display_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Payment method display title'),
    '#description' => t('Payment method display title'),
    '#default_value' => !empty($settings['display_title']) ? $settings['display_title'] : t('Stripe_pi'),
  );
  $form['integration_type'] = array(
    '#type' => 'select',
    '#title' => t('Integration type'),
    '#description' => t('Choose Stripe integration method: Elements makes it easy to collect credit card (and other similarly sensitive) details without having the information touch your server.'),
    '#options' => array(
      'elements' => t('Elements'),
    ),
    '#default_value' => !empty($settings['integration_type']) ? $settings['integration_type'] : COMMERCE_STRIPE_PI_DEFAULT_INTEGRATION,
  );
  $form['elements_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('These settings are specific to "elements" integration type.'),
    '#states' => array(
      'visible' => array(
        ':input[name$="[integration_type]"]' => array(
          'value' => 'elements',
        ),
      ),
    ),
  );

  // Postal Code.
  $form['elements_settings']['hide_postal_code'] = array(
    '#type' => 'checkbox',
    '#title' => t('Hide Postal code'),
    '#description' => t('If enabled, postal code field will be hidden from form elements.'),
    '#default_value' => !empty($settings['elements_settings']['hide_postal_code']) ? $settings['elements_settings']['hide_postal_code'] : FALSE,
  );
  if (module_exists('commerce_cardonfile')) {
    $form['cardonfile'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable Card on File functionality.'),
      '#default_value' => isset($settings['cardonfile']) ? $settings['cardonfile'] : 0,
    );
  }
  else {
    $form['cardonfile'] = array(
      '#type' => 'markup',
      '#markup' => t('To enable Card on File functionality, download and install the Commerce Card on File module.'),
    );
  }
  $form['txn_type'] = array(
    '#type' => 'radios',
    '#title' => t('Default credit card transaction type'),
    '#description' => t('The default will be used to process transactions during checkout.'),
    '#options' => array(
      COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'),
      COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual capture after checkout)'),
    ),
    '#default_value' => isset($settings['txn_type']) ? $settings['txn_type'] : COMMERCE_CREDIT_AUTH_CAPTURE,
  );
  $form['commerce_stripe_pi_api_version'] = array(
    '#type' => 'select',
    '#title' => t('Stripe API Version'),
    '#options' => array(
      COMMERCE_STRIPE_PI_API_LATEST_TESTED => t('Latest Tested (2019-07-11)'),
      COMMERCE_STRIPE_PI_API_ACCOUNT_DEFAULT => t('Account Default'),
      COMMERCE_STRIPE_PI_API_VERSION_CUSTOM => t('Custom'),
    ),
    '#empty_option' => COMMERCE_STRIPE_PI_API_ACCOUNT_DEFAULT,
    '#empty_value' => 'Account Default',
    '#default_value' => $settings['commerce_stripe_pi_api_version'],
    '#description' => t('Specify the API version to use for requests.
    Defaults to the version configured in your <a href="@dash">Stripe Account</a>.', array(
      '@dash' => 'http://dashboard.stripe.com/account/apikeys',
    )),
  );
  $form['commerce_stripe_pi_api_version_custom'] = array(
    '#type' => 'textfield',
    '#title' => t('Specify an API Version'),
    '#description' => t('Useful for testing API Versioning before committing to an upgrade. See the <a href="@docs">API Docs</a> and your <a href="@changelog">API Changelog</a>.', array(
      '@docs' => 'https://stripe.com/docs/upgrades',
      '@changelog' => 'https://stripe.com/docs/upgrades#api-changelog',
    )),
    '#default_value' => !empty($settings['commerce_stripe_pi_api_version_custom']) ? $settings['commerce_stripe_pi_api_version_custom'] : '',
    '#size' => 12,
    '#states' => array(
      'visible' => array(
        ':input[name$="[commerce_stripe_pi_api_version]"]' => array(
          'value' => COMMERCE_STRIPE_PI_API_VERSION_CUSTOM,
        ),
      ),
    ),
  );
  $form['webhooks'] = array(
    '#type' => 'fieldset',
    '#title' => t('Webhooks settings.'),
  );
  $form['webhooks']['url'] = array(
    '#type' => 'textfield',
    '#title' => t('Webhooks Url'),
    '#default_value' => !empty($settings['webhooks']['url']) ? $settings['webhooks']['url'] : '',
    '#description' => t('Define url to access to you webhook. Ex: http://mysite.com'),
    '#suffix' => '<label>' . t('Webhook url to configure in Stripe (reload after saving) :') . '</label><code>' . (!empty($settings['webhooks']['url']) ? $settings['webhooks']['url'] : '') . '/commerce_stripe_pi/webhook</code>',
  );
  $form['webhooks']['events'] = array(
    '#type' => 'select',
    '#multiple' => TRUE,
    '#options' => array(
      'account.updated' => t('account.updated'),
      'account.application.authorized' => t('account.application.authorized'),
      'account.application.deauthorized' => t('account.application.deauthorized'),
      'account.external_account.created' => t('account.external_account.created'),
      'account.external_account.deleted' => t('account.external_account.deleted'),
      'account.external_account.updated' => t('account.external_account.updated'),
      'application_fee.created' => t('application_fee.created'),
      'application_fee.refunded' => t('application_fee.refunded'),
      'application_fee.refund.updated' => t('application_fee.refund.updated'),
      'balance.available' => t('balance.available'),
      'capability.updated' => t('capability.updated'),
      'charge.captured' => t('charge.captured'),
      'charge.expired' => t('charge.expired'),
      'charge.failed' => t('charge.failed'),
      'charge.pending' => t('charge.pending'),
      'charge.refunded' => t('charge.refunded'),
      'charge.succeeded' => t('charge.succeeded'),
      'charge.updated' => t('charge.updated'),
      'charge.dispute.closed' => t('charge.dispute.closed'),
      'charge.dispute.created' => t('charge.dispute.created'),
      'charge.dispute.funds_reinstated' => t('charge.dispute.funds_reinstated'),
      'charge.dispute.funds_withdrawn' => t('charge.dispute.funds_withdrawn'),
      'charge.dispute.updated' => t('charge.dispute.updated'),
      'charge.refund.updated' => t('charge.refund.updated'),
      'checkout.session.completed' => t('checkout.session.completed'),
      'coupon.created' => t('coupon.created'),
      'coupon.deleted' => t('coupon.deleted'),
      'coupon.updated' => t('coupon.updated'),
      'credit_note.created' => t('credit_note.created'),
      'credit_note.updated' => t('credit_note.updated'),
      'credit_note.voided' => t('credit_note.voided'),
      'customer.created' => t('customer.created'),
      'customer.deleted' => t('customer.deleted'),
      'customer.updated' => t('customer.updated'),
      'customer.discount.created' => t('customer.discount.created'),
      'customer.discount.deleted' => t('customer.discount.deleted'),
      'customer.discount.updated' => t('customer.discount.updated'),
      'customer.source.created' => t('customer.source.created'),
      'customer.source.deleted' => t('customer.source.deleted'),
      'customer.source.expiring' => t('customer.source.expiring'),
      'customer.source.updated' => t('customer.source.updated'),
      'customer.subscription.created' => t('customer.subscription.created'),
      'customer.subscription.deleted' => t('customer.subscription.deleted'),
      'customer.subscription.trial_will_end' => t('customer.subscription.trial_will_end'),
      'customer.subscription.updated' => t('customer.subscription.updated'),
      'customer.tax_id.created' => t('customer.tax_id.created'),
      'customer.tax_id.deleted' => t('customer.tax_id.deleted'),
      'customer.tax_id.updated' => t('customer.tax_id.updated'),
      'file.created' => t('file.created'),
      'invoice.created' => t('invoice.created'),
      'invoice.deleted' => t('invoice.deleted'),
      'invoice.finalized' => t('invoice.finalized'),
      'invoice.marked_uncollectible' => t('invoice.marked_uncollectible'),
      'invoice.payment_action_required' => t('invoice.payment_action_required'),
      'invoice.payment_failed' => t('invoice.payment_failed'),
      'invoice.payment_succeeded' => t('invoice.payment_succeeded'),
      'invoice.sent' => t('invoice.sent'),
      'invoice.upcoming' => t('invoice.upcoming'),
      'invoice.updated' => t('invoice.updated'),
      'invoice.voided' => t('invoice.voided'),
      'invoiceitem.created' => t('invoiceitem.created'),
      'invoiceitem.deleted' => t('invoiceitem.deleted'),
      'invoiceitem.updated' => t('invoiceitem.updated'),
      'issuing_authorization.created' => t('issuing_authorization.created'),
      'issuing_authorization.request' => t('issuing_authorization.request'),
      'issuing_authorization.updated' => t('issuing_authorization.updated'),
      'issuing_card.created' => t('issuing_card.created'),
      'issuing_card.updated' => t('issuing_card.updated'),
      'issuing_cardholder.created' => t('issuing_cardholder.created'),
      'issuing_cardholder.updated' => t('issuing_cardholder.updated'),
      'issuing_dispute.created' => t('issuing_dispute.created'),
      'issuing_dispute.updated' => t('issuing_dispute.updated'),
      'issuing_settlement.created' => t('issuing_settlement.created'),
      'issuing_settlement.updated' => t('issuing_settlement.updated'),
      'issuing_transaction.created' => t('issuing_transaction.created'),
      'issuing_transaction.updated' => t('issuing_transaction.updated'),
      'order.created' => t('order.created'),
      'order.payment_failed' => t('order.payment_failed'),
      'order.payment_succeeded' => t('order.payment_succeeded'),
      'order.updated' => t('order.updated'),
      'order_return.created' => t('order_return.created'),
      'payment_intent.amount_capturable_updated' => t('payment_intent.amount_capturable_updated'),
      'payment_intent.created' => t('payment_intent.created'),
      'payment_intent.payment_failed' => t('payment_intent.payment_failed'),
      'payment_intent.succeeded' => t('payment_intent.succeeded'),
      'payment_method.attached' => t('payment_method.attached'),
      'payment_method.card_automatically_updated' => t('payment_method.card_automatically_updated'),
      'payment_method.detached' => t('payment_method.detached'),
      'payment_method.updated' => t('payment_method.updated'),
      'payout.canceled' => t('payout.canceled'),
      'payout.created' => t('payout.created'),
      'payout.failed' => t('payout.failed'),
      'payout.paid' => t('payout.paid'),
      'payout.updated' => t('payout.updated'),
      'person.created' => t('person.created'),
      'person.deleted' => t('person.deleted'),
      'person.updated' => t('person.updated'),
      'plan.created' => t('plan.created'),
      'plan.deleted' => t('plan.deleted'),
      'plan.updated' => t('plan.updated'),
      'product.created' => t('product.created'),
      'product.deleted' => t('product.deleted'),
      'product.updated' => t('product.updated'),
      'radar.early_fraud_warning.created' => t('radar.early_fraud_warning.created'),
      'radar.early_fraud_warning.updated' => t('radar.early_fraud_warning.updated'),
      'recipient.created' => t('recipient.created'),
      'recipient.deleted' => t('recipient.deleted'),
      'recipient.updated' => t('recipient.updated'),
      'reporting.report_run.failed' => t('reporting.report_run.failed'),
      'reporting.report_run.succeeded' => t('reporting.report_run.succeeded'),
      'reporting.report_type.updated' => t('reporting.report_type.updated'),
      'review.closed' => t('review.closed'),
      'review.opened' => t('review.opened'),
      'setup_intent.created' => t('setup_intent.created'),
      'setup_intent.setup_failed' => t('setup_intent.setup_failed'),
      'setup_intent.succeeded' => t('setup_intent.succeeded'),
      'sigma.scheduled_query_run.created' => t('sigma.scheduled_query_run.created'),
      'sku.created' => t('sku.created'),
      'sku.deleted' => t('sku.deleted'),
      'sku.updated' => t('sku.updated'),
      'source.canceled' => t('source.canceled'),
      'source.chargeable' => t('source.chargeable'),
      'source.failed' => t('source.failed'),
      'source.mandate_notification' => t('source.mandate_notification'),
      'source.refund_attributes_required' => t('source.refund_attributes_required'),
      'source.transaction.created' => t('source.transaction.created'),
      'source.transaction.updated' => t('source.transaction.updated'),
      'tax_rate.created' => t('tax_rate.created'),
      'tax_rate.updated' => t('tax_rate.updated'),
      'topup.canceled' => t('topup.canceled'),
      'topup.created' => t('topup.created'),
      'topup.failed' => t('topup.failed'),
      'topup.reversed' => t('topup.reversed'),
      'topup.succeeded' => t('topup.succeeded'),
      'transfer.created' => t('transfer.created'),
      'transfer.failed' => t('transfer.failed'),
      'transfer.paid' => t('transfer.paid'),
      'transfer.reversed' => t('transfer.reversed'),
      'transfer.updated' => t('transfer.updated'),
    ),
    '#title' => t('Webhooks events'),
    '#default_value' => !empty($settings['webhooks']['events']) ? $settings['webhooks']['events'] : array(
      'payment_intent.payment_failed',
      'payment_intent.succeeded',
    ),
    '#description' => t('Define events to watch.'),
  );
  $form['webhooks']['webhook_id'] = array(
    '#type' => 'textfield',
    '#title' => t('Webhook id'),
    '#default_value' => !empty($settings['webhooks']['webhook_id']) ? $settings['webhooks']['webhook_id'] : '',
  );
  $form['webhooks']['secret'] = array(
    '#type' => 'textfield',
    '#title' => t('Webhook secret'),
    '#default_value' => !empty($settings['webhooks']['secret']) ? $settings['webhooks']['secret'] : '',
  );
  return $form;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function commerce_stripe_pi_form_rules_ui_edit_element_alter(&$form, &$form_state) {
  if (method_exists($form_state['rules_element'], 'getElementName') && $form_state['rules_element']
    ->getElementName() === 'commerce_payment_enable_commerce_stripe_pi') {
    $form['#validate'][] = 'commerce_stripe_pi_form_rules_ui_edit_element_validate';
  }
}

/**
 * During payment method rule validation, register webhooks on stripe.
 */
function commerce_stripe_pi_form_rules_ui_edit_element_validate(&$form, &$form_state) {
  $export = $form_state['rules_element']
    ->export();

  // Get webhook url.
  $payment_method = $form_state['values']['parameter']['payment_method']['settings']['payment_method'];
  $webhook_url = $payment_method['settings']['webhooks']['url'];
  $webhook_events = $payment_method['settings']['webhooks']['events'];
  $export_settings = $export['commerce_payment_enable_commerce_stripe_pi']['payment_method']['value']['settings'];
  $webhook_id = $export_settings['webhooks']['webhook_id'];
  $export_charge_mode = $export_settings['stripe_pi_charge_mode'];
  commerce_stripe_pi_load_library();
  Stripe::setApiKey(trim($payment_method['settings']['secret_key']));
  if (empty($webhook_url) && !empty($webhook_id)) {

    // Remove endpoint.
    $endpoint = WebhookEndpoint::retrieve($webhook_id);
    $endpoint
      ->delete();
  }
  if (empty($webhook_url)) {
    return;
  }

  // Check charge mode has not be modified.
  if ($export_charge_mode !== $payment_method['settings']['stripe_pi_charge_mode']) {

    // Delete previews webhook and create it again with charge mode option.
    $endpoint = WebhookEndpoint::retrieve($webhook_id);
    $endpoint
      ->delete();
    $webhook_id = '';
  }
  $endpoint_data = array(
    'url' => $webhook_url . '/commerce_stripe_pi/webhook',
    'enabled_events' => array_keys($webhook_events),
  );
  if (empty($webhook_id)) {
    $endpoint = WebhookEndpoint::create($endpoint_data + array(
      'connect' => $export_charge_mode === 'platform',
    ));

    // Save endpoint id and secret id.
    $export['commerce_payment_enable_commerce_stripe_pi']['payment_method']['value']['settings']['webhooks']['secret'] = $endpoint->secret;
    $export['commerce_payment_enable_commerce_stripe_pi']['payment_method']['value']['settings']['webhooks']['webhook_id'] = $endpoint->id;
    $form_state['rules_element']
      ->import($export);
    return;
  }

  // Check webhook exist before updating it.
  try {
    WebhookEndpoint::retrieve($webhook_id);
  } catch (InvalidRequest $e) {
    drupal_set_message(t('Endpoint at url @url and id @id no more exists.', array(
      '@url' => $webhook_url,
      '@id' => $webhook_id,
    )));

    // Remove endpoint id and secret id.
    $export['commerce_payment_enable_commerce_stripe_pi']['payment_method']['value']['settings']['webhooks']['url'] = '';
    $export['commerce_payment_enable_commerce_stripe_pi']['payment_method']['value']['settings']['webhooks']['secret'] = '';
    $export['commerce_payment_enable_commerce_stripe_pi']['payment_method']['value']['settings']['webhooks']['webhook_id'] = '';
    $form_state['rules_element']
      ->import($export);
    return;
  }

  // Update webhook url.
  WebhookEndpoint::update($webhook_id, $endpoint_data);
}

/**
 * Generate credit card form.
 */
function _commerce_stripe_pi_credit_card_form() {
  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
  $credit_card_fields = array(
    'owner' => '',
    'number' => '',
    'exp_month' => '',
    'exp_year' => '',
    'code' => '',
  );
  $form = commerce_payment_credit_card_form($credit_card_fields);

  // Add a css class so that we can easily identify Stripe related input fields
  // Do not require the fields
  //
  // Remove "name" attributes from Stripe related input elements to
  // prevent card data to be sent to Drupal server
  // (see https://stripe.com/docs/tutorials/forms)
  foreach (array_keys($credit_card_fields) as $key) {
    $credit_card_field =& $form['credit_card'][$key];
    $credit_card_field['#attributes']['class'][] = 'stripe_pi';
    $credit_card_field['#required'] = FALSE;
    $credit_card_field['#post_render'][] = '_commerce_stripe_pi_credit_card_field_remove_name';
  }
  return $form;
}

/**
 * Payment method callback: checkout and terminal form.
 */
function commerce_stripe_pi_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $cardonfile_id = _commerce_stripe_pi_get_cardonfile_id_from_checkout_pane($payment_method, $pane_values, $order);
  $payment_intent = _commerce_stripe_pi_get_payment_intent($order_wrapper, $payment_method, $cardonfile_id);

  // Payment has already been succeeded.
  if ($payment_intent['status'] === COMMERCE_STRIPE_PI_SUCCEEDED || $payment_intent['status'] === COMMERCE_STRIPE_PI_REQUIRES_CAPTURE) {

    // We should not be in the current checkout pane anymore.
    $order_status = commerce_order_status_load($order->status);
    $checkout_page = commerce_checkout_page_load($order_status['checkout_page']);

    // Order status match with the current checkout pane.
    // We should not be in this status anymore.
    if ($checkout_page['page_id'] === $checkout_pane['page']) {
      drupal_set_message(t('You order has already been paid. Please refresh the page or contact the site administrator if your order is not completed in few minutes.'), 'error');
      watchdog('commerce_stripe_pi', 'The order @order_id is in @status state and is already paid but we try to display the payment form in checkout page.', array(
        '@order_id' => $order->order_id,
        '@status' => $order->status,
      ), WATCHDOG_DEBUG);
    }
    else {

      // Move to the updated checkout page.
      drupal_goto(commerce_checkout_order_uri($order));
    }
    return [];
  }
  $integration_type = !empty($payment_method['settings']['integration_type']) ? $payment_method['settings']['integration_type'] : COMMERCE_STRIPE_PI_DEFAULT_INTEGRATION;
  $field = field_info_field('commerce_customer_address');
  $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', 'billing');

  // Attempt to load the billing address from the order data.
  $billing_address = addressfield_default_values($field, $instance, array());
  if (!empty($order->commerce_customer_billing) && !empty($order_wrapper->commerce_customer_billing->commerce_customer_address)) {
    $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address
      ->value();
  }

  // Pass the billing address values to JS, so they can be included in
  // the token creation sent to Stripe.
  $address = array(
    'address_line1' => !empty($billing_address['thoroughfare']) ? $billing_address['thoroughfare'] : '',
    'address_line2' => !empty($billing_address['premise']) ? $billing_address['premise'] : '',
    'address_city' => !empty($billing_address['locality']) ? $billing_address['locality'] : '',
    'address_state' => !empty($billing_address['administrative_area']) ? $billing_address['administrative_area'] : '',
    'address_zip' => !empty($billing_address['postal_code']) ? $billing_address['postal_code'] : '',
    'address_country' => !empty($billing_address['country']) ? $billing_address['country'] : '',
    'name' => !empty($billing_address['name_line']) ? $billing_address['name_line'] : '',
  );

  // Store them in Drupal.settings for easier access.
  drupal_add_js(array(
    'commerce_stripe_pi_address' => $address,
  ), array(
    'type' => 'setting',
  ));
  $form = _commerce_stripe_pi_elements_form($payment_intent['client_secret']);
  _commerce_stripe_pi_form_configure_stripe_pi_common($form, $payment_intent['client_secret'], $integration_type);

  // To display validation errors.
  $form['errors'] = array(
    '#type' => 'markup',
    '#markup' => '<div class="payment-errors"></div>',
  );
  return $form;
}

/**
 * Get cardonfile id from pane.
 *
 * @param array $payment_method
 *   The payment method.
 * @param array $pane_values
 *   The pane values.
 * @param object $order
 *   The order.
 *
 * @return string|null
 *   The cardonfile id or NULL.
 */
function _commerce_stripe_pi_get_cardonfile_id_from_checkout_pane(array $payment_method, array $pane_values, $order) {

  // Cardonfile is not enabled, exit.
  if (empty($payment_method['settings']['cardonfile']) || !module_exists('commerce_cardonfile')) {
    return NULL;
  }

  // In case we display the form for the first time, and user has a
  // cardonfile, load this card as default.
  if (empty($pane_values)) {
    $stored_cards = commerce_cardonfile_load_multiple_by_uid($order->uid, $payment_method['instance_id']);
    if (empty($stored_cards)) {
      return NULL;
    }
    $valid_cards = array_filter($stored_cards, 'commerce_cardonfile_validate_card_expiration');
    if (empty($valid_cards)) {
      return NULL;
    }

    // If have un-expired cards.
    foreach ($valid_cards as $valid_card) {

      // Return the default card.
      if (!empty($valid_card->instance_default)) {
        return $valid_card->card_id;
      }
    }

    // If there is no default card, get the first card.
    $valid_cards_id = array_keys($valid_cards);
    return reset($valid_cards_id);
  }

  // If user already select a cardonfile, use it.
  if (!empty($pane_values['payment_details']['cardonfile']) && $pane_values['payment_details']['cardonfile'] !== 'new') {
    return $pane_values['payment_details']['cardonfile'];
  }
  return NULL;
}

/**
 * Remove name from credit card field.
 */
function _commerce_stripe_pi_credit_card_field_remove_name($content, $element) {
  $name_pattern = '/\\sname\\s*=\\s*[\'"]?' . preg_quote($element['#name']) . '[\'"]?/';
  return preg_replace($name_pattern, '', $content);
}

/**
 * Implements hook_form_alter().
 */
function commerce_stripe_pi_form_alter(&$form, &$form_state, $form_id) {

  // Exit if the current form ID is for a checkout page form.
  if (strpos($form_id, 'commerce_checkout_form_') !== 0 || !commerce_checkout_page_load(substr($form_id, 23))) {
    return;
  }

  // Exit if the current page's form does no include the payment checkout pane.
  if (empty($form['commerce_payment'])) {
    return;
  }

  // Exit if no payment method instance id.
  if (empty($form['commerce_payment']['payment_method']['#default_value'])) {
    return;
  }

  // Exit if not using a new card.
  if (module_exists('commerce_cardonfile')) {
    if (isset($form_state['values']['commerce_payment']['payment_details']['cardonfile'])) {
      if ($form_state['values']['commerce_payment']['payment_details']['cardonfile'] != 'new') {

        // Do not display the store cardonfile checkbox, we are already displaying
        // a cardonfile.
        $form['commerce_payment']['payment_details']['credit_card']['cardonfile_store']['#access'] = FALSE;

        // On radio change.
        return;
      }
    }
    elseif (isset($form['commerce_payment']['payment_details']['cardonfile']['#default_value']) && $form['commerce_payment']['payment_details']['cardonfile']['#default_value'] != 'new') {

      // Do not display the store cardonfile checkbox, we are already displaying
      // a cardonfile.
      $form['commerce_payment']['payment_details']['credit_card']['cardonfile_store']['#access'] = FALSE;

      // Initial page load.
      return;
    }
  }

  // Extract payment method instance id.
  $payment_method = commerce_payment_method_instance_load($form['commerce_payment']['payment_method']['#default_value']);
  if ($payment_method['base'] === 'commerce_stripe_pi') {

    // Do not display card registration checkbox for already registered cards.
    if (isset($form_state['values']) && $form_state['values']['commerce_payment']['payment_details']['cardonfile'] !== 'new') {
      $form['commerce_payment']['payment_details']['credit_card']['cardonfile_store']['#access'] = FALSE;
    }

    // Only display registration card on add card form.
    if (!isset($form_state['values']) && isset($form['commerce_payment']['payment_details']['cardonfile']['#options']) && count($form['commerce_payment']['payment_details']['cardonfile']['#options']) > 1) {
      $form['commerce_payment']['payment_details']['credit_card']['cardonfile_store']['#access'] = FALSE;
    }

    // Add submit handler.
    if (isset($form['buttons']['continue'])) {

      // Allow to bypass the automatic redirection to next checkout step.
      // Replace the default callback 'commerce_checkout_form_submit' with a
      // lighter version.
      if (($position = array_search('commerce_checkout_form_submit', $form['buttons']['continue']['#submit'], NULL)) !== FALSE) {
        $form['buttons']['continue']['#submit'][$position] = 'commerce_stripe_pi_commerce_checkout_form_submit';
      }
      else {
        array_unshift($form['buttons']['continue']['#submit'], 'commerce_stripe_pi_commerce_checkout_form_submit');
      }
    }
  }
}

/**
 * Define redirect on the global checkout form.
 */
function commerce_stripe_pi_commerce_checkout_form_submit($form, &$form_state) {

  // Fallback on default form submit.
  commerce_checkout_form_submit($form, $form_state);
}

/**
 * Implements hook_commerce_checkout_complete().
 */
function commerce_stripe_pi_commerce_checkout_complete($order) {

  // Deal only with commerce_stripe_pi payment method.
  if (!isset($order->data['payment_method']) || $order->data['payment_method'] !== 'commerce_stripe_pi|commerce_payment_commerce_stripe_pi') {
    return;
  }
  if (!commerce_stripe_pi_load_library()) {
    drupal_set_message(t('Error making the payment. Please contact shop admin to proceed.'), 'error');
    watchdog('commerce_stripe_pi', 'Cannot load stripe library for order @order', array(
      '@order' => $order->order_id,
    ), WATCHDOG_ERROR);
    commerce_stripe_pi_redirect_pane_previous_page($order, 'Cannot load stripe library.');
    drupal_goto(commerce_checkout_order_uri($order));
  }

  // Load payment intent to check payment succeed.
  // Deal with Payment intents.
  $order_payment_intent = _commerce_stripe_pi_get_payment_intent_order($order);
  if (empty($order_payment_intent)) {
    drupal_set_message(t('Error making the payment. Please try again or contact shop admin to proceed.'), 'error');
    watchdog('commerce_stripe_pi', 'Payment intent is missing from the order @order', array(
      '@order' => $order->order_id,
    ), WATCHDOG_ERROR);
    commerce_stripe_pi_redirect_pane_previous_page($order, 'Payment intent is missing from the order.');
    drupal_goto(commerce_checkout_order_uri($order));
  }

  // Verify status.
  $payment_intent = PaymentIntent::retrieve($order_payment_intent['id']);
  if (NULL === $payment_intent) {
    drupal_set_message(t('Error making the payment. Please try again or contact shop admin to proceed.'), 'error');
    watchdog('commerce_stripe_pi', 'Stripe does not find payment intent for order @order and payment intent id @paymentintent', array(
      '@order' => $order->order_id,
      '@paymentintent' => $order_payment_intent['id'],
    ), WATCHDOG_ERROR);
    commerce_stripe_pi_redirect_pane_previous_page($order, 'Stripe does not find payment intent.');
    drupal_goto(commerce_checkout_order_uri($order));
  }

  // Only succeeded and requires_capture are authorized to complete checkout.
  if ($payment_intent->status !== COMMERCE_STRIPE_PI_SUCCEEDED && $payment_intent->status !== COMMERCE_STRIPE_PI_REQUIRES_CAPTURE) {
    drupal_set_message(t('Error making the payment. Please try again or contact shop admin to proceed.'), 'error');
    watchdog('commerce_stripe_pi', 'Payment intent status is not succeeded nor requires_capture for order @order and payment intent @paymentintent. Status : @status', array(
      '@order' => $order->order_id,
      '@paymentintent' => $payment_intent->id,
      '@status' => $payment_intent->status,
    ), WATCHDOG_ERROR);
    commerce_stripe_pi_redirect_pane_previous_page($order, 'Payment intent status is not succeeded nor requires_capture.');
    drupal_goto(commerce_checkout_order_uri($order));
  }
}

/**
 * Moves an order back to the previous page via an order update and redirect.
 *
 * Error with payement intent during checkout complete are responsible for
 * calling this method.
 *
 * @param object $order
 *   An order object.
 * @param string $log
 *   Optional log message to use when updating the order status in conjunction
 *   with the redirect to the previous checkout page.
 */
function commerce_stripe_pi_redirect_pane_previous_page($order, $log) {

  // Load the order status object for the current order.
  $order_status = commerce_order_status_load($order->status);
  if ($order_status['state'] === 'checkout' && $order_status['checkout_page'] === 'complete') {
    $payment_page = commerce_checkout_page_load($order_status['checkout_page']);
    $prev_page = $payment_page['prev_page'];
    $order = commerce_order_status_update($order, 'checkout_' . $prev_page, FALSE, NULL, $log);
  }
}

/**
 * Payment method callback: checkout form submission.
 */
function commerce_stripe_pi_submit_form_submit($payment_method, $pane_form, $pane_values, $order) {
  if (!commerce_stripe_pi_load_library()) {
    drupal_set_message(t('Error making the payment. Please contact shop admin to proceed.'), 'error');
    return FALSE;
  }

  // Deal with Payment intents.
  $order_payment_intent = _commerce_stripe_pi_get_payment_intent_order($order);
  if (empty($order_payment_intent)) {
    return FALSE;
  }

  // Verify status.
  $payment_intent = PaymentIntent::retrieve($order_payment_intent['id']);

  // Rebuild checkout form if the transaction fails.
  if (!commerce_stripe_pi_create_transaction($order->order_id, $payment_intent, $payment_method)) {
    return FALSE;
  }

  // Add payment method if customer select the option.
  if (!empty($payment_method['settings']['cardonfile']) && !empty($pane_values['credit_card']['cardonfile_store']) && module_exists('commerce_cardonfile')) {
    $account = user_load($order->uid);
    $stripe_pi_payment_method = _commerce_stripe_pi_create_payment_method($payment_intent, $account, $payment_method);
    $profile = NULL;
    if (!empty($order->commerce_customer_billing)) {
      $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

      // Get the billing profile in use and associate it with the card.
      $profile = $order_wrapper->commerce_customer_billing
        ->value();
    }
    _commerce_stripe_pi_save_cardonfile($stripe_pi_payment_method, $order->uid, $payment_method, $pane_values['cardonfile_instance_default'], $profile);
  }
}

/**
 * Get a payment transaction from a commerce_stripe_pi payment intent id.
 *
 * @param string $remote_id
 *   The payment intent id.
 *
 * @return string|bool
 *   The transaction identifier, FALSE otherwise.
 */
function _commerce_stripe_pi_get_commerce_payment_transaction_from_remote_id($remote_id) {
  $query = db_select('commerce_payment_transaction', 'cpt');
  $query
    ->fields('cpt', [
    'transaction_id',
  ]);
  $query
    ->condition('remote_id', $remote_id);
  $query
    ->condition('payment_method', 'commerce_stripe_pi');
  $result = $query
    ->execute();
  return $result
    ->fetchField();
}

/**
 * Create a transaction.
 *
 * @param string $order_id
 *   The commerce order identifier.
 * @param \Stripe\PaymentIntent $payment_intent
 *   The payment intent object.
 * @param array $payment_method
 *   The drupal payment method.
 *
 * @return bool
 *   The transaction status.
 */
function commerce_stripe_pi_create_transaction($order_id, PaymentIntent $payment_intent, array $payment_method = NULL) {
  $lock = __FUNCTION__ . '_' . $order_id;

  // Do not create transaction if already exists.
  $transaction_id = _commerce_stripe_pi_get_commerce_payment_transaction_from_remote_id($payment_intent->id);
  if ($transaction_id) {
    return TRUE;
  }

  // Be sure to not create concurrent transactions.
  // It's possible to have two concurrent transactions called from webhook and
  // checkout submit.
  if (lock_acquire($lock)) {
    try {

      // Check a transaction for this remote status does not already exists.
      if ($transaction = _commerce_stripe_pi_get_commerce_payment_transaction_from_remote_id($payment_intent->id)) {
        lock_release($lock);
        return TRUE;
      }
      if (NULL === $payment_method) {
        $payment_method = commerce_payment_method_instance_load('commerce_stripe_pi|commerce_payment_commerce_stripe_pi');
      }
      if (method_exists($payment_intent, '__toJSON')) {
        $payment_intent_json = $payment_intent
          ->__toJSON();
      }
      else {
        $payment_intent_json = $payment_intent
          ->toJSON();
      }

      // If paymentIntent  is succeeded or requires_capture.
      if ($payment_intent->status === COMMERCE_STRIPE_PI_REQUIRES_CAPTURE || $payment_intent->status === COMMERCE_STRIPE_PI_SUCCEEDED) {

        // The payment don't need any additional actions and is completed !
        // Handle post-payment fulfillment.
        $transaction = commerce_payment_transaction_new('commerce_stripe_pi', $order_id);
        $transaction->instance_id = $payment_method['instance_id'];

        // Payment intent is integer in cents, but transaction expect decimal.
        $transaction->currency_code = strtoupper($payment_intent->currency);
        $transaction->remote_id = $payment_intent->id;

        // Set the Commerce Payment Transaction UID from order uid.
        $order = commerce_order_load($order_id);
        $transaction->uid = $order->uid;
        if ($payment_intent->status === COMMERCE_STRIPE_PI_SUCCEEDED) {
          $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
          $transaction->remote_status = $payment_intent->status;
          $transaction->message = t('Stripe payment intent succeeded.');
        }
        elseif ($payment_intent->status === COMMERCE_STRIPE_PI_REQUIRES_CAPTURE) {
          $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING;
          $transaction->remote_status = 'AUTH_ONLY';
          $transaction->message = t('Stripe payment intent pending capture.');
        }
        $transaction->amount = $payment_intent->amount;
        $transaction->payload[REQUEST_TIME] = $payment_intent_json;
        if (!_commerce_stripe_pi_commerce_payment_transaction_save($transaction)) {
          lock_release($lock);
          return FALSE;
        }
      }
      else {

        // Invalid status.
        drupal_set_message(t('We received the following error processing your card: @error
         Please enter your information again or try a different card.', array(
          '@error' => t('Invalid PaymentIntent status'),
        )), 'error');
        watchdog('commerce_stripe_pi', 'Payment refused using payment intent with Invalid PaymentIntent status for order @order_id : @stripe_pi_error.', array(
          '@order_id' => $order_id,
          '@stripe_pi_error' => $payment_intent_json,
        ), WATCHDOG_NOTICE);
        $transaction = commerce_payment_transaction_new('commerce_stripe_pi', $order_id);
        $transaction->message = t('Payment intent refused.');
        $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
        $transaction->remote_status = $payment_intent->status;
        $transaction->payload[REQUEST_TIME] = $payment_intent_json;
        _commerce_stripe_pi_commerce_payment_transaction_save($transaction);
        lock_release($lock);
        return FALSE;
      }
    } catch (Exception $e) {
      lock_release($lock);
      watchdog('commerce_stripe_pi', 'Following error received when creating transaction for order @order_id : @error.', array(
        '@order_id' => $order_id,
        '@error' => $e
          ->getMessage(),
      ), WATCHDOG_ERROR);
      return FALSE;
    }
  }
  lock_release($lock);
  return TRUE;
}

/**
 * Attempt to save the transaction and set messages if unsuccessful.
 */
function _commerce_stripe_pi_commerce_payment_transaction_save($transaction) {
  if (!commerce_payment_transaction_save($transaction)) {
    drupal_set_message(t('Our site is currently unable to process your card. Please contact the site administrator to complete your transaction'), 'error');
    watchdog('commerce_stripe_pi', 'commerce_payment_transaction_save returned false in saving a stripe transaction for order_id @order_id.', array(
      '@order_id' => $transaction->order_id,
    ), WATCHDOG_ERROR);
    return FALSE;
  }
  return TRUE;
}

/**
 * Call Stripe to create card for a user.
 *
 * @param \Stripe\PaymentIntent $payment_intent
 *   Payment Intent object.
 * @param object $account
 *   User account of the card owner.
 * @param array $payment_method
 *   Array containing payment_method informations.
 * @param bool $throw_exceptions
 *   Should exceptions be throwned ?
 *
 * @return \Stripe\PaymentMethod|bool
 *   The payment method object, FALSE in case of error.
 *
 * @throws \Exception
 */
function _commerce_stripe_pi_create_payment_method($payment_intent, $account, array $payment_method, $throw_exceptions = FALSE) {
  if (!commerce_stripe_pi_load_library()) {
    return FALSE;
  }
  $stripe_pi_payment_method_id = $payment_intent->payment_method;
  if (empty($payment_intent->customer)) {

    // If there is no existing customer id, use the Stripe form token to create
    // one.
    $stripe_pi_customer_id = commerce_stripe_pi_customer_id($account->uid, $payment_method['instance_id']);
  }
  else {
    $stripe_pi_customer_id = $payment_intent->customer;
  }
  if (!$stripe_pi_customer_id) {
    try {
      $customer = Customer::create(array(
        'email' => $account->mail,
        'description' => t('Customer for @mail', array(
          '@mail' => $account->mail,
        )),
        'payment_method' => $stripe_pi_payment_method_id,
      ));
      $stripe_pi_payment_method = PaymentMethod::retrieve($stripe_pi_payment_method_id);
    } catch (Exception $e) {
      drupal_set_message(t('We received the following error processing your card: %error. Your card cannot be saved.', array(
        '%error' => $e
          ->getMessage(),
      )), 'error');
      watchdog('commerce_stripe_pi', 'Following error received when creating Stripe customer: @stripe_pi_error.', array(
        '@stripe_pi_error' => $e
          ->getMessage(),
      ), WATCHDOG_NOTICE);
      if ($throw_exceptions) {
        throw $e;
      }
      return FALSE;
    }
  }
  else {
    try {
      $customer = Customer::retrieve($stripe_pi_customer_id);
    } catch (Exception $e) {
      drupal_set_message(t('We received the following error processing your card: %error. Please enter your information again or try a different card.', array(
        '%error' => $e
          ->getMessage(),
      )), 'error');
      watchdog('commerce_stripe_pi', 'Following error received when adding a card to customer: @stripe_pi_error.', array(
        '@stripe_pi_error' => $e
          ->getMessage(),
      ), WATCHDOG_NOTICE);
      if ($throw_exceptions) {
        throw $e;
      }
      return FALSE;
    }
    $stripe_pi_customer_id = $customer->id;
    try {
      $stripe_pi_payment_method = PaymentMethod::retrieve($stripe_pi_payment_method_id);
      $stripe_pi_payment_method
        ->attach([
        'customer' => $stripe_pi_customer_id,
      ]);
    } catch (Exception $e) {
      drupal_set_message(t('We received the following error processing your card: %error. Please enter your information again or try a different card.', array(
        '%error' => $e
          ->getMessage(),
      )), 'error');
      watchdog('commerce_stripe_pi', 'Following error received when attaching a payment method to customer: @stripe_pi_error.', array(
        '@stripe_pi_error' => $e
          ->getMessage(),
      ), WATCHDOG_NOTICE);
      if ($throw_exceptions) {
        throw $e;
      }
      return FALSE;
    }
  }
  return $stripe_pi_payment_method;
}

/**
 * Save payment method to card on file.
 *
 * @param object $stripe_pi_payment_method
 *   The stripe payment method.
 * @param string $uid
 *   The user id.
 * @param array $payment_method
 *   The drupal payment method.
 * @param bool $set_default
 *   True if it's default payment method for the user.
 * @param object $billing_profile
 *   The billing profile to attach to card.
 *
 * @return bool
 *   The cardonfile save status.
 */
function _commerce_stripe_pi_save_cardonfile($stripe_pi_payment_method, $uid, array $payment_method, $set_default, $billing_profile = NULL) {
  commerce_stripe_pi_load_library();
  $customer_id = (string) $stripe_pi_payment_method->customer;
  $payment_method_id = (string) $stripe_pi_payment_method->id;
  $card = $stripe_pi_payment_method->card;

  // Store the Stripe customer and card ids in the remote id field of
  // {commerce_cardonfile} table.
  $remote_id = $customer_id . '|' . $payment_method_id;

  // Populate and save the card.
  $card_data = commerce_cardonfile_new();
  $card_data->uid = $uid;
  $card_data->payment_method = $payment_method['method_id'];
  $card_data->instance_id = $payment_method['instance_id'];
  $card_data->remote_id = $remote_id;
  $card_data->card_type = $card->brand;
  $card_data->card_name = $card->name;
  $card_data->card_number = $card->last4;
  $card_data->card_exp_month = $card->exp_month;
  $card_data->card_exp_year = $card->exp_year;
  $card_data->status = 1;
  $card_data->instance_default = $set_default;

  // Save our updated Card on file.
  commerce_cardonfile_save($card_data);

  // Associate the stored card with a billing profile, if one was given.
  if (!empty($billing_profile)) {
    $card_wrapper = entity_metadata_wrapper('commerce_cardonfile', $card_data);
    $card_wrapper->commerce_cardonfile_profile
      ->set($billing_profile);
    $card_wrapper
      ->save();
  }
  watchdog('commerce_stripe_pi', 'Stripe Customer Profile @profile_id created and saved to user @uid.', array(
    '@profile_id' => $customer_id,
    '@uid' => $uid,
  ));
  return $card_data;
}

/**
 * Implements hook_commerce_payment_method_info_alter().
 *
 * Displays a warning if Stripe private and public keys are not set and the
 * user has permission to administer payment methods.
 */
function commerce_stripe_pi_commerce_payment_method_info_alter(&$payment_methods) {
  if (isset($payment_methods['commerce_stripe_pi'])) {

    // Just return if they don't have permission to see these errors.
    if (!user_access('administer payment methods')) {
      return;
    }
    $found_errors = FALSE;
    $settings = _commerce_stripe_pi_load_settings();

    // If secret_key or public_key is not set.
    if (empty($settings['secret_key']) || empty($settings['public_key'])) {
      $found_errors = TRUE;
      drupal_set_message(t('Stripe secret and public key are required in order to use Stripe payment method. See README.txt for instructions.'), 'warning');
    }

    // If integration_type is not set.
    if (empty($settings['integration_type'])) {
      $found_errors = TRUE;
      drupal_set_message(t('The Stripe payment method "Integration type" is not set. Stripe.js will be used by default.'), 'warning');
    }

    // If they need to configure anything, be nice and give them the link.
    if ($found_errors) {
      $link = l(t('configured here'), 'admin/commerce/config/payment-methods');
      drupal_set_message(t('Settings required for the Stripe payment method can be !link.', array(
        '!link' => $link,
      )), 'warning');
    }
  }
}

/**
 * Load settings for commerce_stripe_pi payment method.
 *
 * @param string $name
 *   The setting name to load.
 *
 * @return array
 *   The settings.
 */
function _commerce_stripe_pi_load_settings($name = NULL) {
  static $settings = array();
  $commerce_stripe_pi_payment_method = NULL;
  if (!empty($settings)) {
    return $settings;
  }
  if (commerce_payment_method_load('commerce_stripe_pi') && rules_config_load('commerce_payment_commerce_stripe_pi')) {
    $commerce_stripe_pi_payment_method = commerce_payment_method_instance_load('commerce_stripe_pi|commerce_payment_commerce_stripe_pi');
  }
  if (NULL !== $name && rules_config_load('commerce_payment_commerce_stripe_pi')) {
    $commerce_stripe_pi_payment_method = commerce_payment_method_instance_load('commerce_stripe_pi|commerce_payment_commerce_stripe_pi');
  }
  if (NULL !== $commerce_stripe_pi_payment_method) {
    $settings = $commerce_stripe_pi_payment_method['settings'];
  }
  return $settings;
}

/**
 * Load a setting from commerce_stripe_pi payment method settings.
 *
 * @param string $name
 *   The setting name to load.
 * @param string $default_value
 *   The default value in case no settings exists.
 *
 * @return mixed|null
 *   The setting.
 */
function _commerce_stripe_pi_load_setting($name, $default_value = NULL) {
  $settings = _commerce_stripe_pi_load_settings($name);
  return isset($settings[$name]) ? $settings[$name] : $default_value;
}

/**
 * Get cardonfile payment method.
 */
function commerce_stripe_pi_get_cardonfile_payment_method($cardonfile_id) {
  if (!commerce_stripe_pi_load_library()) {
    return FALSE;
  }
  $card_data = commerce_cardonfile_load($cardonfile_id);
  if (empty($card_data) || 0 === (int) $card_data->status) {
    drupal_set_message(t('The requested card on file is no longer valid.'), 'error');
    return FALSE;
  }

  // Fetch the customer id and payment method id from $card_data->remote_id.
  list($customer_id, $payment_method_id) = explode('|', $card_data->remote_id);
  return array(
    'customer' => $customer_id,
    'payment_method' => $payment_method_id,
  );
}

/**
 * Set cardonfile payment method.
 */
function commerce_stripe_pi_set_cardonfile_payment_method($payment_intent_id, $cardonfile_id) {
  if (!commerce_stripe_pi_load_library()) {
    return FALSE;
  }
  if (!($payment_method = commerce_stripe_pi_get_cardonfile_payment_method($cardonfile_id))) {
    return FALSE;
  }
  $data = array(
    'payment_method' => $payment_method['payment_method'],
    'customer' => $payment_method['customer'],
  );

  // Add payment_method to payment_intent.
  return PaymentIntent::update($payment_intent_id, $data);
}

/**
 * Card on file callback: create form.
 */
function commerce_stripe_pi_cardonfile_create_form($form, &$form_state, $op, $card_data) {
  $account = user_load($form_state['build_info']['args'][1]->uid);

  // @todo: Check for a customer_id we can reuse from Stripe.
  // Pass along information to the validate and submit handlers.
  $form['card_data'] = array(
    '#type' => 'value',
    '#value' => $card_data,
  );
  $form['op'] = array(
    '#type' => 'value',
    '#value' => $op,
  );
  $form['user'] = array(
    '#type' => 'value',
    '#value' => $account,
  );
  $stripe_pi_setup_intent = _commerce_stripe_pi_setup_intent();
  $form += _commerce_stripe_pi_elements_form($stripe_pi_setup_intent->client_secret);
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Add card'),
  );
  _commerce_stripe_pi_form_configure_stripe_pi_common($form, $stripe_pi_setup_intent->client_secret);
  $payment_method = commerce_payment_method_instance_load($card_data->instance_id);
  $stored_cards = commerce_cardonfile_load_multiple_by_uid($account->uid, $payment_method['instance_id']);
  if (!empty($stored_cards)) {
    $valid_cards = array_filter($stored_cards, 'commerce_cardonfile_validate_card_expiration');
  }
  $form['credit_card']['cardonfile_instance_default'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use as default card for payments with %method', array(
      '%method' => $payment_method['display_title'],
    )),
    '#default_value' => empty($valid_cards) ? TRUE : FALSE,
    '#disabled' => empty($valid_cards) ? TRUE : FALSE,
  );

  // Create a billing profile object and add the address form.
  $profile = commerce_customer_profile_new('billing', $account->uid);

  // Add the entity context of the current cart order.
  $profile->entity_context = array(
    'entity_type' => 'commerce_cardonfile',
    'entity_id' => $card_data->card_id,
  );
  $form['commerce_customer_profile'] = array(
    '#type' => 'value',
    '#value' => $profile,
  );

  // Add the field widgets for the profile.
  field_attach_form('commerce_customer_profile', $profile, $form, $form_state);

  // Add a validation callback so that we can call field_attach functions.
  $form['#validate'][] = 'commerce_stripe_pi_cardonfile_create_validate';

  // Tweak the form to remove the fieldset from the address field if there
  // is only one on this profile.
  $addressfields = array();
  foreach (commerce_info_fields('addressfield', 'commerce_customer_profile') as $field_name => $field) {
    if (!empty($form['address'][$field_name]['#language'])) {
      $langcode = $form['address'][$field_name]['#language'];

      // Only consider this addressfield if it's represented on the form.
      if (!empty($form['address'][$field_name][$langcode])) {
        $addressfields[] = array(
          $field_name,
          $langcode,
        );
      }
    }
  }

  // Check to ensure only one addressfield was found on the form.
  if (count($addressfields) === 1) {
    list($field_name, $langcode) = array_shift($addressfields);
    foreach (element_children($form['address'][$field_name][$langcode]) as $delta) {

      // Don't mess with the "Add another item" button that could be present.
      if ($form['address'][$field_name][$langcode][$delta]['#type'] != 'submit') {
        $form['address'][$field_name][$langcode][$delta]['#type'] = 'container';
      }
    }
  }
  if (isset($form['address']) && NULL !== $form['address']) {
    commerce_stripe_pi_set_addressfield_class_names($form['address']);
  }
  $form['errors'] = array(
    '#markup' => '<div id="card-errors"></div>',
  );
  return $form;
}

/**
 * Common form for payment intent.
 *
 * @param array $form
 *   The form.
 * @param string $stripe_pi_payment_intent
 *   The payment intent identifier.
 * @param string $integration_type
 *   The integration type.
 */
function _commerce_stripe_pi_form_configure_stripe_pi_common(array &$form, $stripe_pi_payment_intent, $integration_type = COMMERCE_STRIPE_PI_DEFAULT_INTEGRATION) {
  $public_key = _commerce_stripe_pi_load_setting('public_key');

  // Add secret intent to settings to be used by JS.
  drupal_add_js([
    'stripe_pi' => [
      'payment_intent' => [
        'client_secret' => $stripe_pi_payment_intent,
      ],
    ],
  ], 'setting');

  // Add stripe payment intent field.
  $form['stripe_pi_payment_intent'] = array(
    '#type' => 'hidden',
    '#attributes' => array(
      'id' => 'stripe_pi_payment_intent',
    ),
    '#default_value' => $stripe_pi_payment_intent,
  );
  $settings = _commerce_stripe_pi_load_setting('elements_settings');
  $form['#attached']['js'] = array(
    drupal_get_path('module', 'commerce_stripe_pi') . '/commerce_stripe_pi.elements.js' => array(
      'preprocess' => FALSE,
      'cache' => FALSE,
    ),
  );
  $stripe_pi_settings = array(
    'hide_postal_code' => (bool) $settings['hide_postal_code'],
  );
  $form['#attached']['js'][] = array(
    'data' => array(
      'stripe_pi' => $stripe_pi_settings + array(
        'publicKey' => trim($public_key),
        'integration_type' => $integration_type,
      ),
    ),
    'type' => 'setting',
  );
}

/**
 * Validation callback for card on file creation.
 */
function commerce_stripe_pi_cardonfile_create_validate($form, &$form_state) {
  $profile = $form_state['values']['commerce_customer_profile'];
  field_attach_form_validate('commerce_customer_profile', $profile, $form, $form_state);
}

/**
 * Submission callback for cardonfile form.
 */
function commerce_stripe_pi_cardonfile_create_form_submit($form, &$form_state) {
  $card_data = $form_state['values']['card_data'];
  $payment_method = commerce_payment_method_instance_load($card_data->instance_id);
  commerce_stripe_pi_cardonfile_create($form, $form_state, $payment_method, $card_data);
  $form_state['redirect'] = 'user/' . $card_data->uid . '/cards';
}

/**
 * Card on file callback: Update form.
 */
function commerce_stripe_pi_cardonfile_update_form($form, &$form_state, $op, $card_data) {
  $account = user_load($form_state['build_info']['args'][1]->uid);

  // @todo: Check for a customer_id we can reuse from Stripe.
  $form['card_data'] = array(
    '#type' => 'value',
    '#value' => $card_data,
  );
  $form['op'] = array(
    '#type' => 'value',
    '#value' => $op,
  );
  $form['user'] = array(
    '#type' => 'value',
    '#value' => $account,
  );
  $form['errors'] = array(
    '#markup' => '<div id="card-errors"></div>',
  );

  // Add the active card's details so the user knows which one it's updating.
  $card['number'] = t('xxxxxx @card_number', array(
    '@card_number' => $card_data->card_number,
  ));
  $card['exp_month'] = strlen($card_data->card_exp_month) === 1 ? '0' . $card_data->card_exp_month : $card_data->card_exp_month;
  $card['exp_year'] = $card_data->card_exp_year;

  // Because this is an update operation we implement the payment form directly.
  $fields = array(
    'code' => '',
    'exp_month' => $card['exp_month'],
    'exp_year' => $card['exp_year'],
  );

  // Because this is an Update form we need to implement the payment form
  // directly.
  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
  $form += commerce_payment_credit_card_form($fields, $card);

  // Disable the card number field.
  $form['credit_card']['number']['#disabled'] = TRUE;

  // Hide the unused owner field.
  $form['credit_card']['owner']['#access'] = FALSE;
  $payment_method = commerce_payment_method_instance_load($card_data->instance_id);
  $stored_cards = commerce_cardonfile_load_multiple_by_uid($account->uid, $payment_method['instance_id']);
  $valid_cards = array();

  // If have stored cards ...
  if (!empty($stored_cards)) {
    $valid_cards = array_filter($stored_cards, 'commerce_cardonfile_validate_card_expiration');
  }
  $card_count = count($valid_cards);
  $form['credit_card']['cardonfile_instance_default'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use as default card for payments with %method', array(
      '%method' => $payment_method['display_title'],
    )),
    '#default_value' => empty($valid_cards) && $card_count === 1 ? TRUE : $card_data->instance_default,
    '#disabled' => empty($valid_cards) && $card_count === 1 ? TRUE : $card_data->instance_default,
  );
  $wrapper = entity_metadata_wrapper('commerce_cardonfile', $card_data);

  // Tweak the form to remove the fieldset from the address field if there
  // is only one on this profile.
  $addressfields = array();
  $langcode = LANGUAGE_NONE;
  foreach (commerce_info_fields('addressfield', 'commerce_customer_profile') as $field_name => $field) {
    if (!empty($form[$field_name]['#language'])) {
      $langcode = $form[$field_name]['#language'];

      // Only consider this addressfield if it's represented on the form.
      if (!empty($form[$field_name][$langcode])) {
        $addressfields[] = array(
          $field_name,
          $langcode,
        );
      }
    }
  }

  // Load the billing profile associated with this card and populate the address
  // form.
  if ($wrapper
    ->__isset('commerce_cardonfile_profile') && $wrapper->commerce_cardonfile_profile
    ->value()) {
    $profile = $wrapper->commerce_cardonfile_profile
      ->value();
  }
  else {
    $profile = commerce_customer_profile_new('billing', $account->uid);

    // Prepare an addressfield array to set to the customer profile.
    $field = field_info_field('commerce_customer_address');
    $instance = field_info_instance('commerce_customer_profile', 'commerce_customer_address', 'billing');
    $address = addressfield_default_values($field, $instance);
    $profile->commerce_customer_address[$langcode][] = $address;
  }

  // Add the entity context of the current cart order.
  $profile->entity_context = array(
    'entity_type' => 'commerce_cardonfile',
    'entity_id' => $card_data->card_id,
  );
  $form['commerce_customer_profile'] = array(
    '#type' => 'value',
    '#value' => $profile,
  );

  // Add the field widgets for the profile.
  field_attach_form('commerce_customer_profile', $profile, $form, $form_state);

  // Add a validation callback so that we can call field_attach functions.
  $form['#validate'][] = 'commerce_stripe_pi_cardonfile_update_validate';

  // Check to ensure only one addressfield was found on the form.
  if (count($addressfields) === 1) {
    list($field_name, $langcode) = array_shift($addressfields);
    foreach (element_children($form['address'][$field_name][$langcode]) as $delta) {

      // Don't mess with the "Add another item" button that could be present.
      if ($form[$field_name][$langcode][$delta]['#type'] !== 'submit') {
        $form[$field_name][$langcode][$delta]['#type'] = 'container';
      }
    }
  }
  if (isset($form[$field_name][$langcode][0]) && NULL !== $form[$field_name][$langcode][0]) {
    commerce_stripe_pi_set_addressfield_class_names($form[$field_name][$langcode][0]);
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Update card'),
  );
  return $form;
}

/**
 * Validate the cardonfile update.
 */
function commerce_stripe_pi_cardonfile_update_validate($form, &$form_state) {
  $profile = NULL;
  if (!empty($form_state['values']['commerce_customer_profile'])) {
    $profile = $form_state['values']['commerce_customer_profile'];
  }
  field_attach_form_validate('commerce_customer_profile', $profile, $form, $form_state);
}

/**
 * Submit the cardonfile update.
 */
function commerce_stripe_pi_cardonfile_update_form_submit($form, &$form_state) {
  $card_data = $form_state['values']['card_data'];
  $payment_method = commerce_payment_method_instance_load($card_data->instance_id);
  commerce_stripe_pi_cardonfile_update($form, $form_state, $payment_method, $card_data);
  $form_state['redirect'] = 'user/' . $card_data->uid . '/cards';
}

/**
 * Card on file callback: create.
 */
function commerce_stripe_pi_cardonfile_create($form, &$form_state, $payment_method, $card_data) {
  if (!commerce_stripe_pi_load_library()) {
    return FALSE;
  }
  $account = $form_state['values']['user'];
  $payment_intent = SetupIntent::retrieve($form_state['values']['stripe_pi_payment_intent']);
  $stripe_pi_payment_method = _commerce_stripe_pi_create_payment_method($payment_intent, $account, $payment_method);
  if (!$stripe_pi_payment_method) {
    return;
  }

  // Associate a billing profile if we have one.
  $profile = NULL;
  if (isset($form_state['values']['commerce_customer_profile'])) {
    $profile = $form_state['values']['commerce_customer_profile'];
    $profile->status = TRUE;

    // Set the profile's uid if it's being created at this time.
    if (empty($profile->profile_id)) {
      $profile->uid = $account->uid;
    }

    // Notify field widgets.
    field_attach_submit('commerce_customer_profile', $profile, $form, $form_state);

    // Save the profile to pass to the form validators.
    commerce_customer_profile_save($profile);
  }
  _commerce_stripe_pi_save_cardonfile($stripe_pi_payment_method, $account->uid, $payment_method, $form_state['values']['credit_card']['cardonfile_instance_default'], $profile);
}

/**
 * Card on file callback: updates the associated customer payment profile.
 */
function commerce_stripe_pi_cardonfile_update($form, &$form_state, $payment_method, $card_data) {
  if (!commerce_stripe_pi_load_library()) {
    return FALSE;
  }

  // Fetch the customer id and paymenet method id from $card_data->remote_id.
  list(, $payment_method_id) = explode('|', $card_data->remote_id);
  try {
    $data = array(
      'card' => array(
        'exp_month' => $form_state['values']['credit_card']['exp_month'],
        'exp_year' => $form_state['values']['credit_card']['exp_year'],
      ),
    );
    $langcode = $form['commerce_customer_address']['#language'];
    if (!empty($form_state['values']['commerce_customer_address'][$langcode])) {
      $address = reset($form_state['values']['commerce_customer_address'][$langcode]);
      $data['billing_details'] = array(
        'address' => array(
          'city' => $address['locality'],
          'country' => $address['country'],
          'line1' => $address['thoroughfare'],
          'line2' => $address['premise'],
          'postal_code' => $address['postal_code'],
          'state' => $address['administrative_area'],
        ),
        'name' => $address['name_line'],
      );
    }
    PaymentMethod::update($payment_method_id, $data);
    return TRUE;
  } catch (Exception $e) {
    drupal_set_message(t('We received the following error processing your card: %error. Please enter your information again or try a different card.', array(
      '%error' => $e
        ->getMessage(),
    )), 'error');
    watchdog('commerce_stripe_pi', 'Following error received when updating card @stripe_pi_error.', array(
      '@stripe_pi_error' => $e
        ->getMessage(),
    ), WATCHDOG_NOTICE);
    return FALSE;
  }
}

/**
 * Card on file callback: deletes the associated customer payment profile.
 */
function commerce_stripe_pi_cardonfile_delete($form, &$form_state, $payment_method, $card_data) {
  if (!commerce_stripe_pi_load_library()) {
    return FALSE;
  }

  // Fetch the customer id and payment method id from $card_data->remote_id.
  list($customer_id, $payment_method_id) = explode('|', $card_data->remote_id);
  if (empty($payment_method_id) || empty($customer_id)) {
    return TRUE;
  }
  try {
    $payment_method = PaymentMethod::retrieve($payment_method_id);
    $payment_method
      ->detach();
    return TRUE;
  } catch (Exception $e) {
    drupal_set_message(t('We received the following error processing your card: %error. Please enter your information again or try a different card.', array(
      '%error' => $e
        ->getMessage(),
    )), 'error');
    watchdog('commerce_stripe_pi', 'Following error received when deleting card @stripe_pi_error.', array(
      '@stripe_pi_error' => $e
        ->getMessage(),
    ), WATCHDOG_NOTICE);
    return FALSE;
  }
}

/**
 * Brings the stripe php client library into scope.
 */
function commerce_stripe_pi_load_library() {
  $library = libraries_load('stripe-php');
  if (!$library || empty($library['loaded'])) {
    watchdog('commerce_stripe_pi', 'Failure to load Stripe API PHP Client Library. Please see the Status Report for more.', array(), WATCHDOG_CRITICAL);
    return FALSE;
  }
  return $library;
}

/**
 * Check existing cards on file to see if the customer has a Stripe customer id.
 *
 * @param int $uid
 *   The customer's Drupal user id.
 * @param string $instance_id
 *   The payment method instance id.
 *
 * @return mixed
 *   The customer id if one was found, otherwise FALSE
 */
function commerce_stripe_pi_customer_id($uid, $instance_id = NULL) {
  $stored_cards = commerce_cardonfile_load_multiple_by_uid($uid, $instance_id);
  if (!empty($stored_cards)) {
    $card_data = reset($stored_cards);
    list($customer_id, ) = explode('|', $card_data->remote_id);
  }
  return !empty($customer_id) ? $customer_id : FALSE;
}

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter() for addressfield.
 *
 * Set unique classes on billing address fields so that commerce_stripe_pi.js
 * can find them.
 */
function commerce_stripe_pi_field_widget_addressfield_standard_form_alter(&$element, &$form_state, $context) {
  if (NULL !== $element && $context['field']['field_name'] === 'commerce_customer_address' && $context['instance']['bundle'] === 'billing') {
    commerce_stripe_pi_set_addressfield_class_names($element);
  }
}

/**
 * Sets unique class names on address.
 *
 * Sets unique class names on address field form elements so that they can be
 * picked up by commerce_stripe_pi.js.
 *
 * @param array $element
 *   The addressfield form element.
 */
function commerce_stripe_pi_set_addressfield_class_names(array &$element) {

  // @todo: Make compatible with latest Addressbook module.
  if (isset($element['street_block']['thoroughfare'])) {
    $element['street_block']['thoroughfare']['#attributes']['class'][] = 'commerce-stripe-pi-thoroughfare';
  }
  if (isset($element['street_block']['premise'])) {
    $element['street_block']['premise']['#attributes']['class'][] = 'commerce-stripe-pi-premise';
  }
  if (isset($element['locality_block']['locality'])) {
    $element['locality_block']['locality']['#attributes']['class'][] = 'commerce-stripe-pi-locality';
  }
  if (isset($element['locality_block']['administrative_area'])) {
    $element['locality_block']['administrative_area']['#attributes']['class'][] = 'commerce-stripe-pi-administrative-area';
  }
  if (isset($element['locality_block']['postal_code'])) {
    $element['locality_block']['postal_code']['#attributes']['class'][] = 'commerce-stripe-pi-postal-code';
  }
  if (isset($element['country'])) {
    $element['country']['#attributes']['class'][] = 'commerce-stripe-pi-country';
  }
}

/**
 * Adds a metadata key to an existing information array.
 *
 * By default nothing is added here. Third party modules can implement
 * hook_commerce_stripe_pi_metadata(), documented in commerce_stripe_pi.api.php,
 * to add metadata. Metadata is useful to pass arbitrary information to Stripe,
 * such as the order number, information about the items in the cart, etc.
 *
 * @param array &$data
 *   An associative array, to which [metadata] => array(...) will be added, in
 *   case modules define information via the hook_commerce_stripe_pi_metadata()
 *   hook.
 * @param object $order
 *   The commerce order object.
 */
function commerce_stripe_pi_add_metadata(array &$data, $order) {
  $metadata = module_invoke_all('commerce_stripe_pi_metadata', $order);
  if (count($metadata)) {
    $data['metadata'] = $metadata;
  }
}

/**
 * Implements hook_commerce_stripe_pi_metadata().
 */
function commerce_stripe_pi_commerce_stripe_pi_metadata($order) {
  return array(
    'order_id' => $order->order_id,
    'order_number' => $order->order_number,
    'uid' => $order->uid,
    'email' => $order->mail,
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Make sure the terminal form is aware of Stripe.
 */
function commerce_stripe_pi_form_commerce_payment_order_transaction_add_form_alter(&$form, &$form_state, $form_id) {
  if (!empty($form['payment_terminal']) && isset($form_state['payment_method']['method_id']) && $form_state['payment_method']['method_id'] === 'commerce_stripe_pi') {

    // Do not trigger Stripe Javascript for Card on File payments.
    if (isset($form['payment_terminal']['payment_details']['credit_card']['#access']) && $form['payment_terminal']['payment_details']['credit_card']['#access'] === FALSE) {
      return;
    }
    $form['payment_terminal']['payment_details']['txn_type'] = array(
      '#type' => 'select',
      '#title' => t('Transaction type'),
      '#options' => array(
        COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only'),
        COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'),
      ),
      '#default_value' => $form_state['payment_method']['settings']['txn_type'],
    );
    $form['payment_terminal']['commerce_stripe_pi_terminal'] = array(
      '#value' => 1,
      '#type' => 'hidden',
    );
  }
}

/**
 * Returns whether advanced fraud should be enabled.
 *
 * Stripe recommends to include their js script on every page, to enable their
 * advanced fraud protection. This could potentially have privacy issues, so
 * the variable commerce_stripe_pi_advanced_fraud_enabled allows you to disable
 * it.
 *
 * @return bool
 *   Whether the advanced fraud protection should be enabled.
 */
function commerce_stripe_pi_advanced_fraud_enabled() {
  return (bool) variable_get('commerce_stripe_pi_advanced_fraud_enabled', TRUE);
}

/**
 * Implements hook_page_build().
 */
function commerce_stripe_pi_page_build(&$page) {

  // Include stripe.js on every page if advanced fraud protection is enabled.
  if (commerce_stripe_pi_advanced_fraud_enabled()) {
    $page['content']['#attached']['js'][COMMERCE_STRIPE_PI_JS] = array(
      'type' => 'external',
    );
  }
}

/**
 * Callback for the Stripe Elements form.
 */
function _commerce_stripe_pi_elements_form($client_secret = NULL) {
  $form = [];
  $form['credit_card'] = array(
    '#tree' => TRUE,
    '#attached' => array(
      'css' => array(
        drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.theme.css',
      ),
    ),
  );

  // Include js script only on checkout page if the advanced fraud protection is
  // not enabled.
  if (!commerce_stripe_pi_advanced_fraud_enabled()) {
    $form['credit_card']['#attached']['js'][COMMERCE_STRIPE_PI_JS] = array(
      'type' => 'external',
    );
  }
  $form['credit_card']['number'] = array(
    '#type' => 'item',
    '#markup' => '<div data-stripe="number" id="card-element"></div>',
  );
  $form['card_errors'] = array(
    '#type' => 'item',
    '#markup' => '<div data-stripe-pi-errors="number" class="stripe-pi-errors form-text" id="card-errors"></div>',
    '#weight' => 0,
  );
  return $form;
}

/**
 * Capture a transaction.
 *
 * @param object $transaction
 *   The commerce payment transaction object.
 * @param string $amount
 *   The amount to capture.
 */
function commerce_stripe_pi_capture($transaction, $amount, $is_decimal = FALSE) {
  if (!commerce_stripe_pi_load_library()) {
    watchdog('commerce_stripe_pi', 'Error loading payment intent library to capture payment : @transaction', array(
      '@transaction' => print_r($transaction, TRUE),
    ), WATCHDOG_ERROR);
    return;
  }

  // Set the amount that is to be captured. This amount has already been
  // validated, but needs to be converted to cents for Stripe.
  $capture_amount = $amount;
  if ($is_decimal) {
    $dec_amount = number_format($amount, 2, '.', '');
    $capture_amount = commerce_currency_decimal_to_amount($dec_amount, $transaction->currency_code);
  }
  $cparams = array(
    'amount_to_capture' => $capture_amount,
  );
  try {
    $paymentIntent = PaymentIntent::retrieve($transaction->remote_id);

    // Should only capture payment intent requiring capture.
    if ($paymentIntent->status !== COMMERCE_STRIPE_PI_REQUIRES_CAPTURE) {
      watchdog('commerce_stripe_pi', 'Following payment intent @paymentintent_id can\'t be captured because his status is not \'requires_capture\' but \'@status\'.', array(
        '@paymentintent_id' => $paymentIntent->id,
        '@status' => $paymentIntent->status,
      ), WATCHDOG_NOTICE);
      return;
    }
    drupal_alter('commerce_stripe_pi_capture', $transaction, $paymentIntent, $cparams);
    $response = $paymentIntent
      ->capture($cparams);
    if (method_exists($response, '__toJSON')) {
      $response_json = $response
        ->__toJSON();
    }
    else {
      $response_json = $response
        ->toJSON();
    }
    $transaction->payload[REQUEST_TIME] = $response_json;
    $transaction->remote_status = commerce_stripe_pi_get_remote_status(NULL, $transaction, 'capture');
    $transaction->message .= '<br />' . t('Captured: @date', array(
      '@date' => format_date(REQUEST_TIME, 'short'),
    ));
    $transaction->message .= '<br />' . t('Captured Amount: @amount', array(
      '@amount' => commerce_currency_amount_to_decimal($capture_amount, $transaction->currency_code),
    ));
    $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
    $transaction->amount = $capture_amount;
    commerce_payment_transaction_save($transaction);
  } catch (Exception $e) {
    drupal_set_message(t('We received the following error when trying to capture the transaction.'), 'error');
    drupal_set_message(check_plain($e
      ->getMessage()), 'error');
    watchdog('commerce_stripe_pi', 'Following error received when processing card for capture @paymentintent : @stripe_pi_error.', array(
      '@paymentintent' => $transaction->remote_id,
      '@stripe_pi_error' => $e
        ->getMessage(),
    ), WATCHDOG_NOTICE);
    $transaction->payload[REQUEST_TIME] = $e->json_body;
    $transaction->message = t('Capture processing error: @stripe_pi_error', array(
      '@stripe_pi_error' => $e
        ->getMessage(),
    ));
    $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
    $transaction->remote_status = 'FAILED';
    commerce_payment_transaction_save($transaction);
  }
}

/**
 * Access callback for processing capture.
 *
 * @param object $order
 *   The commerce order entity.
 * @param object $transaction
 *   The commerce payment transaction entity.
 *
 * @return bool
 *   Return TRUE if the user can update the transaction.
 */
function commerce_stripe_pi_capture_access($order, $transaction) {

  // Return FALSE if the transaction isn't for Commerce Stripe or isn't
  // awaiting capture.  This is detected by the AUTH_ONLY status.
  if ($transaction->payment_method !== 'commerce_stripe_pi' || empty($transaction->remote_id) || strtoupper($transaction->remote_status) !== 'AUTH_ONLY') {
    return FALSE;
  }

  // Return FALSE if it is more than 7 days past the original authorization.
  // Stripe does not allow capture after this time.
  if (time() - $transaction->created > 86400 * 7) {
    return FALSE;
  }

  // Allow access if the user can update this transaction.
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Access callback for voiding transactions.
 *
 * @param object $order
 *   The commerce_order entity.
 * @param object $transaction
 *   The commerce payment transaction entity.
 *
 * @return bool
 *   Return TRUE if the user can update the transaction.
 */
function commerce_stripe_pi_void_access($order, $transaction) {

  // Only auth_only transactions can be voided.
  if ($transaction->payment_method !== 'commerce_stripe_pi' || empty($transaction->remote_id) || strtoupper($transaction->remote_status) !== 'AUTH_ONLY') {
    return FALSE;
  }

  // Allow access if the user can update this transaction.
  return commerce_payment_transaction_access('update', $transaction);
}

/**
 * Get the capture flag for the charge.
 *
 * Used in commerce_stripe_pi_submit_form_submit to get the capture flag for the
 * charge.
 *
 * @param array $payment_method
 *   An array containing the payment_method information.
 * @param array $pane_values
 *   An array containing values from the Commerce Stripe payment pane.
 *
 * @return bool
 *   Returns TRUE if capture is set & FALSE for auth only.
 */
function _commerce_stripe_pi_get_txn_capture_bool(array $payment_method, array $pane_values) {
  $txn_type = !empty($payment_method['settings']['txn_type']) ? $payment_method['settings']['txn_type'] : COMMERCE_CREDIT_AUTH_CAPTURE;

  // This handles the case when we are in the payment terminal. The
  // $pane_values contains which type of transaction chosen.
  if (!empty($pane_values['txn_type'])) {
    $txn_type = $pane_values['txn_type'];
  }

  // The capture flag in the charge takes a boolean. This simply
  // translates the txn constants to a bool.
  if ($txn_type === COMMERCE_CREDIT_AUTH_CAPTURE) {
    return TRUE;
  }
  if ($txn_type === COMMERCE_CREDIT_AUTH_ONLY) {
    return FALSE;
  }

  // Return TRUE by default.
  return TRUE;
}

/**
 * Get the transactions remote status.
 *
 * @param bool $txn_capture_bool
 *   Indicates whether or not the payment was captured.
 * @param object $prev_transaction
 *   The previous transaction entity.
 * @param string $action
 *   The operation being performed on the transaction.
 *
 * @return string
 *   The remote status.
 *
 * @throws Exception
 */
function commerce_stripe_pi_get_remote_status($txn_capture_bool, $prev_transaction = NULL, $action = NULL) {

  // This is the case that a new charge is being created.
  if (!$prev_transaction) {
    if ($txn_capture_bool === FALSE) {
      return 'AUTH_ONLY';
    }
    return 'AUTH_CAPTURE';
  }

  // An administrator is acting upon a previous transaction in the admin ui
  // in order to capture or void.
  switch ($action) {
    case 'capture':
      if ($prev_transaction->remote_status === 'AUTH_ONLY') {
        return 'PRIOR_AUTH_CAPTURE';
      }

      // This means that internally this function has been called improperly by
      // the programmer.
      throw new Exception(t('Remote Status capture improperly used internally.'));
    case 'void':
      if ($prev_transaction->remote_status === 'AUTH_ONLY') {
        return 'VOID';
      }

      // This means that internally this function has been called improperly by
      // the programmer.
      throw new Exception(t('Remote Status void improperly used internally.'));
  }
  return 'FAILED';
}

/**
 * Get txn message success.
 *
 * A different success message needs to be displayed depending on whether the
 * transaction is a authorization only transaction, or an auth capture
 * transaction.
 *
 * @param bool $txn_capture_bool
 *   Indicates whether or not the payment was captured.
 *
 * @return string
 *   A success message for the strip transaction.
 */
function commerce_stripe_pi_get_txn_message_success($txn_capture_bool) {
  return $txn_capture_bool ? t('Payment completed successfully.') : t('Payment information authorized successfully.');
}

/**
 * Gets commerce_payment transaction status from capture boolean.
 *
 * @param bool $txn_capture_bool
 *   Indicates whether or not the payment was captured.
 *
 * @return string
 *   The transaction status.
 */
function commerce_stripe_pi_get_txn_status($txn_capture_bool) {
  return $txn_capture_bool ? COMMERCE_PAYMENT_STATUS_SUCCESS : COMMERCE_PAYMENT_STATUS_PENDING;
}

/**
 * Implements hook_commerce_cardonfile_checkout_pane_form_alter().
 */
function commerce_stripe_pi_commerce_cardonfile_checkout_pane_form_alter(&$payment_details, $form, $form_state) {

  // Show credit_card so element.js can load payment_intent.
  if (empty($form_state['values']['commerce_payment']['payment_details']['cardonfile']) || $form_state['values']['commerce_payment']['payment_details']['cardonfile'] !== 'new') {
    $payment_details['credit_card']['#access'] = TRUE;
  }
}

/**
 * Get the customer from the order.
 *
 * @param \EntityMetadataWrapper $order_wrapper
 *   The order wrapper.
 *
 * @return \Stripe\Customer|null
 *   A Customer object or NULL.
 *
 * @throws \Stripe\Error\Api
 */
function _commerce_stripe_pi_get_customer_from_order(\EntityMetadataWrapper $order_wrapper) {
  $mail = $order_wrapper->mail
    ->value();
  $customer = _commerce_stripe_pi_get_customer($mail);
  if (NULL !== $customer) {
    return $customer;
  }
  return _commerce_stripe_pi_create_customer(user_load($order_wrapper->uid
    ->value()), $mail, $order_wrapper);
}

/**
 * Create a customer.
 *
 * @param object $user
 *   A user object.
 * @param string $mail
 *   User email from order.
 * @param \EntityMetadataWrapper|null $order_wrapper
 *   The order wrapper.
 *
 * @return \Stripe\Customer
 *   The stripe customer.
 */
function _commerce_stripe_pi_create_customer($user, $mail, $order_wrapper = NULL) {
  $customer_info = array(
    'email' => $mail,
    'name' => $user->name,
  );
  drupal_alter('commerce_stripe_pi_create_customer', $customer_info, $user, $mail, $order_wrapper);
  return Customer::create($customer_info);
}

/**
 * Get customer object from e-mail.
 *
 * @param string $mail
 *   The e-mail to retrieve customer.
 *
 * @return mixed|null
 *   The Customer object, NULL otherwise.
 *
 * @throws \Stripe\Error\Api
 */
function _commerce_stripe_pi_get_customer($mail) {
  $customers = Customer::all(array(
    'email' => $mail,
    'limit' => 1,
  ));
  if (empty($customers->data)) {
    return NULL;
  }
  return reset($customers->data);
}

/**
 * Get payment intent and create if not exists.
 *
 * @param \EntityMetadataWrapper $order_wrapper
 *   The order wrapper to make payment intent.
 * @param array $payment_method
 *   The payment method informations.
 *
 * @return array
 *   The payment intent data.
 *
 * @throws \EntityMetadataWrapperException
 * @throws \Stripe\Error\Api
 */
function _commerce_stripe_pi_get_payment_intent(EntityMetadataWrapper $order_wrapper, array $payment_method, $cardonfile_id = NULL) {
  commerce_stripe_pi_load_library();
  $order = $order_wrapper
    ->value();
  $order_total = $order_wrapper->commerce_order_total
    ->value();

  // Get existing payment intent if exists.
  $payment_intent = _commerce_stripe_pi_get_payment_intent_order($order);

  // Generate data for payment intent.
  $payment_intent_data = [
    'amount' => (int) $order_total['amount'],
    'currency' => strtolower($payment_method['settings']['stripe_pi_currency']),
    'setup_future_usage' => 'on_session',
    'description' => format_string('@site_name: !order_label (@order_id)', array(
      '@site_name' => variable_get('site_name', 'Drupal'),
      '!order_label' => $order_wrapper
        ->label(),
      '@order_id' => $order->order_id,
    )),
  ];
  commerce_stripe_pi_add_metadata($payment_intent_data, $order);

  // Get payment method and stripe customer from cardonfile.
  if (NULL !== $cardonfile_id) {
    $cardonfile_payment_method = commerce_stripe_pi_get_cardonfile_payment_method($cardonfile_id);
    $payment_method_id = $cardonfile_payment_method['payment_method'];
    $customer_id = $cardonfile_payment_method['customer'];
    $payment_intent_data += array(
      'payment_method' => $payment_method_id,
      'customer' => $customer_id,
    );
  }
  else {

    // Get customer from Stripe.
    $customer = _commerce_stripe_pi_get_customer_from_order($order_wrapper);
    if (NULL !== $customer) {
      $payment_intent_data += array(
        'customer' => $customer->id,
      );
    }
  }

  // Select the appropriate Account id if we charge for an account with Connect.
  if (isset($payment_method['settings']['stripe_pi_charge_mode']) && $payment_method['settings']['stripe_pi_charge_mode'] === 'platform' && !empty($payment_method['settings']['platform_key'])) {
    Stripe::setClientId(trim($payment_method['settings']['platform_key']));
  }

  // Allow other modules to alter payment intent data.
  drupal_alter('commerce_stripe_pi_payment_intent_data', $payment_intent_data, $order_wrapper, $payment_method, $cardonfile_id);

  // If no payment intent has already be made for this order, create it.
  // If payment intent status is a requires_action, cancel previous payment
  // intent and re-create payment intent.
  if (empty($payment_intent) || ($payment_intent['status'] === 'requires_confirmation' || $payment_intent['status'] === 'requires_payment_method')) {

    // Cancel previous payment intent.
    if (!empty($payment_intent)) {
      if (!commerce_stripe_pi_cancel_payment_intent($payment_intent['id'], 'duplicate')) {
        $payment_intent_object = PaymentIntent::retrieve($payment_intent['id']);
        return json_decode(json_encode($payment_intent_object), TRUE);
      }
    }

    // Re-create payment intent.
    $payment_intent_data += [
      // ID of the payment method (a PaymentMethod, Card, BankAccount, or saved
      // Source object) to attach to this PaymentIntent.
      'payment_method_types' => array(
        'card',
      ),
      'capture_method' => $payment_method['settings']['txn_type'] === 'auth_capture' ? 'automatic' : 'manual',
    ];
    $payment_intent_object = PaymentIntent::create($payment_intent_data);

    // Convert the object containing objects into a recursive array.
    $payment_intent = json_decode(json_encode($payment_intent_object), TRUE);
    _commerce_stripe_pi_set_payment_intent_order($order, $payment_intent);
  }
  else {
    $payment_intent_object = PaymentIntent::update($payment_intent['id'], $payment_intent_data);

    // Convert the object containing objects into a recursive array.
    $payment_intent = json_decode(json_encode($payment_intent_object), TRUE);
    _commerce_stripe_pi_set_payment_intent_order($order, $payment_intent);
  }
  return $payment_intent;
}

/**
 * Get payment intent informations stored in order data.
 *
 * @param object $order
 *   The order.
 *
 * @return array
 *   The payment intents informations.
 */
function _commerce_stripe_pi_get_payment_intent_order($order) {
  return isset($order->data['stripe_pi']['payment_intent']) ? $order->data['stripe_pi']['payment_intent'] : array();
}

/**
 * Store the payment intent in order data.
 *
 * @param object $order
 *   The order object.
 * @param array $data
 *   The payment intent data to store.
 */
function _commerce_stripe_pi_set_payment_intent_order($order, array $data) {
  $payment_intent = json_decode(json_encode($data), TRUE);
  $order->data['stripe_pi']['payment_intent'] = $payment_intent;
  commerce_order_save($order);
}

/**
 * Return a SetupIntent instance.
 *
 * @return \Stripe\SetupIntent
 *   The Setup Intent object.
 */
function _commerce_stripe_pi_setup_intent() {
  commerce_stripe_pi_load_library();
  return SetupIntent::create(array(
    'usage' => 'on_session',
  ));
}

/**
 * Cancel a payment intent.
 *
 * @param string $payment_intent_id
 *   The payment intent identifier.
 * @param string $reason
 *   The reason for the cancellation (duplicate, fraudulent,
 *   requested_by_customer, or abandoned)
 * @param null|string $stripe_client_id
 *   The Stripe client connect request identifier.
 *
 * @return bool
 *   TRUE if the payment intent is canceled.
 *
 * @throws \Exception
 */
function commerce_stripe_pi_cancel_payment_intent($payment_intent_id, $reason = 'abandoned', $stripe_client_id = NULL) {
  commerce_stripe_pi_load_library();
  if (NULL !== $stripe_client_id) {
    Stripe::setClientId(trim($stripe_client_id));
  }

  // Only those reasons are allowed by Stripe.
  $reasons_allowed = array(
    'duplicate' => 'duplicate',
    'fraudulent' => 'fraudulent',
    'requested_by_customer' => 'requested_by_customer',
    'abandoned' => 'abandoned',
  );
  if (!isset($reasons_allowed[$reason])) {
    throw new \Exception(sprintf('Payment intent cancellation reason is not allowed : \'%s\'', $reason));
  }
  try {
    $payment_intent = PaymentIntent::retrieve($payment_intent_id);
    $payment_intent
      ->cancel([
      'cancellation_reason' => $reason,
    ]);
  } catch (InvalidRequest $exception) {
    watchdog('commerce_stripe_pi', 'Error cancelling payment intent for id @payment_intent_id : @stripe_pi_error', array(
      '@payment_intent_id' => $payment_intent_id,
      '@stripe_pi_error' => $exception
        ->getMessage(),
    ), WATCHDOG_NOTICE);
    return FALSE;
  }
  return TRUE;
}

Functions

Namesort descending Description
commerce_stripe_pi_add_metadata Adds a metadata key to an existing information array.
commerce_stripe_pi_advanced_fraud_enabled Returns whether advanced fraud should be enabled.
commerce_stripe_pi_cancel_payment_intent Cancel a payment intent.
commerce_stripe_pi_capture Capture a transaction.
commerce_stripe_pi_capture_access Access callback for processing capture.
commerce_stripe_pi_cardonfile_create Card on file callback: create.
commerce_stripe_pi_cardonfile_create_form Card on file callback: create form.
commerce_stripe_pi_cardonfile_create_form_submit Submission callback for cardonfile form.
commerce_stripe_pi_cardonfile_create_validate Validation callback for card on file creation.
commerce_stripe_pi_cardonfile_delete Card on file callback: deletes the associated customer payment profile.
commerce_stripe_pi_cardonfile_update Card on file callback: updates the associated customer payment profile.
commerce_stripe_pi_cardonfile_update_form Card on file callback: Update form.
commerce_stripe_pi_cardonfile_update_form_submit Submit the cardonfile update.
commerce_stripe_pi_cardonfile_update_validate Validate the cardonfile update.
commerce_stripe_pi_commerce_cardonfile_checkout_pane_form_alter Implements hook_commerce_cardonfile_checkout_pane_form_alter().
commerce_stripe_pi_commerce_checkout_complete Implements hook_commerce_checkout_complete().
commerce_stripe_pi_commerce_checkout_form_submit Define redirect on the global checkout form.
commerce_stripe_pi_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_stripe_pi_commerce_payment_method_info_alter Implements hook_commerce_payment_method_info_alter().
commerce_stripe_pi_commerce_stripe_pi_metadata Implements hook_commerce_stripe_pi_metadata().
commerce_stripe_pi_create_transaction Create a transaction.
commerce_stripe_pi_customer_id Check existing cards on file to see if the customer has a Stripe customer id.
commerce_stripe_pi_field_widget_addressfield_standard_form_alter Implements hook_field_widget_WIDGET_TYPE_form_alter() for addressfield.
commerce_stripe_pi_form_alter Implements hook_form_alter().
commerce_stripe_pi_form_commerce_payment_order_transaction_add_form_alter Implements hook_form_FORM_ID_alter().
commerce_stripe_pi_form_rules_ui_edit_element_alter Implements hook_form_FORM_ID_alter().
commerce_stripe_pi_form_rules_ui_edit_element_validate During payment method rule validation, register webhooks on stripe.
commerce_stripe_pi_get_cardonfile_payment_method Get cardonfile payment method.
commerce_stripe_pi_get_remote_status Get the transactions remote status.
commerce_stripe_pi_get_txn_message_success Get txn message success.
commerce_stripe_pi_get_txn_status Gets commerce_payment transaction status from capture boolean.
commerce_stripe_pi_libraries_info Implements hook_libraries_info().
commerce_stripe_pi_libraries_postload_callback Post-load callback for the Stripe PHP Library.
commerce_stripe_pi_load_library Brings the stripe php client library into scope.
commerce_stripe_pi_menu Implements hook_menu().
commerce_stripe_pi_page_build Implements hook_page_build().
commerce_stripe_pi_redirect_pane_previous_page Moves an order back to the previous page via an order update and redirect.
commerce_stripe_pi_return_access Access callback for processing returns.
commerce_stripe_pi_settings_form Payment method settings form.
commerce_stripe_pi_set_addressfield_class_names Sets unique class names on address.
commerce_stripe_pi_set_cardonfile_payment_method Set cardonfile payment method.
commerce_stripe_pi_submit_form Payment method callback: checkout and terminal form.
commerce_stripe_pi_submit_form_submit Payment method callback: checkout form submission.
commerce_stripe_pi_void_access Access callback for voiding transactions.
commerce_stripe_pi_webhook Get Stripe async response after payment intent.
_commerce_stripe_pi_commerce_payment_transaction_save Attempt to save the transaction and set messages if unsuccessful.
_commerce_stripe_pi_create_customer Create a customer.
_commerce_stripe_pi_create_payment_method Call Stripe to create card for a user.
_commerce_stripe_pi_credit_card_field_remove_name Remove name from credit card field.
_commerce_stripe_pi_credit_card_form Generate credit card form.
_commerce_stripe_pi_elements_form Callback for the Stripe Elements form.
_commerce_stripe_pi_form_configure_stripe_pi_common Common form for payment intent.
_commerce_stripe_pi_get_cardonfile_id_from_checkout_pane Get cardonfile id from pane.
_commerce_stripe_pi_get_commerce_payment_transaction_from_remote_id Get a payment transaction from a commerce_stripe_pi payment intent id.
_commerce_stripe_pi_get_customer Get customer object from e-mail.
_commerce_stripe_pi_get_customer_from_order Get the customer from the order.
_commerce_stripe_pi_get_payment_intent Get payment intent and create if not exists.
_commerce_stripe_pi_get_payment_intent_order Get payment intent informations stored in order data.
_commerce_stripe_pi_get_txn_capture_bool Get the capture flag for the charge.
_commerce_stripe_pi_load_setting Load a setting from commerce_stripe_pi payment method settings.
_commerce_stripe_pi_load_settings Load settings for commerce_stripe_pi payment method.
_commerce_stripe_pi_save_cardonfile Save payment method to card on file.
_commerce_stripe_pi_setup_intent Return a SetupIntent instance.
_commerce_stripe_pi_set_payment_intent_order Store the payment intent in order data.

Constants