1178 lines
38 KiB
PHP
1178 lines
38 KiB
PHP
<?php
|
|
/**
|
|
* Abstract Payment Gateway
|
|
*
|
|
* @package checkout-plugins-stripe-woo
|
|
* @since 0.0.1
|
|
*/
|
|
|
|
namespace CPSW\Gateway;
|
|
|
|
use WC_Payment_Gateway;
|
|
use CPSW\Inc\Helper;
|
|
use CPSW\Inc\Logger;
|
|
use CPSW\Gateway\Stripe\Stripe_Api;
|
|
use CPSW\Inc\Traits\Subscriptions;
|
|
use WP_Error;
|
|
use WC_Payment_Tokens;
|
|
use Exception;
|
|
|
|
/**
|
|
* Abstract Payment Gateway
|
|
*
|
|
* @since 0.0.1
|
|
*/
|
|
abstract class Abstract_Payment_Gateway extends WC_Payment_Gateway {
|
|
|
|
use Subscriptions;
|
|
|
|
/**
|
|
* Url of assets directory
|
|
*
|
|
* @var string
|
|
*/
|
|
private $assets_url = CPSW_URL . 'assets/';
|
|
|
|
/**
|
|
* Zero currencies accepted by stripe.
|
|
*
|
|
* @var array
|
|
*/
|
|
private static $zero_currencies = [ 'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VUV', 'XAF', 'XOF', 'XPF', 'VND' ];
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
add_filter( 'woocommerce_payment_gateways', [ $this, 'add_gateway_class' ], 999 );
|
|
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] );
|
|
add_action( 'woocommerce_admin_order_totals_after_total', [ $this, 'get_stripe_order_data' ] );
|
|
add_action( 'wp_ajax_create_setup_intent', [ $this, 'create_setup_intent' ] );
|
|
}
|
|
|
|
/**
|
|
* Adds transaction url in order details page
|
|
*
|
|
* @param WC_Order $order current order.
|
|
* @return string
|
|
*/
|
|
public function get_transaction_url( $order ) {
|
|
if ( 'test' === Helper::get_payment_mode() ) {
|
|
$this->view_transaction_url = 'https://dashboard.stripe.com/test/payments/%s';
|
|
} else {
|
|
$this->view_transaction_url = 'https://dashboard.stripe.com/payments/%s';
|
|
}
|
|
|
|
return parent::get_transaction_url( $order );
|
|
}
|
|
|
|
/**
|
|
* Get Order description string
|
|
*
|
|
* @param WC_Order $order current order.
|
|
* @return string
|
|
*/
|
|
public function get_order_description( $order ) {
|
|
return apply_filters( 'cpsw_get_order_description', get_bloginfo( 'name' ) . ' - ' . __( 'Order ', 'checkout-plugins-stripe-woo' ) . $order->get_id() );
|
|
}
|
|
|
|
/**
|
|
* Registering Gateway to WooCommerce
|
|
*
|
|
* @param array $methods List of registered gateways.
|
|
* @return array
|
|
*/
|
|
public function add_gateway_class( $methods ) {
|
|
array_unshift( $methods, $this );
|
|
return $methods;
|
|
}
|
|
|
|
/**
|
|
* Get billing countries for gateways
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @return string $billing_country
|
|
*/
|
|
public function get_billing_country() {
|
|
global $wp;
|
|
|
|
if ( isset( $wp->query_vars['order-pay'] ) ) {
|
|
$order = wc_get_order( absint( $wp->query_vars['order-pay'] ) );
|
|
$billing_country = $order->get_billing_country();
|
|
} else {
|
|
$customer = WC()->customer;
|
|
$billing_country = $customer ? $customer->get_billing_country() : null;
|
|
|
|
if ( ! $billing_country ) {
|
|
$billing_country = WC()->countries->get_base_country();
|
|
}
|
|
}
|
|
|
|
return $billing_country;
|
|
}
|
|
|
|
/**
|
|
* Get WooCommerce currency
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_currency() {
|
|
global $wp;
|
|
|
|
if ( isset( $wp->query_vars['order-pay'] ) ) {
|
|
$order = wc_get_order( absint( $wp->query_vars['order-pay'] ) );
|
|
|
|
return $order->get_currency();
|
|
}
|
|
|
|
return get_woocommerce_currency();
|
|
}
|
|
|
|
/**
|
|
* Checks whether this gateway is available.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function is_available() {
|
|
if ( 'yes' !== $this->enabled ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! Helper::get_payment_mode() && is_checkout() ) {
|
|
return false;
|
|
}
|
|
|
|
if ( 'test' === Helper::get_payment_mode() ) {
|
|
if ( empty( Helper::get_setting( 'cpsw_test_pub_key' ) ) || empty( Helper::get_setting( 'cpsw_test_secret_key' ) ) ) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if ( empty( Helper::get_setting( 'cpsw_pub_key' ) ) || empty( Helper::get_setting( 'cpsw_secret_key' ) ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get/Retrieve stripe customer id if exists
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param mixed $order current woocommerce order.
|
|
*
|
|
* @return mixed customer id
|
|
*/
|
|
public function get_customer_id( $order = false ) {
|
|
$user = wp_get_current_user();
|
|
$user_id = ( $user->ID && $user->ID > 0 ) ? $user->ID : false;
|
|
$customer_id = false;
|
|
|
|
if ( $user_id ) {
|
|
$customer_id = get_user_option( '_cpsw_customer_id', $user_id );
|
|
if ( $customer_id ) {
|
|
return $customer_id;
|
|
}
|
|
}
|
|
|
|
$customer = false;
|
|
if ( ! $customer_id ) {
|
|
$customer = $this->create_stripe_customer( $order, $user->email );
|
|
}
|
|
|
|
if ( $customer ) {
|
|
if ( $user_id ) {
|
|
update_user_option( $user_id, '_cpsw_customer_id', $customer->id, false );
|
|
}
|
|
return $customer->id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates stripe customer object
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param object $order woocommerce order object.
|
|
* @param boolean|string $user_email user email id.
|
|
*
|
|
* @return Stripe::Customer
|
|
*/
|
|
public function create_stripe_customer( $order = false, $user_email = false ) {
|
|
$args = [
|
|
'email' => $user_email,
|
|
];
|
|
|
|
if ( $order ) {
|
|
$args = [
|
|
'description' => 'Customer for Order #' . $order->get_id(),
|
|
'email' => $user_email ? $user_email : $order->get_billing_email(),
|
|
'address' => [ // sending name and billing address to stripe to support indian exports.
|
|
'city' => method_exists( $order, 'get_billing_city' ) ? $order->get_billing_city() : $order->billing_city,
|
|
'country' => method_exists( $order, 'get_billing_country' ) ? $order->get_billing_country() : $order->billing_country,
|
|
'line1' => method_exists( $order, 'get_billing_address_1' ) ? $order->get_billing_address_1() : $order->billing_address_1,
|
|
'line2' => method_exists( $order, 'get_billing_address_2' ) ? $order->get_billing_address_2() : $order->billing_address_2,
|
|
'postal_code' => method_exists( $order, 'get_billing_postcode' ) ? $order->get_billing_postcode() : $order->billing_postcode,
|
|
'state' => method_exists( $order, 'get_billing_state' ) ? $order->get_billing_state() : $order->billing_state,
|
|
],
|
|
'name' => ( method_exists( $order, 'get_billing_first_name' ) ? $order->get_billing_first_name() : $order->billing_first_name ) . ' ' . ( method_exists( $order, 'get_billing_last_name' ) ? $order->get_billing_last_name() : $order->billing_last_name ),
|
|
];
|
|
}
|
|
|
|
$args = apply_filters( 'cpsw_create_stripe_customer_args', $args );
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->customers( 'create', [ $args ] );
|
|
$response = $response['success'] ? $response['data'] : false;
|
|
|
|
if ( empty( $response->id ) ) {
|
|
return false;
|
|
}
|
|
|
|
return apply_filters( 'cpsw_create_stripe_customer_response', $response );
|
|
}
|
|
|
|
/**
|
|
* Refunds amount from stripe and return true/false as result
|
|
*
|
|
* @param string $order_id order id.
|
|
* @param string $amount refund amount.
|
|
* @param string $reason reason of refund.
|
|
* @return bool
|
|
*/
|
|
public function process_refund( $order_id, $amount = null, $reason = '' ) {
|
|
if ( 0 >= $amount ) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
$reason = empty( $reason ) ? __( 'N/A', 'checkout-plugins-stripe-woo' ) : '';
|
|
$order = wc_get_order( $order_id );
|
|
$intent_secret = $order->get_meta( '_cpsw_intent_secret', true );
|
|
$response = $this->create_refund_request( $order, $amount, $reason, $intent_secret['id'] );
|
|
$refund_response = $response['success'] ? $response['data'] : false;
|
|
|
|
if ( $refund_response ) {
|
|
|
|
if ( isset( $refund_response->balance_transaction ) ) {
|
|
$this->update_balance( $order, $refund_response->balance_transaction );
|
|
}
|
|
|
|
$refund_time = gmdate( 'Y-m-d H:i:s', time() );
|
|
set_transient( '_cpsw_refund_id_cache_' . $order_id, $refund_response->id, 60 );
|
|
set_transient( '_cpsw_refund_status_cache_' . $order_id, $refund_response->status, 60 );
|
|
$order->update_meta_data( '_cpsw_refund_id', $refund_response->id );
|
|
$order->add_order_note( __( 'Reason : ', 'checkout-plugins-stripe-woo' ) . $reason . '.<br>' . __( 'Amount : ', 'checkout-plugins-stripe-woo' ) . get_woocommerce_currency_symbol() . $amount . '.<br>' . __( 'Status : ', 'checkout-plugins-stripe-woo' ) . ucfirst( $refund_response->status ) . ' [ ' . $refund_time . ' ] ' . ( is_null( $refund_response->id ) ? '' : '<br>' . __( 'Transaction ID : ', 'checkout-plugins-stripe-woo' ) . $refund_response->id ) );
|
|
Logger::info( __( 'Refund initiated: ', 'checkout-plugins-stripe-woo' ) . __( 'Reason : ', 'checkout-plugins-stripe-woo' ) . $reason . __( 'Amount : ', 'checkout-plugins-stripe-woo' ) . get_woocommerce_currency_symbol() . $amount . __( 'Status : ', 'checkout-plugins-stripe-woo' ) . ucfirst( $refund_response->status ) . ' [ ' . $refund_time . ' ] ' . ( is_null( $refund_response->id ) ? '' : __( 'Transaction ID : ', 'checkout-plugins-stripe-woo' ) . $refund_response->id ), true );
|
|
|
|
if ( 'succeeded' === $refund_response->status ) {
|
|
return true;
|
|
} else {
|
|
return new WP_Error( 'error', __( 'Your refund process is ', 'checkout-plugins-stripe-woo' ) . ucfirst( $refund_response->status ) );
|
|
}
|
|
} else {
|
|
$order->add_order_note( __( 'Reason : ', 'checkout-plugins-stripe-woo' ) . $reason . '.<br>' . __( 'Amount : ', 'checkout-plugins-stripe-woo' ) . get_woocommerce_currency_symbol() . $amount . '.<br>' . __( ' Status : Failed ', 'checkout-plugins-stripe-woo' ) );
|
|
Logger::error( $response['message'], true );
|
|
return new WP_Error( 'error', $response['message'] );
|
|
}
|
|
} catch ( Exception $e ) {
|
|
Logger::error( $e->getMessage(), true );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process response for saved cards
|
|
*
|
|
* @param object $response intent response.
|
|
* @param object $order order response.
|
|
* @return array
|
|
*/
|
|
public function process_response( $response, $order ) {
|
|
Logger::info( 'Processing: ' . $response->id );
|
|
|
|
$order_id = $order->get_id();
|
|
$captured = ( isset( $response->captured ) && $response->captured ) ? 'yes' : 'no';
|
|
|
|
// Store charge data.
|
|
$order->update_meta_data( '_cpsw_charge_captured', $captured );
|
|
|
|
if ( isset( $response->balance_transaction ) ) {
|
|
$this->update_balance( $order, $response->balance_transaction, true );
|
|
}
|
|
|
|
if ( 'yes' === $captured ) {
|
|
/**
|
|
* Charge can be captured but in a pending state. Payment methods
|
|
* that are asynchronous may take couple days to clear. Webhook will
|
|
* take care of the status changes.
|
|
*/
|
|
if ( 'pending' === $response->status || 'processing' === $response->status ) {
|
|
$order_stock_reduced = $order->get_meta( '_order_stock_reduced', true );
|
|
|
|
if ( ! $order_stock_reduced ) {
|
|
wc_reduce_stock_levels( $order_id );
|
|
}
|
|
|
|
$order->set_transaction_id( $response->id );
|
|
$others_info = 'cpsw_sepa' === $this->id ? __( 'Payment will be completed once payment_intent.succeeded webhook received from Stripe.', 'checkout-plugins-stripe-woo' ) : '';
|
|
|
|
/* translators: transaction id, other info */
|
|
$order->update_status( 'on-hold', sprintf( __( 'Stripe charge awaiting payment: %1$s. %2$s', 'checkout-plugins-stripe-woo' ), $response->id, $others_info ) );
|
|
}
|
|
|
|
if ( 'succeeded' === $response->status ) {
|
|
if ( $order->has_status( [ 'pending', 'failed', 'on-hold' ] ) ) {
|
|
$order->payment_complete( $response->id );
|
|
}
|
|
|
|
/* translators: transaction id */
|
|
$message = sprintf( __( 'Stripe charge complete (Charge ID: %s)', 'checkout-plugins-stripe-woo' ), $response->id );
|
|
Logger::info( $message, true );
|
|
$order->add_order_note( $message );
|
|
}
|
|
|
|
if ( 'failed' === $response->status ) {
|
|
$message = __( 'Payment processing failed. Please retry.', 'checkout-plugins-stripe-woo' );
|
|
Logger::error( $message, true );
|
|
$order->add_order_note( $message );
|
|
}
|
|
} else {
|
|
$order->set_transaction_id( $response->id );
|
|
|
|
if ( $order->has_status( [ 'pending', 'failed', 'on-hold' ] ) ) {
|
|
wc_reduce_stock_levels( $order_id );
|
|
}
|
|
|
|
/* translators: transaction id */
|
|
$order_info = 'cpsw_sepa' === $this->id ? sprintf( __( 'Stripe charge awaiting payment: %1$s. Payment will be completed once payment_intent.succeeded webhook received from Stripe.', 'checkout-plugins-stripe-woo' ), $response->id ) : sprintf( __( 'Stripe charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization. Attempting to refund the order in part or in full will release the authorization and cancel the payment.', 'checkout-plugins-stripe-woo' ), $response->id );
|
|
|
|
$order->update_status( 'on-hold', $order_info );
|
|
}
|
|
|
|
if ( is_callable( [ $order, 'save' ] ) ) {
|
|
$order->save();
|
|
}
|
|
|
|
do_action( 'cpsw_process_response', $response, $order );
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Basic details of logged in user
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return array current user data.
|
|
*/
|
|
public function get_clients_details() {
|
|
return apply_filters(
|
|
'cswp_clients_details',
|
|
[
|
|
'ip' => isset( $_SERVER['REMOTE_ADDR'] ) ? esc_url_raw( $_SERVER['REMOTE_ADDR'] ) : '',
|
|
'agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : '',
|
|
'referer' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( $_SERVER['HTTP_REFERER'] ) : '',
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks if payment intent available for current order or else creates new payment intent.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $order_id woocommerce order id.
|
|
* @param string $idempotency_key unique idempotency key.
|
|
* @param array $args payment_intent arguments.
|
|
* @param boolean $return_error_response if true returns complete error response array.
|
|
*
|
|
* @return array intent data.
|
|
*/
|
|
public function get_payment_intent( $order_id, $idempotency_key, $args, $return_error_response = false ) {
|
|
$order = wc_get_order( $order_id );
|
|
$intent_secret = $order->get_meta( '_cpsw_intent_secret' );
|
|
$client_secret = '';
|
|
|
|
if ( ! empty( $intent_secret ) ) {
|
|
$secret = $intent_secret;
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->payment_intents( 'retrieve', [ $secret['id'] ] );
|
|
|
|
if ( $response['success'] && 'success' === $response['data']->status ) {
|
|
wc_add_notice( __( 'An error has occurred internally, due to which you are not redirected to the order received page.', 'checkout-plugins-stripe-woo' ), $notice_type = 'error' );
|
|
wp_safe_redirect( wc_get_checkout_url() );
|
|
exit();
|
|
}
|
|
}
|
|
|
|
$args = [
|
|
[ $args ],
|
|
[ 'idempotency_key' => $idempotency_key ],
|
|
];
|
|
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->payment_intents( 'create', $args );
|
|
|
|
if ( $response['success'] ) {
|
|
$intent = $response['data'];
|
|
} elseif ( $return_error_response ) {
|
|
return $response;
|
|
} else {
|
|
wc_add_notice( $response['message'], 'error' );
|
|
return false;
|
|
}
|
|
|
|
$intent_data = [
|
|
'id' => $intent->id,
|
|
'client_secret' => $intent->client_secret,
|
|
];
|
|
|
|
$order = wc_get_order( $order_id );
|
|
$order->update_meta_data( '_cpsw_intent_secret', $intent_data );
|
|
$order->save();
|
|
$client_secret = $intent->client_secret;
|
|
|
|
return [
|
|
'client_secret' => $client_secret,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Returns amount as per currency type
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $total amount to be processed.
|
|
* @param string $currency transaction currency.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_formatted_amount( $total, $currency = '' ) {
|
|
if ( ! $currency ) {
|
|
$currency = get_woocommerce_currency();
|
|
}
|
|
|
|
if ( in_array( strtoupper( $currency ), self::$zero_currencies, true ) ) {
|
|
// Zero decimal currencies accepted by stripe.
|
|
return absint( $total );
|
|
} else {
|
|
return absint( wc_format_decimal( ( (float) $total * 100 ), wc_get_price_decimals() ) ); // In cents.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add metadata to stripe
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @param int $order_id WooCommerce order Id.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_metadata( $order_id ) {
|
|
$order = wc_get_order( $order_id );
|
|
$details = [];
|
|
$billing_first_name = $order->get_billing_first_name();
|
|
$billing_last_name = $order->get_billing_last_name();
|
|
$name = $billing_first_name . ' ' . $billing_last_name;
|
|
|
|
if ( ! empty( $name ) ) {
|
|
$details['name'] = $name;
|
|
}
|
|
|
|
if ( ! empty( $order->get_billing_email() ) ) {
|
|
$details['email'] = $order->get_billing_email();
|
|
}
|
|
|
|
if ( ! empty( $order->get_billing_phone() ) ) {
|
|
$details['phone'] = $order->get_billing_phone();
|
|
}
|
|
|
|
if ( ! empty( $order->get_billing_address_1() ) ) {
|
|
$details['address'] = $order->get_billing_address_1();
|
|
}
|
|
|
|
if ( ! empty( $order->get_billing_city() ) ) {
|
|
$details['city'] = $order->get_billing_city();
|
|
}
|
|
|
|
if ( ! empty( $order->get_billing_country() ) ) {
|
|
$details['country'] = $order->get_billing_country();
|
|
}
|
|
|
|
$details['site_url'] = get_site_url();
|
|
|
|
return apply_filters( 'cpsw_metadata_details', $details, $order );
|
|
}
|
|
|
|
/**
|
|
* All payment icons that work with Stripe
|
|
*
|
|
* @since 1.2.0
|
|
*
|
|
* @param string $gateway_id gateway id to fetch icon.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function payment_icons( $gateway_id ) {
|
|
$icons = [
|
|
'cpsw_alipay' => '<img src="' . $this->assets_url . 'icon/alipay.svg" class="cpsw-alipay-icon" alt="Alipay" width="50px" />',
|
|
'cpsw_ideal' => '<img src="' . $this->assets_url . 'icon/ideal.svg" class="cpsw-ideal-icon" alt="iDEAL" width="32" />',
|
|
'cpsw_klarna' => '<img src="' . $this->assets_url . 'icon/klarna.svg" class="cpsw-klarna-icon" alt="Klarna" width="60" />',
|
|
'cpsw_p24' => '<img src="' . $this->assets_url . 'icon/p24.svg" class="cpsw-p24-icon" alt="Przelewy24" width="60" />',
|
|
'cpsw_bancontact' => '<img src="' . $this->assets_url . 'icon/bancontact.svg" class="cpsw-bancontact-icon" alt="Bancontact" width="40" />',
|
|
'cpsw_wechat' => '<img src="' . $this->assets_url . 'icon/wechat.svg" class="cpsw-wechat-icon" alt="WeChat" width="80" />',
|
|
'cpsw_sepa' => '<img src="' . $this->assets_url . 'icon/sepa.svg" class="cpsw-sepa-icon" alt="SEPA" width="50px" />',
|
|
];
|
|
|
|
return apply_filters(
|
|
'cpsw_payment_icons',
|
|
isset( $icons[ $gateway_id ] ) ? $icons[ $gateway_id ] : ''
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create refund request.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param object $order order.
|
|
* @param string $amount refund amount.
|
|
* @param string $reason reason of refund.
|
|
* @param string $intent_secret_id secret key.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function create_refund_request( $order, $amount, $reason, $intent_secret_id ) {
|
|
$client = $this->get_clients_details();
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->payment_intents( 'retrieve', [ $intent_secret_id ] );
|
|
$status = $response['success'] && isset( $response['data']->charges->data[0]->captured ) ? $response['data']->charges->data[0]->captured : false;
|
|
|
|
if ( ! $status ) {
|
|
Logger::error( __( 'Uncaptured Amount cannot be refunded', 'checkout-plugins-stripe-woo' ), true );
|
|
return new WP_Error( 'error', __( 'Uncaptured Amount cannot be refunded', 'checkout-plugins-stripe-woo' ) );
|
|
}
|
|
|
|
$intent_response = $response['data'];
|
|
$currency = $intent_response->currency;
|
|
|
|
$refund_params = apply_filters(
|
|
'cpsw_refund_request_args',
|
|
[
|
|
'payment_intent' => $intent_secret_id,
|
|
'amount' => $this->get_formatted_amount( $amount, $currency ),
|
|
'reason' => 'requested_by_customer',
|
|
'metadata' => [
|
|
'order_id' => $order->get_order_number(),
|
|
'customer_ip' => $client['ip'],
|
|
'agent' => $client['agent'],
|
|
'referer' => $client['referer'],
|
|
'reason_for_refund' => $reason,
|
|
],
|
|
]
|
|
);
|
|
|
|
$stripe_api = new Stripe_Api();
|
|
return $stripe_api->refunds( 'create', [ $refund_params ] );
|
|
}
|
|
|
|
/**
|
|
* Clean/Trim statement descriptor as per stripe requirement.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param string $statement_descriptor User Input.
|
|
*
|
|
* @return string optimized statement descriptor.
|
|
*/
|
|
public function clean_statement_descriptor( $statement_descriptor = '' ) {
|
|
$disallowed_characters = [ '<', '>', '\\', '*', '"', "'", '/', '(', ')', '{', '}' ];
|
|
|
|
// Strip any tags.
|
|
$statement_descriptor = wp_strip_all_tags( $statement_descriptor );
|
|
|
|
// Strip any HTML entities.
|
|
// Props https://stackoverflow.com/questions/657643/how-to-remove-html-special-chars .
|
|
$statement_descriptor = preg_replace( '/&#?[a-z0-9]{2,8};/i', '', $statement_descriptor );
|
|
|
|
// Next, remove any remaining disallowed characters.
|
|
$statement_descriptor = str_replace( $disallowed_characters, '', $statement_descriptor );
|
|
|
|
// Trim any whitespace at the ends and limit to 22 characters.
|
|
$statement_descriptor = substr( trim( $statement_descriptor ), 0, 22 );
|
|
|
|
return $statement_descriptor;
|
|
}
|
|
|
|
/**
|
|
* Process order after stripe payment
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param object $response intent response data.
|
|
* @param string $order_id currnt coocommerce id.
|
|
*
|
|
* @return array return data.
|
|
*/
|
|
public function process_order( $response, $order_id ) {
|
|
$order = wc_get_order( $order_id );
|
|
|
|
if ( isset( $response->balance_transaction ) ) {
|
|
$this->update_balance( $order, $response->balance_transaction, true );
|
|
}
|
|
|
|
if ( true === $response->captured ) {
|
|
$order->payment_complete( $response->id );
|
|
/* translators: order id */
|
|
Logger::info( sprintf( __( 'Payment successful Order id - %1s', 'checkout-plugins-stripe-woo' ), $order->get_id() ), true );
|
|
|
|
$source_name = 'cpsw_stripe' === $this->id ? ucfirst( $response->payment_method_details->card->brand ) : ucfirst( $response->payment_method_details->type );
|
|
|
|
$order->add_order_note( __( 'Payment Status: ', 'checkout-plugins-stripe-woo' ) . ucfirst( $response->status ) . ', ' . __( 'Source: Payment is Completed via ', 'checkout-plugins-stripe-woo' ) . $source_name );
|
|
} else {
|
|
/* translators: transaction id */
|
|
$order->update_status( 'on-hold', sprintf( __( 'Charge authorized (Charge ID: %s). Process order to take payment, or cancel to remove the pre-authorization. Attempting to refund the order in part or in full will release the authorization and cancel the payment.', 'checkout-plugins-stripe-woo' ), $response->id ) );
|
|
/* translators: transaction id */
|
|
Logger::info( sprintf( __( 'Charge authorized Order id - %1s', 'checkout-plugins-stripe-woo' ), $order->get_id() ), true );
|
|
}
|
|
|
|
WC()->cart->empty_cart();
|
|
|
|
/**
|
|
* Action when process order.
|
|
*
|
|
* @since 1.4.0
|
|
*
|
|
* @param obj $response Payment response data.
|
|
* @param obj $order WooCommerce main order.
|
|
* @param string $this->id Current payment gateway id.
|
|
*/
|
|
do_action( 'cpsw_local_gateways_process_order', $response, $order, $this->id );
|
|
|
|
return $this->get_return_url( $order );
|
|
}
|
|
|
|
/**
|
|
* Save payment method to meta of current order
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param object $order current woocommerce order.
|
|
* @param object $payment_method payment method associated with current order.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function save_payment_method_to_order( $order, $payment_method ) {
|
|
if ( $payment_method->customer ) {
|
|
$order->update_meta_data( '_cpsw_customer_id', $payment_method->customer );
|
|
}
|
|
|
|
$order->update_meta_data( '_cpsw_source_id', $payment_method->source );
|
|
|
|
if ( is_callable( [ $order, 'save' ] ) ) {
|
|
$order->save();
|
|
}
|
|
|
|
$this->maybe_update_source_on_subscription_order( $order, $payment_method );
|
|
}
|
|
|
|
/**
|
|
* Prepare payment method object
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param object $payment_method payment method object from intent.
|
|
* @param object $token token object used for payment.
|
|
*
|
|
* @return object
|
|
*/
|
|
public function prepare_payment_method( $payment_method, $token ) {
|
|
return (object) apply_filters(
|
|
'cpsw_prepare_payment_method_args',
|
|
[
|
|
'token_id' => $token->get_id(),
|
|
'customer' => $payment_method->customer,
|
|
'source' => $payment_method->id,
|
|
'source_object' => $payment_method,
|
|
'payment_method' => $payment_method->id,
|
|
'payment_method_object' => $payment_method,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks if using saved payment method or new card
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function is_using_saved_payment_method() {
|
|
// nonce verification already done while saving data in woocommerce function.
|
|
$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( wp_unslash( $_POST['payment_method'] ) ) : $this->id; //phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
return ( isset( $_POST[ 'wc-' . $payment_method . '-payment-token' ] ) && 'new' !== $_POST[ 'wc-' . $payment_method . '-payment-token' ] ); //phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
}
|
|
|
|
/**
|
|
* This function checks for the conditions whether current card should be saved or not.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $order_id WooCommerce order id.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function should_save_card( $order_id ) {
|
|
$status = false;
|
|
// Just checks if this key exists in chosen payment method, nonce verification not required.
|
|
$status = ( ! empty( $_POST[ 'wc-' . $this->id . '-new-payment-method' ] ) ) || $this->has_subscription( $order_id ); //phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
|
|
return apply_filters( 'cpsw_force_save_card', $status );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get token object for selected saved card payment
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param array $request post request.
|
|
* @return object
|
|
*/
|
|
public function get_token_from_request( array $request ) {
|
|
$payment_method = ! is_null( $request['payment_method'] ) ? $request['payment_method'] : null;
|
|
$token_request_key = 'wc-' . $payment_method . '-payment-token';
|
|
if (
|
|
! isset( $request[ $token_request_key ] ) ||
|
|
'new' === $request[ $token_request_key ]
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
$token = WC_Payment_Tokens::get( wc_clean( $request[ $token_request_key ] ) );
|
|
|
|
// If the token doesn't belong to this gateway or the current user it's invalid.
|
|
if ( ! $token || $payment_method !== $token->get_gateway_id() || $token->get_user_id() !== get_current_user_id() ) {
|
|
return null;
|
|
}
|
|
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Process payment using saved cards.
|
|
*
|
|
* @since 1.0.0
|
|
*
|
|
* @param int $order_id woocommerce order id.
|
|
* @return array
|
|
*/
|
|
public function process_payment_with_saved_payment_method( $order_id ) {
|
|
try {
|
|
$order = wc_get_order( $order_id );
|
|
// Nonce verification already done in parent woocommerce function.
|
|
$token = $this->get_token_from_request( wc_clean( $_POST ) ); //phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->payment_methods( 'retrieve', [ $token->get_token() ] );
|
|
$payment_method = $response['success'] ? $response['data'] : false;
|
|
$prepared_payment_method = $this->prepare_payment_method( $payment_method, $token );
|
|
|
|
$this->save_payment_method_to_order( $order, $prepared_payment_method );
|
|
/* translators: %1$1s order id, %2$2s order total amount */
|
|
Logger::info( sprintf( __( 'Begin processing payment with saved payment method for order %1$1s for the amount of %2$2s', 'checkout-plugins-stripe-woo' ), $order_id, $order->get_total() ) );
|
|
|
|
$request = [
|
|
'payment_method' => $payment_method->id,
|
|
'payment_method_types' => [ $this->payment_method_types ],
|
|
'amount' => $this->get_formatted_amount( $order->get_total() ),
|
|
'currency' => strtolower( $order->get_currency() ),
|
|
'description' => $this->get_order_description( $order ),
|
|
'customer' => $payment_method->customer,
|
|
'metadata' => $this->get_metadata( $order_id ),
|
|
];
|
|
|
|
if ( 'cpsw_sepa' !== $this->id ) {
|
|
$request['confirm'] = true;
|
|
}
|
|
|
|
if ( ! empty( $this->capture_method ) ) {
|
|
$request['capture_method'] = $this->capture_method;
|
|
}
|
|
|
|
if ( ! empty( trim( $this->statement_descriptor ) ) ) {
|
|
$request['statement_descriptor'] = $this->statement_descriptor;
|
|
}
|
|
|
|
$response = $this->create_payment_for_saved_payment_method( apply_filters( 'cpsw_saved_card_payment_intent_post_data', $request ) );
|
|
|
|
if ( $response['success'] ) {
|
|
$intent = $response['data'];
|
|
} else {
|
|
wc_add_notice( $response['message'], 'error' );
|
|
$intent = false;
|
|
}
|
|
|
|
if ( ! $intent ) {
|
|
return [
|
|
'result' => 'fail',
|
|
'redirect' => '',
|
|
];
|
|
}
|
|
|
|
$intent_data = [
|
|
'id' => $intent->id,
|
|
'client_secret' => $intent->client_secret,
|
|
];
|
|
|
|
$order->update_meta_data( '_cpsw_intent_secret', $intent_data );
|
|
$order->save();
|
|
|
|
if ( 'requires_action' === $intent->status || 'requires_confirmation' === $intent->status ) {
|
|
if ( isset( $intent->next_action->type ) && 'redirect_to_url' === $intent->next_action->type && ! empty( $intent->next_action->redirect_to_url->url ) ) {
|
|
return [
|
|
'result' => 'success',
|
|
'redirect' => $intent->next_action->redirect_to_url->url,
|
|
];
|
|
} else {
|
|
return apply_filters(
|
|
'cpsw_saved_card_payment_return_data',
|
|
[
|
|
'result' => 'success',
|
|
'redirect' => $this->get_return_url( $order ),
|
|
'payment_method' => $intent_data['id'],
|
|
'intent_secret' => $intent_data['client_secret'],
|
|
'save_card' => false,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( $intent->amount > 0 ) {
|
|
// Use the last charge within the intent to proceed.
|
|
$this->process_response( end( $intent->charges->data ), $order );
|
|
} else {
|
|
$order->payment_complete();
|
|
}
|
|
|
|
// Remove cart.
|
|
if ( isset( WC()->cart ) ) {
|
|
WC()->cart->empty_cart();
|
|
}
|
|
|
|
// Return thank you page redirect.
|
|
return [
|
|
'result' => 'success',
|
|
'redirect' => $this->get_return_url( $order ),
|
|
];
|
|
|
|
} catch ( Exception $e ) {
|
|
wc_add_notice( $e->getMessage(), 'error' );
|
|
|
|
/* translators: error message */
|
|
$order->update_status( 'failed' );
|
|
|
|
return [
|
|
'result' => 'fail',
|
|
'redirect' => '',
|
|
];
|
|
}
|
|
}
|
|
|
|
/** Get stripe order data
|
|
*
|
|
* @param int $order_id WooCommerce order id.
|
|
* @return void
|
|
* @since 1.3.0
|
|
*/
|
|
public function get_stripe_order_data( $order_id ) {
|
|
if ( apply_filters( 'cpsw_hide_stripe_order_data', false, $order_id ) ) {
|
|
return;
|
|
}
|
|
|
|
$order = wc_get_order( $order_id );
|
|
|
|
if ( $this->id !== $order->get_payment_method() || empty( $order->get_meta( '_cpsw_display_stripe_data' ) ) ) {
|
|
return;
|
|
}
|
|
|
|
$currency = $this->get_stripe_currency( $order );
|
|
$net = $this->get_stripe_net( $order );
|
|
$fees = $this->get_stripe_fee( $order );
|
|
|
|
if ( ! $currency || ! $net || ! $fees ) {
|
|
return;
|
|
}
|
|
|
|
?>
|
|
|
|
<tr>
|
|
<td class="label stripe-fees">
|
|
<?php echo wp_kses_post( wc_help_tip( __( 'Fee charged by stripe for this order.', 'checkout-plugins-stripe-woo' ) ) ); ?>
|
|
<?php esc_html_e( 'Stripe Fee:', 'checkout-plugins-stripe-woo' ); ?>
|
|
</td>
|
|
<td style='width:"1%";'></td>
|
|
<td class="total">
|
|
<?php echo wp_kses_post( wc_price( $fees, [ 'currency' => $currency ] ) ); ?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label stripe-payout">
|
|
<?php echo wp_kses_post( wc_help_tip( __( 'Net amount that will be credited to your Stripe bank account.', 'checkout-plugins-stripe-woo' ) ) ); ?>
|
|
<?php esc_html_e( 'Stripe Payout:', 'checkout-plugins-stripe-woo' ); ?>
|
|
</td>
|
|
<td style='width:"1%";'></td>
|
|
<td class="net">
|
|
<?php echo wp_kses_post( wc_price( $net, [ 'currency' => $currency ] ) ); ?>
|
|
</td>
|
|
</tr>
|
|
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Get stripe currency
|
|
*
|
|
* @param object $order WooCommerce Order.
|
|
* @return string
|
|
* @since 1.3.0
|
|
*/
|
|
public function get_stripe_currency( $order ) {
|
|
return $order->get_meta( '_cpsw_stripe_currency', true );
|
|
}
|
|
|
|
/**
|
|
* Get stripe fee amount
|
|
*
|
|
* @param object $order WooCommerce Order.
|
|
* @return string
|
|
* @since 1.3.0
|
|
*/
|
|
public function get_stripe_fee( $order ) {
|
|
return (float) $order->get_meta( '_cpsw_stripe_fee', true );
|
|
}
|
|
|
|
/**
|
|
* Get stripe net amount
|
|
*
|
|
* @param object $order WooCommerce Order.
|
|
* @return string
|
|
* @since 1.3.0
|
|
*/
|
|
public function get_stripe_net( $order ) {
|
|
return $order->get_meta( '_cpsw_stripe_net', true );
|
|
}
|
|
|
|
/**
|
|
* Update stripe transaction data in local database
|
|
*
|
|
* @param object $order WooCommerce order.
|
|
* @param array $data array of stripe meta_data to be added.
|
|
* @return void
|
|
* @since 1.3.0
|
|
*/
|
|
public function update_stripe_order_data( $order, $data ) {
|
|
foreach ( $data as $key => $value ) {
|
|
$order->update_meta_data( '_cpsw_stripe_' . $key, $value );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update stripe transaction data balance from balance transactions api
|
|
*
|
|
* @param object $order WooCommerce order.
|
|
* @param string $transaction_id stripe transaction id.
|
|
* @param boolean $initiate send true if this call will initiate balance transation update, use for intent creations only.
|
|
* @return void
|
|
* @since 1.3.0
|
|
*/
|
|
public function update_balance( $order, $transaction_id, $initiate = false ) {
|
|
if ( $initiate ) {
|
|
$order->update_meta_data( '_cpsw_display_stripe_data', true );
|
|
} elseif ( empty( $order->get_meta( '_cpsw_display_stripe_data' ) ) ) {
|
|
return;
|
|
}
|
|
|
|
$stripe = new Stripe_Api();
|
|
$response = $stripe->balance_transactions( 'retrieve', [ $transaction_id ] );
|
|
$balance = $response['success'] ? $response['data'] : false;
|
|
|
|
if ( ! $balance ) {
|
|
Logger::error( __( 'Unable to update stripe transaction balance', 'checkout-plugins-stripe-woo' ) );
|
|
return;
|
|
}
|
|
|
|
$fee_refund = ! empty( $balance->fee ) ? $this->format_amount( $order, $balance->fee ) : 0;
|
|
$net_refund = ! empty( $balance->net ) ? $this->format_amount( $order, $balance->net ) : 0;
|
|
|
|
$fee = (float) $this->get_stripe_fee( $order ) + (float) $fee_refund;
|
|
$net = (float) $this->get_stripe_net( $order ) + (float) $net_refund;
|
|
$currency = ! empty( $balance->currency ) ? strtoupper( $balance->currency ) : null;
|
|
|
|
$data = [
|
|
'fee' => $fee,
|
|
'net' => $net,
|
|
'currency' => $currency,
|
|
];
|
|
|
|
$this->update_stripe_order_data( $order, $data );
|
|
|
|
if ( is_callable( [ $order, 'save' ] ) ) {
|
|
$order->save();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format amount from stripe as per currency
|
|
*
|
|
* @param object $order WooCommerce order object.
|
|
* @param int $amount fee or net amount to be formatted.
|
|
* @return float
|
|
* @since 1.3.0
|
|
*/
|
|
public function format_amount( $order, $amount ) {
|
|
if ( ! is_object( $order ) ) {
|
|
return;
|
|
}
|
|
$amount = $this->get_original_amount( $amount, $order->get_currency() );
|
|
|
|
return number_format( $amount, 2, '.', '' );
|
|
}
|
|
|
|
/**
|
|
* Returns amount received from stripe in WooCommerce as per currency
|
|
*
|
|
* @since 1.3.0
|
|
*
|
|
* @param string $total amount to be processed.
|
|
* @param string $currency transaction currency.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_original_amount( $total, $currency = '' ) {
|
|
if ( ! $currency ) {
|
|
$currency = get_woocommerce_currency();
|
|
}
|
|
|
|
if ( in_array( strtoupper( $currency ), self::$zero_currencies, true ) ) {
|
|
// Zero decimal currencies accepted by stripe.
|
|
return absint( $total );
|
|
} else {
|
|
return (float) wc_format_decimal( ( (float) $total / 100 ), wc_get_price_decimals() ); // In cents.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create payment for saved payment method, and this function
|
|
* useful for CartFlows.
|
|
*
|
|
* @param array $request_args arguments.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function create_payment_for_saved_payment_method( $request_args ) {
|
|
$stripe_api = new Stripe_Api();
|
|
return $stripe_api->payment_intents( 'create', [ $request_args ] );
|
|
}
|
|
|
|
/** Wrapper function to update stripe balance transaction data in order meta
|
|
*
|
|
* @param object $order WooCommerce Order.
|
|
* @param string $intent_id associated stripe intent id.
|
|
* @param boolean $initiate send true if this call will initiate balance transaction update, use for intent creations only.
|
|
* @return void
|
|
* @since 1.3.0
|
|
*/
|
|
public function update_stripe_balance( $order, $intent_id, $initiate = false ) {
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->payment_intents( 'retrieve', [ $intent_id ] );
|
|
$intent = $response['success'] ? $response['data'] : false;
|
|
|
|
if ( $intent ) {
|
|
|
|
$charge_obj = end( $intent->charges->data );
|
|
// Update payout details.
|
|
if ( isset( $charge_obj->balance_transaction ) ) {
|
|
$this->update_balance( $order, $charge_obj->balance_transaction, $initiate );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get stripe default currency.
|
|
*
|
|
* @since 1.4.0
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function get_stripe_default_currency() {
|
|
if (
|
|
empty( Helper::get_setting( 'cpsw_account_id' ) ) ||
|
|
! isset( $this->match_stripe_currency )
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
$account_default_currency = get_transient( 'cpsw_stripe_account_default_currency' );
|
|
|
|
if ( false === $account_default_currency ) {
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->accounts( 'retrieve', [ Helper::get_setting( 'cpsw_account_id' ) ] );
|
|
$account_info = $response['success'] ? $response['data'] : false;
|
|
|
|
if ( ! $account_info ) {
|
|
return false;
|
|
}
|
|
|
|
$account_default_currency = strtolower( $account_info->default_currency );
|
|
delete_transient( 'cpsw_stripe_account_default_currency' );
|
|
set_transient( 'cpsw_stripe_account_default_currency', $account_default_currency, 60 * MINUTE_IN_SECONDS );
|
|
}
|
|
|
|
return array_merge( $this->match_stripe_currency, [ $account_default_currency ] );
|
|
}
|
|
|
|
/**
|
|
* Checks to see if request is invalid and that
|
|
* they are worth retrying.
|
|
*
|
|
* @since 1.4.2
|
|
* @param obj $error Stripe return error data.
|
|
*/
|
|
public function is_retryable_error( $error ) {
|
|
return (
|
|
'invalid_request_error' === $error->type ||
|
|
'idempotency_error' === $error->type ||
|
|
'rate_limit_error' === $error->type ||
|
|
'api_connection_error' === $error->type ||
|
|
'api_error' === $error->type
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates setup intent for update payment method
|
|
*
|
|
* @return void
|
|
*/
|
|
public function create_setup_intent() {
|
|
if ( ! isset( $_POST['_security'] ) || ! wp_verify_nonce( sanitize_text_field( $_POST['_security'] ), 'cpsw_js_error_nonce' ) ) {
|
|
wp_send_json_error( [ 'message' => __( 'Invalid Nonce', 'checkout-plugins-stripe-woo' ) ] );
|
|
}
|
|
|
|
if ( empty( $_POST['paymentMethod'] ) ) {
|
|
wp_send_json_error( [ 'message' => __( 'Payment Method not provided', 'checkout-plugins-stripe-woo' ) ] );
|
|
}
|
|
|
|
$stripe_api = new Stripe_Api();
|
|
$response = $stripe_api->setup_intents( 'create', [ [ 'payment_method_types' => [ 'card' ] ] ] );
|
|
$response = $response['success'] ? $response['data'] : false;
|
|
wp_send_json_success( [ 'client_secret' => $response->client_secret ] );
|
|
exit();
|
|
}
|
|
}
|