Merge pull request #10260 from justinshreve/token-api

Payment Token API
This commit is contained in:
Justin Shreve 2016-03-04 04:34:41 -08:00
commit 927941e2ca
36 changed files with 2358 additions and 115 deletions

View File

@ -0,0 +1,77 @@
/*global jQuery, woocommerceTokenizationParams */
jQuery( function( $ ) {
var wcTokenizationForm = {
gatewayID: woocommerceTokenizationParams.gatewayID,
userLoggedIn: woocommerceTokenizationParams.userLoggedIn,
hideForm: function() {
$( '#wc-' + this.gatewayID + '-cc-form, #wc-' + this.gatewayID + '-echeck-form' ).hide();
},
showForm: function() {
$( '#wc-' + this.gatewayID + '-cc-form, #wc-' + this.gatewayID + '-echeck-form' ).show();
},
showSaveNewCheckbox: function() {
$( '#wc-' + this.gatewayID + '-new-payment-method-wrap' ).show();
},
hideSaveNewCheckbox: function() {
$( '#wc-' + this.gatewayID + '-new-payment-method-wrap' ).hide();
},
showSaveNewCheckboxForLoggedInOnly: function() {
if ( this.userLoggedIn ) {
$( '#wc-' + this.gatewayID + '-new-payment-method-wrap' ).show();
} else {
$( '#wc-' + this.gatewayID + '-new-payment-method-wrap' ).hide();
}
}
};
$( document.body ).on( 'updated_checkout', function() {
// Make sure a radio button (1st) is selected if there is no is_default for this payment method..
if ( ! $( 'input[name="wc-' + woocommerceTokenizationParams.gatewayID + '-payment-token"]' ).is( ':checked' ) ) {
$( 'input:radio[name="wc-' + woocommerceTokenizationParams.gatewayID + '-payment-token"]:first' ).attr( 'checked', true );
if ( 'new' === $( 'input:radio[name="wc-' + woocommerceTokenizationParams.gatewayID + '-payment-token"]:first' ).val() ) {
wcTokenizationForm.showForm();
wcTokenizationForm.showSaveNewCheckboxForLoggedInOnly();
} else {
wcTokenizationForm.hideForm();
wcTokenizationForm.hideSaveNewCheckbox();
}
} else {
wcTokenizationForm.hideForm();
wcTokenizationForm.hideSaveNewCheckbox();
}
// When a radio button is changed, make sure to show/hide our new CC info area
$( 'input[name="wc-' + woocommerceTokenizationParams.gatewayID + '-payment-token"]' ).change( function () {
if ( 'new' === $( 'input[name="wc-' + woocommerceTokenizationParams.gatewayID + '-payment-token"]:checked' ).val() ) {
wcTokenizationForm.showForm();
wcTokenizationForm.showSaveNewCheckboxForLoggedInOnly();
} else {
wcTokenizationForm.hideForm();
wcTokenizationForm.hideSaveNewCheckbox();
}
} );
// OR if create account is checked
$ ( 'input#createaccount' ).change( function() {
if ( $( this ).is( ':checked' ) ) {
wcTokenizationForm.showSaveNewCheckbox();
} else {
wcTokenizationForm.hideSaveNewCheckbox();
}
} );
// Don't show the "use new" radio button if we are a guest or only have one method..
if ( 0 === $( '#wc-' + woocommerceTokenizationParams.gatewayID + '-method-count' ).data( 'count' ) || ! woocommerceTokenizationParams.userLoggedIn ) {
$( '.wc-' + woocommerceTokenizationParams.gatewayID + '-payment-form-new-checkbox-wrap' ).hide();
}
} );
} );

View File

@ -0,0 +1 @@
jQuery(function(a){var b={gatewayID:woocommerceTokenizationParams.gatewayID,userLoggedIn:woocommerceTokenizationParams.userLoggedIn,hideForm:function(){a("#wc-"+this.gatewayID+"-cc-form, #wc-"+this.gatewayID+"-echeck-form").hide()},showForm:function(){a("#wc-"+this.gatewayID+"-cc-form, #wc-"+this.gatewayID+"-echeck-form").show()},showSaveNewCheckbox:function(){a("#wc-"+this.gatewayID+"-new-payment-method-wrap").show()},hideSaveNewCheckbox:function(){a("#wc-"+this.gatewayID+"-new-payment-method-wrap").hide()},showSaveNewCheckboxForLoggedInOnly:function(){this.userLoggedIn?a("#wc-"+this.gatewayID+"-new-payment-method-wrap").show():a("#wc-"+this.gatewayID+"-new-payment-method-wrap").hide()}};a(document.body).on("updated_checkout",function(){a('input[name="wc-'+woocommerceTokenizationParams.gatewayID+'-payment-token"]').is(":checked")?(b.hideForm(),b.hideSaveNewCheckbox()):(a('input:radio[name="wc-'+woocommerceTokenizationParams.gatewayID+'-payment-token"]:first').attr("checked",!0),"new"===a('input:radio[name="wc-'+woocommerceTokenizationParams.gatewayID+'-payment-token"]:first').val()?(b.showForm(),b.showSaveNewCheckboxForLoggedInOnly()):(b.hideForm(),b.hideSaveNewCheckbox())),a('input[name="wc-'+woocommerceTokenizationParams.gatewayID+'-payment-token"]').change(function(){"new"===a('input[name="wc-'+woocommerceTokenizationParams.gatewayID+'-payment-token"]:checked').val()?(b.showForm(),b.showSaveNewCheckboxForLoggedInOnly()):(b.hideForm(),b.hideSaveNewCheckbox())}),a("input#createaccount").change(function(){a(this).is(":checked")?b.showSaveNewCheckbox():b.hideSaveNewCheckbox()}),0!==a("#wc-"+woocommerceTokenizationParams.gatewayID+"-method-count").data("count")&&woocommerceTokenizationParams.userLoggedIn||a(".wc-"+woocommerceTokenizationParams.gatewayID+"-payment-form-new-checkbox-wrap").hide()})});

View File

@ -144,6 +144,39 @@ abstract class WC_Abstract_Order {
}
}
/**
* Returns a list of all payment tokens associated with the current order
*
* @since 2.6
* @return array An array of payment token objects
*/
public function get_payment_tokens() {
return WC_Payment_Tokens::get_order_tokens( $this->id );
}
/**
* Add a payment token to an order
*
* @since 2.6
* @param WC_Payment_Token $token Payment token object
* @return boolean True if the token was added, false if not
*/
public function add_payment_token( $token ) {
if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
return false;
}
$token_ids = get_post_meta( $this->id, '_payment_tokens', true );
if ( empty ( $token_ids ) ) {
$token_ids = array();
}
$token_ids[] = $token->get_id();
update_post_meta( $this->id, '_payment_tokens', $token_ids );
do_action( 'woocommerce_payment_token_added_to_order', $this->id, $token->get_id(), $token, $token_ids );
return true;
}
/**
* Set the payment method for the order.
*

View File

@ -102,6 +102,36 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
*/
public $view_transaction_url = '';
/**
* Optional label to show for "new payment method" in the payment
* method/token selection radio selection.
* @var string
*/
public $new_method_label = '';
/**
* Contains a users saved tokens for this gateway.
* @var array
*/
protected $tokens = array();
/**
* Returns a users saved tokens for this gateway.
* @since 2.6.0
* @return array
*/
public function get_tokens() {
if ( sizeof( $this->tokens ) > 0 ) {
return $this->tokens;
}
if ( is_user_logged_in() && $this->supports( 'tokenization' ) && is_checkout() ) {
$this->tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), $this->id );
}
return $this->tokens;
}
/**
* Return the title for admin screens.
* @return string
@ -306,13 +336,12 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
* Override this in your gateway if you have some.
*/
public function payment_fields() {
if ( $description = $this->get_description() ) {
echo wpautop( wptexturize( $description ) );
}
if ( $this->supports( 'default_credit_card_form' ) ) {
$this->credit_card_form();
$this->credit_card_form(); // Deprecated, will be removed in a future version.
}
}
@ -331,48 +360,136 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
}
/**
* Core credit card form which gateways can used if needed.
*
* Enqueues our tokenization script to handle some of the new form options.
* @since 2.6.0
*/
public function tokenization_script() {
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_enqueue_script(
'woocommerce-tokenization-form',
plugins_url( '/assets/js/frontend/tokenization-form' . $suffix . '.js', WC_PLUGIN_FILE ),
array( 'jquery' ),
WC()->version
);
wp_localize_script( 'woocommerce-tokenization-form', 'woocommerceTokenizationParams', array(
'gatewayID' => $this->id,
'userLoggedIn' => (bool) is_user_logged_in(),
) );
}
/**
* Grab and display our saved payment methods.
* @since 2.6.0
*/
public function saved_payment_methods() {
$html = '<p>';
foreach ( $this->get_tokens() as $token ) {
$html .= $this->saved_payment_method( $token );
}
$html .= '</p><span id="wc-' . esc_attr( $this->id ) . '-method-count" data-count="' . esc_attr( count( $this->get_tokens() ) ) . '"></span>';
$html .= '<div class="clear"></div>';
echo apply_filters( 'wc_payment_gateway_form_saved_payment_methods_html', $html, $this );
}
/**
* Outputs a saved payment method from a token.
* @since 2.6.0
* @param WC_Payment_Token $token Payment Token
* @return string Generated payment method HTML
*/
public function saved_payment_method( $token ) {
$html = sprintf(
'<input type="radio" id="wc-%1$s-payment-token-%2$s" name="wc-%1$s-payment-token" style="width:auto;" class="wc-gateway-payment-token wc-%1$s-payment-token" value="%2$s" %3$s/>',
esc_attr( $this->id ),
esc_attr( $token->get_id() ),
checked( $token->is_default(), true, false )
);
$html .= sprintf( '<label class="wc-gateway-payment-form-saved-payment-method wc-gateway-payment-token-label" for="wc-%s-payment-token-%s">',
esc_attr( $this->id ),
esc_attr( $token->get_id() )
);
$html .= $this->saved_payment_method_title( $token );
$html .= '</label><br />';
return apply_filters( 'wc_payment_gateway_form_saved_payment_method_html', $html, $token, $this );
}
/**
* Outputs a saved payment method's title based on the passed token.
* @since 2.6.0
* @param WC_Payment_Token $token Payment Token
* @return string Generated payment method title HTML
*/
public function saved_payment_method_title( $token ) {
if ( 'CC' == $token->get_type() && is_callable( array( $token, 'get_card_type' ) ) ) {
$type = esc_html__( wc_get_credit_card_type_label( $token->get_card_type() ), 'woocommerce' );
} else if ( 'eCheck' === $token->get_type() ) {
$type = esc_html__( 'eCheck', 'woocommerce' );
}
$type = apply_filters( 'wc_payment_gateway_form_saved_payment_method_title_type_html', $type, $token, $this );
$title = $type;
if ( is_callable( array( $token, 'get_last4' ) ) ) {
$title .= '&nbsp;' . sprintf( esc_html__( 'ending in %s', 'woocommerce' ), $token->get_last4() );
}
if ( is_callable( array( $token, 'get_expiry_month' ) ) && is_callable( array( $token, 'get_expiry_year' ) ) ) {
$title .= ' ' . sprintf( esc_html__( '(expires %s)', 'woocommerce' ), $token->get_expiry_month() . '/' . substr( $token->get_expiry_year(), 2 ) );
}
return apply_filters( 'wc_payment_gateway_form_saved_payment_method_title_html', $title, $token, $this );
}
/**
* Outputs a checkbox for saving a new payment method to the database.
* @since 2.6.0
*/
public function save_payment_method_checkbox() {
$html = sprintf(
'<p class="form-row" id="wc-%s-new-payment-method-wrap">',
esc_attr( $this->id )
);
$html .= sprintf(
'<input name="wc-%1$s-new-payment-method" id="wc-%1$s-new-payment-method" type="checkbox" value="true" style="width:auto;"/>',
esc_attr( $this->id )
);
$html .= sprintf(
'<label for="wc-%s-new-payment-method" style="display:inline;">%s</label>',
esc_attr( $this->id ),
esc_html__( 'Save to Account', 'woocommerce' )
);
$html .= '</p><div class="clear"></div>';
echo $html;
}
/**
* Displays a radio button for entering a new payment method (new CC details) instead of using a saved method.
* Only displayed when a gateway supports tokenization.
* @since 2.6.0
*/
public function use_new_payment_method_checkbox() {
$label = ( ! empty( $this->new_method_label ) ? esc_html( $this->new_method_label ) : esc_html__( 'Use a new payment method', 'woocommerce' ) );
$html = '<input type="radio" id="wc-' . esc_attr( $this->id ). '-new" name="wc-' . esc_attr( $this->id ) . '-payment-token" value="new" style="width:auto;">';
$html .= '<label class="wc-' . esc_attr( $this->id ) . '-payment-form-new-checkbox wc-gateway-payment-token-label" for="wc-' . esc_attr( $this->id ) . '-new">';
$html .= apply_filters( 'woocommerce_payment_gateway_form_new_method_label', $label, $this );
$html .= '</label>';
echo '<div class="wc-' . esc_attr( $this->id ) . '-payment-form-new-checkbox-wrap">' . $html . '</div>';
}
/**
* Core credit card form which gateways can used if needed. Deprecated - inheirt WC_Payment_Gateway_CC instead.
* @param array $args
* @param array $fields
*/
public function credit_card_form( $args = array(), $fields = array() ) {
wp_enqueue_script( 'wc-credit-card-form' );
$default_args = array(
'fields_have_names' => true, // Some gateways like stripe don't need names as the form is tokenized.
);
$args = wp_parse_args( $args, apply_filters( 'woocommerce_credit_card_form_args', $default_args, $this->id ) );
$default_fields = array(
'card-number-field' => '<p class="form-row form-row-wide">
<label for="' . esc_attr( $this->id ) . '-card-number">' . __( 'Card Number', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-number" class="input-text wc-credit-card-form-card-number" type="text" maxlength="20" autocomplete="off" placeholder="•••• •••• •••• ••••" name="' . ( $args['fields_have_names'] ? $this->id . '-card-number' : '' ) . '" />
</p>',
'card-expiry-field' => '<p class="form-row form-row-first">
<label for="' . esc_attr( $this->id ) . '-card-expiry">' . __( 'Expiry (MM/YY)', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-expiry" class="input-text wc-credit-card-form-card-expiry" type="text" autocomplete="off" placeholder="' . esc_attr__( 'MM / YY', 'woocommerce' ) . '" name="' . ( $args['fields_have_names'] ? $this->id . '-card-expiry' : '' ) . '" />
</p>',
'card-cvc-field' => '<p class="form-row form-row-last">
<label for="' . esc_attr( $this->id ) . '-card-cvc">' . __( 'Card Code', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-cvc" class="input-text wc-credit-card-form-card-cvc" type="text" autocomplete="off" placeholder="' . esc_attr__( 'CVC', 'woocommerce' ) . '" name="' . ( $args['fields_have_names'] ? $this->id . '-card-cvc' : '' ) . '" />
</p>'
);
$fields = wp_parse_args( $fields, apply_filters( 'woocommerce_credit_card_form_fields', $default_fields, $this->id ) );
?>
<fieldset id="<?php echo $this->id; ?>-cc-form">
<?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
<?php
foreach ( $fields as $field ) {
echo $field;
}
?>
<?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
<div class="clear"></div>
</fieldset>
<?php
_deprecated_function( 'credit_card_form', '2.6', 'WC_Payment_Gateway_CC->form' );
$cc_form = new WC_Payment_Gateway_CC;
$cc_form->id = $this->id;
$cc_form->supports = $this->supports;
$cc_form->form();
}
}

View File

@ -0,0 +1,264 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WooCommerce Payment Token.
*
* Representation of a general payment token to be extended by individuals types of tokens
* examples: Credit Card, eCheck.
*
* @class WC_Payment_Token
* @since 2.6.0
* @package WooCommerce/Abstracts
* @category Abstract Class
* @author WooThemes
*/
abstract class WC_Payment_Token implements WC_Data {
/** @protected int Token ID. */
protected $id;
/** @protected array Core Token Data (stored in the payment_tokens table). */
protected $data;
/** @protected array Meta Token Data (extra data associated with a payment token, stored in the payment_token_meta table). */
protected $meta;
/**
* Initialize a payment token.
*
* These fields are accepted by all payment tokens:
* default - boolean Optional - Indicates this is the default payment token for a user
* token - string Required - The actual token to store
* gateway_id - string Required - Identifier for the gateway this token is associated with
* user_id - int Optional - ID for the user this token is associated with. 0 if this token is not associated with a user
*
* @since 2.6.0
* @param string $id Token ID
* @param array $data Core token data
* @param array $meta Meta token data
*/
public function __construct( $id = 0, $data = array(), $meta = array() ) {
$this->id = $id;
$this->data = $data;
$this->data['type'] = $this->type;
$this->meta = $meta;
}
/**
* Returns the payment token ID.
* @since 2.6.0
* @return ID Token ID
*/
public function get_id() {
return absint( $this->id );
}
/**
* Returns the raw payment token.
* @since 2.6.0
* @return string Raw token
*/
public function get_token() {
return $this->data['token'];
}
/**
* Set the raw payment token.
* @since 2.6.0
* @param string $token
*/
public function set_token( $token ) {
$this->data['token'] = $token;
}
/**
* Returns the type of this payment token (CC, eCheck, or something else).
* @since 2.6.0
* @return string Payment Token Type (CC, eCheck)
*/
public function get_type() {
return isset( $this->data['type'] ) ? $this->data['type'] : '';
}
/**
* Returns the user ID associated with the token or false if this token is not associated.
* @since 2.6.0
* @return int User ID if this token is associated with a user or 0 if no user is associated
*/
public function get_user_id() {
return ( isset( $this->data['user_id'] ) && $this->data['user_id'] > 0 ) ? absint( $this->data['user_id'] ) : 0;
}
/**
* Set the user ID for the user associated with this order.
* @since 2.6.0
* @param int $user_id
*/
public function set_user_id( $user_id ) {
$this->data['user_id'] = $user_id;
}
/**
* Returns the ID of the gateway associated with this payment token.
* @since 2.6.0
* @return string Gateway ID
*/
public function get_gateway_id() {
return $this->data['gateway_id'];
}
/**
* Set the gateway ID.
* @since 2.6.0
* @param string $gateway_id
*/
public function set_gateway_id( $gateway_id ) {
$this->data['gateway_id'] = $gateway_id;
}
/**
* Returns if the token is marked as default.
* @since 2.6.0
* @return boolean True if the token is default
*/
public function is_default() {
return ! empty( $this->data['is_default'] );
}
/**
* Marks the payment as default or non-default.
* @since 2.6.0
* @param boolean $is_default True or false
*/
public function set_default( $is_default ) {
$this->data['is_default'] = (bool) $is_default;
}
/**
* Returns a dump of the token data (combined data and meta).
* @since 2.6.0
* @return mixed array representation
*/
public function get_data() {
return array_merge( $this->data, array( 'meta' => $this->meta ) );
}
/**
* Validate basic token info (token and type are required).
* @since 2.6.0
* @return boolean True if the passed data is valid
*/
public function validate() {
if ( empty( $this->data['token'] ) ) {
return false;
}
if ( empty( $this->data['type'] ) ) {
return false;
}
return true;
}
/**
* Get a token from the database.
* @since 2.6.0
* @param int $token_id Token ID
*/
public function read( $token_id ) {
global $wpdb;
if ( $token = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d LIMIT 1;", $token_id ) ) ) {
$this->id = $token->token_id;
$token = (array) $token;
unset( $token['token_id'] );
$this->data = $token;
$meta = get_metadata( 'payment_token', $token_id );
$passed_meta = array();
if ( ! empty( $meta ) ) {
foreach( $meta as $meta_key => $meta_value ) {
$passed_meta[ $meta_key ] = $meta_value[0];
}
}
$this->meta = $passed_meta;
}
}
/**
* Update a payment token.
* @since 2.6.0
* @return True on success, false if validation failed and a payment token could not be updated
*/
public function update() {
if ( false === $this->validate() ) {
return false;
}
global $wpdb;
$wpdb->update( $wpdb->prefix . 'woocommerce_payment_tokens', $this->data, array( 'token_id' => $this->get_id() ) );
foreach ( $this->meta as $meta_key => $meta_value ) {
update_metadata( 'payment_token', $this->get_id(), $meta_key, $meta_value );
}
do_action( 'woocommerce_payment_token_updated', $this->get_id() );
return true;
}
/**
* Create a new payment token in the database.
* @since 2.6.0
* @return True on success, false if validation failed and a payment token could not be created
*/
public function create() {
if ( false === $this->validate() ) {
return false;
}
global $wpdb;
// Are there any other tokens? If not, set this token as default
if ( ! $this->is_default() && is_user_logged_in() ) {
$default_token = WC_Payment_Tokens::get_customer_default_token( get_current_user_id() );
if ( is_null( $default_token ) ) {
$this->set_default( true );
}
}
$wpdb->insert( $wpdb->prefix . 'woocommerce_payment_tokens', $this->data );
$this->id = $token_id = $wpdb->insert_id;
foreach ( $this->meta as $meta_key => $meta_value ) {
add_metadata( 'payment_token', $token_id, $meta_key, $meta_value, true );
}
do_action( 'woocommerce_payment_token_created', $token_id );
return true;
}
/**
* Saves a payment token to the database - does not require you to know if this is a new token or an update token.
* @since 2.6.0
* @return True on success, false if validation failed and a payment token could not be saved
*/
public function save() {
if ( $this->get_id() > 0 ) {
return $this->update();
} else {
return $this->create();
}
}
/**
* Remove a payment token from the database.
* @since 2.6.0
*/
public function delete() {
global $wpdb;
$this->read( $this->get_id() ); // Make sure we have a token to return after deletion
$wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokens', array( 'token_id' => $this->get_id() ), array( '%d' ) );
$wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokenmeta', array( 'payment_token_id' => $this->get_id() ), array( '%d' ) );
do_action( 'woocommerce_payment_token_deleted', $this->get_id(), $this );
}
}

View File

@ -201,6 +201,25 @@ class WC_Settings_Payment_Gateways extends WC_Settings_Page {
'desc_tip' => true,
),
array(
'title' => __( 'Delete Payment Method', 'woocommerce' ),
'desc' => __( 'Endpoint for the delete payment method page', 'woocommerce' ),
'id' => 'woocommerce_myaccount_delete_payment_method_endpoint',
'type' => 'text',
'default' => 'delete-payment-method',
'desc_tip' => true,
),
array(
'title' => __( 'Set Default Payment Method', 'woocommerce' ),
'desc' => __( 'Endpoint for the setting a default payment page', 'woocommerce' ),
'id' => 'woocommerce_myaccount_set_default_payment_method_endpoint',
'type' => 'text',
'default' => 'set-default-payment-method',
'desc_tip' => true,
),
array(
'type' => 'sectionend',
'id' => 'checkout_endpoint_options',

View File

@ -334,6 +334,8 @@ if ( ! defined( 'ABSPATH' ) ) {
'woocommerce_shipping_zones',
'woocommerce_shipping_zone_locations',
'woocommerce_shipping_zone_methods',
'woocommerce_payment_tokens',
'woocommerce_payment_tokenmeta',
);
foreach ( $tables as $table ) {

View File

@ -83,6 +83,8 @@ class WC_Autoloader {
$path = $this->include_path . 'admin/';
} elseif ( strpos( $class, 'wc_cli_' ) === 0 ) {
$path = $this->include_path . 'cli/';
} elseif ( strpos( $class, 'wc_payment_token_' ) === 0 ) {
$path = $this->include_path . 'payment-tokens/';
}
if ( empty( $path ) || ( ! $this->load_file( $path . $file ) && strpos( $class, 'wc_' ) === 0 ) ) {

View File

@ -359,17 +359,15 @@ class WC_Form_Handler {
$payment_method = wc_clean( $_POST['payment_method'] );
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
// Validate
$available_gateways[ $payment_method ]->validate_fields();
// Process
if ( wc_notice_count( 'wc_errors' ) == 0 ) {
$result = $available_gateways[ $payment_method ]->add_payment_method();
// Redirect to success/confirmation/payment page
if ( $result['result'] == 'success' ) {
wc_add_message( __( 'Payment method added.', 'woocommerce' ) );
wc_add_notice( __( 'Payment method added.', 'woocommerce' ) );
wp_redirect( $result['redirect'] );
exit();
}

View File

@ -484,6 +484,25 @@ CREATE TABLE {$wpdb->prefix}woocommerce_shipping_zone_methods (
method_id varchar(255) NOT NULL,
method_order bigint(20) NOT NULL,
PRIMARY KEY (instance_id)
) $collate;
CREATE TABLE {$wpdb->prefix}woocommerce_payment_tokens (
token_id bigint(20) NOT NULL auto_increment,
gateway_id varchar(255) NOT NULL,
token text NOT NULL,
user_id bigint(20) NOT NULL DEFAULT '0',
type varchar(255) NOT NULL,
is_default tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (token_id),
KEY user_id (user_id)
) $collate;
CREATE TABLE {$wpdb->prefix}woocommerce_payment_tokenmeta (
meta_id bigint(20) NOT NULL auto_increment,
payment_token_id bigint(20) NOT NULL,
meta_key varchar(255) NULL,
meta_value longtext NULL,
PRIMARY KEY (meta_id),
KEY payment_token_id (payment_token_id),
KEY meta_key (meta_key)
) $collate;
";

View File

@ -148,6 +148,8 @@ class WC_Payment_Gateways {
$_available_gateways[ $gateway->id ] = $gateway;
} else if( $gateway->supports( 'add_payment_method' ) ) {
$_available_gateways[ $gateway->id ] = $gateway;
} else if ( $gateway->supports( 'tokenization' ) ) {
$_available_gateways[ $gateway->id ] = $gateway;
}
}
}
@ -166,7 +168,14 @@ class WC_Payment_Gateways {
return;
}
$current = WC()->session->get( 'chosen_payment_method' );
if ( is_user_logged_in() ) {
$default_token = WC_Payment_Tokens::get_customer_default_token( get_current_user_id() );
if ( ! is_null( $default_token ) ) {
$default_token_gateway = $default_token->get_gateway_id();
}
}
$current = ( isset( $default_token_gateway ) ? $default_token_gateway : WC()->session->get( 'chosen_payment_method' ) );
if ( $current && isset( $gateways[ $current ] ) ) {
$current_gateway = $gateways[ $current ];

View File

@ -0,0 +1,198 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WooCommerce Payment Tokens.
*
* An API for storing and managing tokens for gateways and customers.
*
* @class WC_Payment_Tokens
* @since 2.6.0
* @package WooCommerce/Classes
* @category Class
* @author WooThemes
*/
class WC_Payment_Tokens {
/**
* Returns an array of payment token objects associated with the passed customer ID.
* @since 2.6.0
* @param int $customer_id Customer ID
* @param string $gateway Optional Gateway ID for getting tokens for a specific gateway
* @return array Array of token objects
*/
public static function get_customer_tokens( $customer_id, $gateway_id = '' ) {
if ( $customer_id < 1 ) {
return array();
}
global $wpdb;
$token_results = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE user_id = %d",
$customer_id
) );
if ( empty( $token_results ) ) {
return array();
}
$tokens = array();
foreach ( $token_results as $token_result ) {
if ( empty( $gateway_id ) || $gateway_id === $token_result->gateway_id ) {
$_token = self::get( $token_result->token_id, $token_result );
if ( ! empty( $_token ) ) {
$tokens[ $token_result->token_id ] = $_token;
}
}
}
return apply_filters( 'woocommerce_get_customer_payment_tokens', $tokens, $customer_id );
}
/**
* Returns a customers default token or NULL if there is no default token.
* @since 2.6.0
* @param int $customer_id
* @return WC_Payment_Token|null
*/
public static function get_customer_default_token( $customer_id ) {
if ( $customer_id < 1 ) {
return null;
}
global $wpdb;
$token = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE user_id = %d AND is_default = 1",
$customer_id
) );
if ( $token ) {
return self::get( $token->token_id, $token );
} else {
return null;
}
}
/**
* Returns an array of payment token objects associated with the passed order ID.
* @since 2.6.0
* @param int $order_id Order ID
* @return array Array of token objects
*/
public static function get_order_tokens( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return array();
}
$token_ids = get_post_meta( $order_id, '_payment_tokens', true );
if ( empty ( $token_ids ) ) {
return array();
}
global $wpdb;
$token_ids_as_string = implode( ',', array_map( 'intval', $token_ids ) );
$token_results = $wpdb->get_results(
"SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id IN ( {$token_ids_as_string} )"
);
if ( empty( $token_results ) ) {
return array();
}
$tokens = array();
foreach ( $token_results as $token_result ) {
$_token = self::get( $token_result->token_id, $token_result );
if ( ! empty( $_token ) ) {
$tokens[ $token_result->token_id ] = $_token;
}
}
return apply_filters( 'woocommerce_get_order_payment_tokens', $tokens, $order_id );
}
/**
* Get a token object by ID.
* @since 2.6.0
* @param int $token_id Token ID
* @return WC_Payment_Token|null Returns a valid payment token or null if no token can be found
*/
public static function get( $token_id, $token_result = null ) {
global $wpdb;
if ( is_null( $token_result ) ) {
$token_result = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d",
$token_id
) );
// Still empty? Token doesn't exist? Don't continue
if ( empty( $token_result ) ) {
return null;
}
}
$token_class = 'WC_Payment_Token_' . $token_result->type;
if ( class_exists( $token_class ) ) {
$meta = get_metadata( 'payment_token', $token_id );
$passed_meta = array();
if ( ! empty( $meta ) ) {
foreach( $meta as $meta_key => $meta_value ) {
$passed_meta[ $meta_key ] = $meta_value[0];
}
}
return new $token_class( $token_id, (array) $token_result, $passed_meta );
}
}
/**
* Remove a payment token from the database by ID.
* @since 2.6.0
* @param WC_Payment_Token $token_id Token ID
*/
public static function delete( $token_id ) {
$type = self::get_token_type_by_id( $token_id );
if ( ! empty ( $type ) ) {
$class = 'WC_Payment_Token_' . $type;
$token = new $class( $token_id );
$token->delete();
}
}
/**
* Loops through all of a users payment tokens and sets is_default to false for all but a specific token.
* @since 2.6.0
* @param int $user_id User to set a default for
* @param int $token_id The ID of the token that should be default
*/
public static function set_users_default( $user_id, $token_id ) {
$users_tokens = self::get_customer_tokens( $user_id );
foreach ( $users_tokens as $token ) {
if ( $token_id === $token->get_id() ) {
$token->set_default( true );
} else {
$token->set_default( false );
}
$token->update();
}
}
/**
* Returns what type (credit card, echeck, etc) of token a token is by ID.
* @since 2.6.0
* @param int $token_id Token ID
* @return string Type
*/
public static function get_token_type_by_id( $token_id ) {
global $wpdb;
$type = $wpdb->get_var( $wpdb->prepare(
"SELECT type FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d",
$token_id
) );
return $type;
}
}

View File

@ -65,7 +65,6 @@ class WC_Query {
// Checkout actions.
'order-pay' => get_option( 'woocommerce_checkout_pay_endpoint', 'order-pay' ),
'order-received' => get_option( 'woocommerce_checkout_order_received_endpoint', 'order-received' ),
// My account actions.
'orders' => get_option( 'woocommerce_myaccount_orders_endpoint', 'orders' ),
'view-order' => get_option( 'woocommerce_myaccount_view_order_endpoint', 'view-order' ),
@ -76,6 +75,8 @@ class WC_Query {
'lost-password' => get_option( 'woocommerce_myaccount_lost_password_endpoint', 'lost-password' ),
'customer-logout' => get_option( 'woocommerce_logout_endpoint', 'customer-logout' ),
'add-payment-method' => get_option( 'woocommerce_myaccount_add_payment_method_endpoint', 'add-payment-method' ),
'delete-payment-method' => get_option( 'woocommerce_myaccount_delete_payment_method_endpoint', 'delete-payment-method' ),
'set-default-payment-method' => get_option( 'woocommerce_myaccount_set_default_payment_method_endpoint', 'set-default-payment-method' ),
);
}

View File

@ -0,0 +1,85 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Credit Card Payment Gateway
*
* @since 2.6.0
* @package WooCommerce/Classes
* @author WooThemes
*/
abstract class WC_Payment_Gateway_CC extends WC_Payment_Gateway {
/**
* Builds our payment fields area - including tokenization fields and the actualy payment fields.
* If tokenization is displayed, just the fields will be displayed.
* @since 2.6.0
*/
public function payment_fields() {
$display_tokenization = $this->supports( 'tokenization' ) && is_checkout();
if ( $display_tokenization ) {
$this->tokenization_script();
if ( is_user_logged_in() ) {
$this->saved_payment_methods();
}
$this->use_new_payment_method_checkbox();
}
$this->form();
if ( $display_tokenization ) {
$this->save_payment_method_checkbox();
}
}
/**
* Outputs fields for entering credit card information.
* @since 2.6.0
*/
public function form() {
$html = '';
$fields = array();
$cvc_field = '<p class="form-row form-row-last">
<label for="' . esc_attr( $this->id ) . '-card-cvc">' . __( 'Card Code', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-cvc" class="input-text wc-credit-card-form-card-cvc" type="text" autocomplete="off" placeholder="' . esc_attr__( 'CVC', 'woocommerce' ) . '" name="' . esc_attr( $this->id ) . '-card-cvc" style="width:100px" />
</p>';
$default_fields = array(
'card-number-field' => '<p class="form-row form-row-wide">
<label for="' . esc_attr( $this->id ) . '-card-number">' . __( 'Card Number', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-number" class="input-text wc-credit-card-form-card-number" type="text" maxlength="20" autocomplete="off" placeholder="•••• •••• •••• ••••" name="' . esc_attr( $this->id ) . '-card-number" />
</p>',
'card-expiry-field' => '<p class="form-row form-row-first">
<label for="' . esc_attr( $this->id ) . '-card-expiry">' . __( 'Expiry (MM/YY)', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-card-expiry" class="input-text wc-credit-card-form-card-expiry" type="text" autocomplete="off" placeholder="' . esc_attr__( 'MM / YY', 'woocommerce' ) . '" name="' . esc_attr( $this->id ) . '-card-expiry" />
</p>'
);
if ( ! $this->supports( 'credit_card_form_cvc_on_saved_method' ) ) {
$default_fields['card-cvc-field'] = $cvc_field;
}
$fields = wp_parse_args( $fields, apply_filters( 'woocommerce_credit_card_form_fields', $default_fields, $this->id ) );
?>
<fieldset id="wc-<?php echo esc_attr( $this->id ); ?>-cc-form" class='wc-credit-card-form'>
<?php do_action( 'woocommerce_credit_card_form_start', $this->id ); ?>
<?php
foreach ( $fields as $field ) {
echo $field;
}
?>
<?php do_action( 'woocommerce_credit_card_form_end', $this->id ); ?>
<div class="clear"></div>
</fieldset>
<?php
if ( $this->supports( 'credit_card_form_cvc_on_saved_method' ) ) {
echo '<fieldset>' . $cvc_field . '</fieldset>';
}
}
}

View File

@ -0,0 +1,72 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* eCheck Payment Gateway
*
* @since 2.6.0
* @package WooCommerce/Classes
* @author WooThemes
*/
abstract class WC_Payment_Gateway_eCheck extends WC_Payment_Gateway {
/**
* Builds our payment fields area - including tokenization fields and the actualy payment fields.
* If tokenization is displayed, just the fields will be displayed.
* @since 2.6.0
*/
public function payment_fields() {
$display_tokenization = $this->supports( 'tokenization' ) && is_checkout();
if ( $display_tokenization ) {
$this->tokenization_script();
if ( is_user_logged_in() ) {
$this->saved_payment_methods();
}
$this->use_new_payment_method_checkbox();
}
$this->form();
if ( $display_tokenization ) {
$this->save_payment_method_checkbox();
}
}
/**
* Outputs fields for entering eCheck information.
* @since 2.6.0
*/
public function form() {
$html = '';
$fields = array();
$default_fields = array(
'routing-number' => '<p class="form-row form-row-first">
<label for="' . esc_attr( $this->id ) . '-routing-number">' . __( 'Routing Number', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-routing-number" class="input-text wc-echeck-form-routing-number" type="text" maxlength="9" autocomplete="off" placeholder="•••••••••" name="' . esc_attr( $this->id ) . '-routing-number" />
</p>',
'account-number' => '<p class="form-row form-row-wide">
<label for="' . esc_attr( $this->id ) . '-account-number">' . __( 'Account Number', 'woocommerce' ) . ' <span class="required">*</span></label>
<input id="' . esc_attr( $this->id ) . '-account-number" class="input-text wc-echeck-form-account-number" type="text" autocomplete="off" name="' . esc_attr( $this->id ) . '-account-number" maxlength="17" />
</p>',
);
$fields = wp_parse_args( $fields, apply_filters( 'woocommerce_echeck_form_fields', $default_fields, $this->id ) );
?>
<fieldset id="<?php echo esc_attr( $this->id ); ?>-cc-form" class='wc-echeck-form'>
<?php do_action( 'woocommerce_echeck_form_start', $this->id ); ?>
<?php
foreach ( $fields as $field ) {
echo $field;
}
?>
<?php do_action( 'woocommerce_echeck_form_end', $this->id ); ?>
<div class="clear"></div>
</fieldset><?php
}
}

View File

@ -3,9 +3,9 @@
// Form handler
function simplifyFormHandler() {
var $form = $( 'form.checkout, form#order_review' );
var $form = $( 'form.checkout, form#order_review, form#add_payment_method' );
if ( $( '#payment_method_simplify_commerce' ).is( ':checked' ) ) {
if ( ( $( '#payment_method_simplify_commerce' ).is( ':checked' ) && $( '#wc-simplify_commerce-new' ).is( ':checked' ) ) || ( '1' === $( '#woocommerce_add_payment_method' ).val() ) ) {
if ( 0 === $( 'input.simplify-token' ).length ) {
@ -55,8 +55,9 @@
// Handle Simplify response
function simplifyResponseHandler( data ) {
var $form = $( 'form.checkout, form#order_review' ),
ccForm = $( '#simplify_commerce-cc-form' );
var $form = $( 'form.checkout, form#order_review, form#add_payment_method' ),
ccForm = $( '#wc-simplify_commerce-cc-form' );
if ( data.error ) {
@ -101,8 +102,13 @@
return simplifyFormHandler();
});
/* Pay Page Form */
$( 'form#add_payment_method' ).on( 'submit', function () {
return simplifyFormHandler();
});
/* Both Forms */
$( 'form.checkout, form#order_review' ).on( 'change', '#simplify_commerce-cc-form input', function() {
$( 'form.checkout, form#order_review, form#add_payment_method' ).on( 'change', '#wc-simplify_commerce-cc-form input', function() {
$( '.simplify-token' ).remove();
});

View File

@ -1 +1 @@
!function(a){function b(){var b=a("form.checkout, form#order_review");if(a("#payment_method_simplify_commerce").is(":checked")&&0===a("input.simplify-token").length){b.block({message:null,overlayCSS:{background:"#fff",opacity:.6}});var d=a("#simplify_commerce-card-number").val(),e=a("#simplify_commerce-card-cvc").val(),f=a.payment.cardExpiryVal(a("#simplify_commerce-card-expiry").val()),g=b.find("#billing_address_1").val(),h=b.find("#billing_address_2").val(),i=b.find("#billing_country").val(),j=b.find("#billing_state").val(),k=b.find("#billing_city").val(),l=b.find("#billing_postcode").val();return d=d.replace(/\s/g,""),SimplifyCommerce.generateToken({key:Simplify_commerce_params.key,card:{number:d,cvc:e,expMonth:f.month,expYear:f.year-2e3,addressLine1:g,addressLine2:h,addressCountry:i,addressState:j,addressZip:l,addressCity:k}},c),!1}return!0}function c(b){var c=a("form.checkout, form#order_review"),d=a("#simplify_commerce-cc-form");if(b.error){if(a(".woocommerce-error, .simplify-token",d).remove(),c.unblock(),"validation"===b.error.code){for(var e=b.error.fieldErrors,f=e.length,g="",h=0;f>h;h++)g+="<li>"+Simplify_commerce_params[e[h].field]+" "+Simplify_commerce_params.is_invalid+" - "+e[h].message+".</li>";d.prepend('<ul class="woocommerce-error">'+g+"</ul>")}}else d.append('<input type="hidden" class="simplify-token" name="simplify_token" value="'+b.id+'"/>'),c.submit()}a(function(){a(document.body).on("checkout_error",function(){a(".simplify-token").remove()}),a("form.checkout").on("checkout_place_order_simplify_commerce",function(){return b()}),a("form#order_review").on("submit",function(){return b()}),a("form.checkout, form#order_review").on("change","#simplify_commerce-cc-form input",function(){a(".simplify-token").remove()})})}(jQuery);
!function(a){function b(){var b=a("form.checkout, form#order_review, form#add_payment_method");if((a("#payment_method_simplify_commerce").is(":checked")&&a("#wc-simplify_commerce-new").is(":checked")||"1"===a("#woocommerce_add_payment_method").val())&&0===a("input.simplify-token").length){b.block({message:null,overlayCSS:{background:"#fff",opacity:.6}});var d=a("#simplify_commerce-card-number").val(),e=a("#simplify_commerce-card-cvc").val(),f=a.payment.cardExpiryVal(a("#simplify_commerce-card-expiry").val()),g=b.find("#billing_address_1").val(),h=b.find("#billing_address_2").val(),i=b.find("#billing_country").val(),j=b.find("#billing_state").val(),k=b.find("#billing_city").val(),l=b.find("#billing_postcode").val();return d=d.replace(/\s/g,""),SimplifyCommerce.generateToken({key:Simplify_commerce_params.key,card:{number:d,cvc:e,expMonth:f.month,expYear:f.year-2e3,addressLine1:g,addressLine2:h,addressCountry:i,addressState:j,addressZip:l,addressCity:k}},c),!1}return!0}function c(b){var c=a("form.checkout, form#order_review, form#add_payment_method"),d=a("#wc-simplify_commerce-cc-form");if(b.error){if(a(".woocommerce-error, .simplify-token",d).remove(),c.unblock(),"validation"===b.error.code){for(var e=b.error.fieldErrors,f=e.length,g="",h=0;f>h;h++)g+="<li>"+Simplify_commerce_params[e[h].field]+" "+Simplify_commerce_params.is_invalid+" - "+e[h].message+".</li>";d.prepend('<ul class="woocommerce-error">'+g+"</ul>")}}else d.append('<input type="hidden" class="simplify-token" name="simplify_token" value="'+b.id+'"/>'),c.submit()}a(function(){a(document.body).on("checkout_error",function(){a(".simplify-token").remove()}),a("form.checkout").on("checkout_place_order_simplify_commerce",function(){return b()}),a("form#order_review").on("submit",function(){return b()}),a("form#add_payment_method").on("submit",function(){return b()}),a("form.checkout, form#order_review, form#add_payment_method").on("change","#wc-simplify_commerce-cc-form input",function(){a(".simplify-token").remove()})})}(jQuery);

View File

@ -8,13 +8,13 @@ if ( ! defined( 'ABSPATH' ) ) {
* Simplify Commerce Gateway.
*
* @class WC_Gateway_Simplify_Commerce
* @extends WC_Payment_Gateway
* @extends WC_Payment_Gateway_CC
* @since 2.2.0
* @version 1.0.0
* @package WooCommerce/Classes/Payment
* @author WooThemes
*/
class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway_CC {
/**
* Constructor.
@ -23,6 +23,7 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
$this->id = 'simplify_commerce';
$this->method_title = __( 'Simplify Commerce', 'woocommerce' );
$this->method_description = __( 'Take payments via Simplify Commerce - uses simplify.js to create card tokens and the Simplify Commerce SDK. Requires SSL when sandbox is disabled.', 'woocommerce' );
$this->new_method_label = __( 'Use a new card', 'woocommerce' );
$this->has_fields = true;
$this->supports = array(
'subscriptions',
@ -37,6 +38,7 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
'subscription_date_changes',
'multiple_subscriptions',
'default_credit_card_form',
'tokenization',
'refunds',
'pre-orders'
);
@ -274,7 +276,7 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
}
if ( 'standard' == $this->mode ) {
$this->credit_card_form( array( 'fields_have_names' => false ) );
parent::payment_fields();
}
}
@ -282,7 +284,16 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
* Outputs scripts used for simplify payment.
*/
public function payment_scripts() {
if ( ! is_checkout() || ! $this->is_available() ) {
$load_scripts = false;
if ( is_checkout() ) {
$load_scripts = true;
}
if ( $this->is_available() ) {
$load_scripts = true;
}
if ( false === $load_scripts ) {
return;
}
@ -301,6 +312,98 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
) );
}
public function add_payment_method() {
if ( empty ( $_POST['simplify_token'] ) ) {
wc_add_notice( __( 'There was a problem adding this card.', 'woocommerce' ), 'error' );
return;
}
$cart_token = wc_clean( $_POST['simplify_token'] );
$customer_token = $this->get_users_token();
$current_user = wp_get_current_user();
$customer_info = array(
'email' => $current_user->user_email,
'name' => $current_user->display_name,
);
$token = $this->save_token( $customer_token, $cart_token, $customer_info );
if ( is_null( $token ) ) {
wc_add_notice( __( 'There was a problem adding this card.', 'woocommerce' ), 'error' );
return;
}
return array(
'result' => 'success',
'redirect' => wc_get_endpoint_url( 'payment-methods' ),
);
}
/**
* Actualy saves a customer token to the database.
*
* @param WC_Payment_Token $customer_token Payment Token
* @param string $cart_token CC Token
* @param array $customer_info 'email', 'name'
*/
public function save_token( $customer_token, $cart_token, $customer_info ) {
if ( ! is_null( $customer_token ) ) {
$customer = Simplify_Customer::findCustomer( $customer_token->get_token() );
$updates = array( 'token' => $cart_token );
$customer->setAll( $updates );
$customer->updateCustomer();
$customer = Simplify_Customer::findCustomer( $customer_token->get_token() ); // get updated customer with new set card
$token = $customer_token;
} else {
$customer = Simplify_Customer::createCustomer( array(
'token' => $cart_token,
'email' => $customer_info['email'],
'name' => $customer_info['name'],
) );
$token = new WC_Payment_Token_CC();
$token->set_token( $customer->id );
}
// If we were able to create an save our card, save the data on our side too
if ( is_object( $customer ) && '' != $customer->id ) {
$customer_properties = $customer->getProperties();
$card = $customer_properties['card'];
$token->set_gateway_id( $this->id );
$token->set_card_type( strtolower( $card->type ) );
$token->set_last4( $card->last4 );
$expiry_month = ( 1 === strlen( $card->expMonth ) ? '0' . $card->expMonth : $card->expMonth );
$token->set_expiry_month( $expiry_month );
$token->set_expiry_year( '20' . $card->expYear );
if ( is_user_logged_in() ) {
$token->set_user_id( get_current_user_id() );
}
$token->save();
return $token;
}
return null;
}
/**
* Process customer: updating or creating a new customer/saved CC
*
* @param WC_Order $order Order object
* @param WC_Payment_Token $customer_token Payment Token
* @param string $cart_token CC Token
*/
protected function process_customer( $order, $customer_token = null, $cart_token = '' ) {
// Are we saving a new payment method?
if ( is_user_logged_in() && isset( $_POST['wc-simplify_commerce-new-payment-method'] ) && true === (bool) $_POST['wc-simplify_commerce-new-payment-method'] ) {
$customer_info = array(
'email' => $order->billing_email,
'name' => trim( $order->get_formatted_billing_full_name() ),
);
$token = $this->save_token( $customer_token, $cart_token, $customer_info );
if ( ! is_null( $token ) ) {
$order->add_payment_token( $token );
}
}
}
/**
* Process standard payments.
*
@ -310,10 +413,10 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
* @uses Simplify_BadRequestException
* @return array
*/
protected function process_standard_payments( $order, $cart_token = '' ) {
protected function process_standard_payments( $order, $cart_token = '', $customer_token = '' ) {
try {
if ( empty( $cart_token ) ) {
if ( empty( $cart_token ) && empty( $customer_token ) ) {
$error_msg = __( 'Please make sure your card details have been entered correctly and that your browser supports JavaScript.', 'woocommerce' );
if ( 'yes' == $this->sandbox ) {
@ -323,26 +426,44 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
throw new Simplify_ApiException( $error_msg );
}
$payment = Simplify_Payment::createPayment( array(
'amount' => $order->order_total * 100, // In cents.
'token' => $cart_token,
'description' => sprintf( __( '%s - Order #%s', 'woocommerce' ), esc_html( get_bloginfo( 'name', 'display' ) ), $order->get_order_number() ),
'currency' => strtoupper( get_woocommerce_currency() ),
'reference' => $order->id
) );
// We need to figure out if we want to charge the card token (new unsaved token, no customer, etc)
// or the customer token (just saved method, previously saved method)
$pass_tokens = array();
$order_complete = $this->process_order_status( $order, $payment->id, $payment->paymentStatus, $payment->authCode );
if ( ! empty ( $cart_token ) ) {
$pass_tokens['token'] = $cart_token;
}
if ( ! empty ( $customer_token ) ) {
$pass_tokens['customer'] = $customer_token;
// Use the customer token only, since we already saved the (one time use) card token to the customer
if ( isset( $_POST['wc-simplify_commerce-new-payment-method'] ) && true === (bool) $_POST['wc-simplify_commerce-new-payment-method'] ) {
unset( $pass_tokens['token'] );
}
}
// Did we create an account and save a payment method? We might need to use the customer token instead of the card token
if ( isset( $_POST['createaccount'] ) && true === (bool) $_POST['createaccount'] && empty ( $customer_token ) ) {
$user_token = $this->get_users_token();
if ( ! is_null( $user_token ) ) {
$pass_tokens['customer'] = $user_token->get_token();
unset( $pass_tokens['token'] );
}
}
$payment_response = $this->do_payment( $order, $order->get_total(), $pass_tokens );
if ( is_wp_error( $payment_response ) ) {
throw new Exception( $payment_response->get_error_message() );
} else {
// Remove cart
WC()->cart->empty_cart();
if ( $order_complete ) {
// Return thank you page redirect
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
} else {
$order->add_order_note( __( 'Simplify payment declined', 'woocommerce' ) );
throw new Simplify_ApiException( __( 'Payment was declined - please try another card.', 'woocommerce' ) );
}
} catch ( Simplify_ApiException $e ) {
@ -361,6 +482,62 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
}
}
/**
* do payment function.
*
* @param WC_order $order
* @param int $amount (default: 0)
* @uses Simplify_BadRequestException
* @return bool|WP_Error
*/
public function do_payment( $order, $amount = 0, $token = array() ) {
if ( $amount * 100 < 50 ) {
return new WP_Error( 'simplify_error', __( 'Sorry, the minimum allowed order total is 0.50 to use this payment method.', 'woocommerce' ) );
}
try {
// Charge the customer
$data = array(
'amount' => $amount * 100, // In cents.
'description' => sprintf( __( '%s - Order #%s', 'woocommerce' ), esc_html( get_bloginfo( 'name', 'display' ) ), $order->get_order_number() ),
'currency' => strtoupper( get_woocommerce_currency() ),
'reference' => $order->id
);
$data = array_merge( $data, $token );
$payment = Simplify_Payment::createPayment( $data );
} catch ( Exception $e ) {
$error_message = $e->getMessage();
if ( $e instanceof Simplify_BadRequestException && $e->hasFieldErrors() && $e->getFieldErrors() ) {
$error_message = '';
foreach ( $e->getFieldErrors() as $error ) {
$error_message .= ' ' . $error->getFieldName() . ': "' . $error->getMessage() . '" (' . $error->getErrorCode() . ')';
}
}
$order->add_order_note( sprintf( __( 'Simplify payment error: %s', 'woocommerce' ), $error_message ) );
return new WP_Error( 'simplify_payment_declined', $e->getMessage(), array( 'status' => $e->getCode() ) );
}
if ( 'APPROVED' == $payment->paymentStatus ) {
// Payment complete
$order->payment_complete( $payment->id );
// Add order note
$order->add_order_note( sprintf( __( 'Simplify payment approved (ID: %s, Auth Code: %s)', 'woocommerce' ), $payment->id, $payment->authCode ) );
return true;
} else {
$order->add_order_note( __( 'Simplify payment declined', 'woocommerce' ) );
return new WP_Error( 'simplify_payment_declined', __( 'Payment was declined - please try another card.', 'woocommerce' ) );
}
}
/**
* Process standard payments.
*
@ -374,19 +551,52 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
);
}
protected function get_users_token() {
$customer_token = null;
if ( is_user_logged_in() ) {
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() ) ;
foreach ( $tokens as $token ) {
if ( $token->get_gateway_id() === $this->id ) {
$customer_token = $token;
break;
}
}
}
return $customer_token;
}
/**
* Process the payment.
*
* @param int $order_id
*/
public function process_payment( $order_id ) {
$cart_token = isset( $_POST['simplify_token'] ) ? wc_clean( $_POST['simplify_token'] ) : '';
$order = wc_get_order( $order_id );
if ( 'hosted' == $this->mode ) {
// Payment/CC form is hosted on Simplify
if ( 'hosted' === $this->mode ) {
return $this->process_hosted_payments( $order );
} else {
return $this->process_standard_payments( $order, $cart_token );
}
// New CC info was entered
if ( isset( $_POST['simplify_token'] ) ) {
$cart_token = wc_clean( $_POST['simplify_token'] );
$customer_token = $this->get_users_token();
$customer_token_value = ( ! is_null( $customer_token ) ? $customer_token->get_token() : '' );
$this->process_customer( $order, $customer_token, $cart_token );
return $this->process_standard_payments( $order, $cart_token, $customer_token_value );
}
// Possibly Create (or update) customer/save payment token, use an existing token, and then process the payment
if ( isset( $_POST['wc-simplify_commerce-payment-token'] ) && 'new' !== $_POST['wc-simplify_commerce-payment-token'] ) {
$token_id = wc_clean( $_POST['wc-simplify_commerce-payment-token'] );
$token = WC_Payment_Tokens::get( $token_id );
if ( $token->get_user_id() !== get_current_user_id() ) {
wc_add_notice( __( 'Please make sure your card details have been entered correctly and that your browser supports JavaScript.', 'woocommerce' ), 'error' );
return;
}
$this->process_customer( $order, $token );
return $this->process_standard_payments( $order, '', $token->get_token() );
}
}
@ -411,7 +621,8 @@ class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
'address-city' => $order->billing_city,
'address-state' => $order->billing_state,
'address-zip' => $order->billing_postcode,
'address-country' => $order->billing_country
'address-country' => $order->billing_country,
'operation' => 'create.token',
), $order->id );
return $args;

View File

@ -0,0 +1,139 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WooCommerce Credit Card Payment Token.
*
* Representation of a payment token for credit cards.
*
* @class WC_Payment_Token_CC
* @since 2.6.0
* @category PaymentTokens
* @package WooCommerce/PaymentTokens
* @author WooThemes
*/
class WC_Payment_Token_CC extends WC_Payment_Token {
/** @protected string Token Type String. */
protected $type = 'CC';
/**
* Validate credit card payment tokens.
*
* These fields are required by all credit card payment tokens:
* expiry_month - string Expiration date (MM) for the card
* expiry_year - string Expiration date (YYYY) for the card
* last4 - string Last 4 digits of the card
* card_type - string Card type (visa, mastercard, etc)
*
* @since 2.6.0
* @return boolean True if the passed data is valid
*/
public function validate() {
if ( false === parent::validate() ) {
return false;
}
if ( empty( $this->meta['last4'] ) ) {
return false;
}
if ( empty( $this->meta['expiry_year'] ) ) {
return false;
}
if ( empty( $this->meta['expiry_month'] ) ) {
return false;
}
if ( empty ( $this->meta['card_type'] ) ) {
return false;
}
if ( 4 !== strlen( $this->meta['expiry_year'] ) ) {
return false;
}
if ( 2 !== strlen( $this->meta['expiry_month'] ) ) {
return false;
}
return true;
}
/**
* Returns the card type (mastercard, visa, ...).
* @since 2.6.0
* @return string Card type
*/
public function get_card_type() {
return isset( $this->meta['card_type'] ) ? $this->meta['card_type'] : null;
}
/**
* Set the card type (mastercard, visa, ...).
* @since 2.6.0
* @param string $type
*/
public function set_card_type( $type ) {
$this->meta['card_type'] = $type;
}
/**
* Returns the card expiration year (YYYY).
* @since 2.6.0
* @return string Expiration year
*/
public function get_expiry_year() {
return isset( $this->meta['expiry_year'] ) ? $this->meta['expiry_year'] : null;
}
/**
* Set the expiration year for the card (YYYY format).
* @since 2.6.0
* @param string $year
*/
public function set_expiry_year( $year ) {
$this->meta['expiry_year'] = $year;
}
/**
* Returns the card expiration month (MM).
* @since 2.6.0
* @return string Expiration month
*/
public function get_expiry_month() {
return isset( $this->meta['expiry_month'] ) ? $this->meta['expiry_month'] : null;
}
/**
* Set the expiration month for the card (MM format).
* @since 2.6.0
* @param string $month
*/
public function set_expiry_month( $month ) {
$this->meta['expiry_month'] = $month;
}
/**
* Returns the last four digits.
* @since 2.6.0
* @return string Last 4 digits
*/
public function get_last4() {
return isset( $this->meta['last4'] ) ? $this->meta['last4'] : null;
}
/**
* Set the last four digits.
* @since 2.6.0
* @param string $last4
*/
public function set_last4( $last4 ) {
$this->meta['last4'] = $last4;
}
}

View File

@ -0,0 +1,61 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* WooCommerce eCheck Payment Token.
*
* Representation of a payment token for eChecks.
*
* @class WC_Payment_Token_eCheck
* @since 2.6.0
* @category PaymentTokens
* @package WooCommerce/PaymentTokens
* @author WooThemes
*/
class WC_Payment_Token_eCheck extends WC_Payment_Token {
/** @protected string Token Type String */
protected $type = 'eCheck';
/**
* Validate eCheck payment tokens.
*
* These fields are required by all eCheck payment tokens:
* last4 - string Last 4 digits of the check
*
* @since 2.6.0
* @return boolean True if the passed data is valid
*/
public function validate() {
if ( false === parent::validate() ) {
return false;
}
if ( empty( $this->meta['last4'] ) ) {
return false;
}
return true;
}
/**
* Returns the last four digits.
* @since 2.6.0
* @return string Last 4 digits
*/
public function get_last4() {
return isset( $this->meta['last4'] ) ? $this->meta['last4'] : null;
}
/**
* Set the last four digits.
* @since 2.6.0
* @param string $last4
*/
public function set_last4( $last4 ) {
$this->meta['last4'] = $last4;
}
}

View File

@ -344,4 +344,69 @@ class WC_Shortcode_My_Account {
}
}
/**
* Deletes a payment method from a users list and displays a message to the user
*
* @since 2.6
* @param int $id Payment Token ID
*/
public static function delete_payment_method( $id ) {
$token = WC_Payment_Tokens::get( $id );
if ( is_null( $token ) ) {
wc_add_notice( __( 'Invalid payment method', 'woocommerce' ), 'error' );
woocommerce_account_payment_methods();
return false;
}
if ( get_current_user_id() !== $token->get_user_id() ) {
wc_add_notice( __( 'Invalid payment method', 'woocommerce' ), 'error' );
woocommerce_account_payment_methods();
return false;
}
if ( false === wp_verify_nonce( $_REQUEST['_wpnonce'], 'delete-payment-method-' . $id ) ) {
wc_add_notice( __( 'Invalid payment method', 'woocommerce' ), 'error' );
woocommerce_account_payment_methods();
return false;
}
WC_Payment_Tokens::delete( $id );
wc_add_notice( __( 'Payment method deleted.', 'woocommerce' ) );
woocommerce_account_payment_methods();
}
/**
* Sets a payment method as default and displays a message to the user
*
* @since 2.6
* @param int $id Payment Token ID
*/
public static function set_default_payment_method( $id ) {
$token = WC_Payment_Tokens::get( $id );
if ( is_null( $token ) ) {
wc_add_notice( __( 'Invalid payment method', 'woocommerce' ), 'error' );
woocommerce_account_payment_methods();
return false;
}
if ( get_current_user_id() !== $token->get_user_id() ) {
wc_add_notice( __( 'Invalid payment method', 'woocommerce' ), 'error' );
woocommerce_account_payment_methods();
return false;
}
if ( false === wp_verify_nonce( $_REQUEST['_wpnonce'], 'set-default-payment-method-' . $id ) ) {
wc_add_notice( __( 'Invalid payment method', 'woocommerce' ), 'error' );
woocommerce_account_payment_methods();
return false;
}
WC_Payment_Tokens::set_users_default( $token->get_user_id(), intval( $id ) );
wc_add_notice( __( 'This payment method was successfully set as your default.', 'woocommerce' ) );
woocommerce_account_payment_methods();
}
}

View File

@ -208,3 +208,106 @@ function wc_get_account_payment_methods_columns() {
'actions' => '&nbsp;',
) );
}
/**
* Get My Account > Payment methods types
*
* @since 2.6.0
* @return array
*/
function wc_get_account_payment_methods_types() {
return apply_filters( 'woocommerce_payment_methods_types', array(
'cc' => __( 'Credit Card', 'woocommerce' ),
'echeck' => __( 'eCheck', 'woocommerce' ),
) );
}
/**
* Returns an array of a user's saved payments list for output on the account tab.
*
* @since 2.6
* @param array $list List of payment methods passed from wc_get_customer_saved_methods_list()
* @param int $customer_id The customer to fetch payment methods for
* @return array Filtered list of customers payment methods
*/
function wc_get_account_saved_payment_methods_list( $list, $customer_id ) {
$payment_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id );
foreach ( $payment_tokens as $payment_token ) {
$delete_url = wc_get_endpoint_url( 'delete-payment-method', $payment_token->get_id() );
$delete_url = wp_nonce_url( $delete_url, 'delete-payment-method-' . $payment_token->get_id() );
$set_default_url = wc_get_endpoint_url( 'set-default-payment-method', $payment_token->get_id() );
$set_default_url = wp_nonce_url( $set_default_url, 'set-default-payment-method-' . $payment_token->get_id() );
$type = strtolower( $payment_token->get_type() );
$list[ $type ][] = array(
'method' => array(
'gateway' => $payment_token->get_gateway_id(),
),
'expires' => esc_html__( 'N/A', 'woocommerce' ),
'is_default' => $payment_token->is_default(),
'actions' => array(
'delete' => array(
'url' => $delete_url,
'name' => esc_html__( 'Delete', 'woocommerce' ),
),
),
);
$key = key( array_slice( $list[ $type ], -1, 1, true ) );
if ( ! $payment_token->is_default() ) {
$list[ $type ][$key]['actions']['default'] = array(
'url' => $set_default_url,
'name' => esc_html__( 'Make Default', 'woocommerce' ),
);
}
$list[ $type ][ $key ] = apply_filters( 'woocommerce_payment_methods_list_item', $list[ $type ][ $key ], $payment_token );
}
return $list;
}
add_filter( 'woocommerce_saved_payment_methods_list', 'wc_get_account_saved_payment_methods_list', 10, 2 );
/**
* Controls the output for credit cards on the my account page.
*
* @since 2.6
* @param array $item Individual list item from woocommerce_saved_payment_methods_list
* @param WC_Payment_Token $payment_token The payment token associated with this method entry
* @return array Filtered item
*/
function wc_get_account_saved_payment_methods_list_item_cc( $item, $payment_token ) {
if ( 'cc' !== strtolower( $payment_token->get_type() ) ) {
return $item;
}
$card_type = $payment_token->get_card_type();
$item['method']['last4'] = $payment_token->get_last4();
$item['method']['brand'] = ( ! empty( $card_type ) ? ucfirst( $card_type ) : esc_html__( 'Credit Card', 'woocommerce' ) );
$item['expires'] = $payment_token->get_expiry_month() . '/' . substr( $payment_token->get_expiry_year(), -2 );
return $item;
}
add_filter( 'woocommerce_payment_methods_list_item', 'wc_get_account_saved_payment_methods_list_item_cc', 10, 2 );
/**
* Controls the output for eChecks on the my account page.
*
* @since 2.6
* @param array $item Individual list item from woocommerce_saved_payment_methods_list
* @param WC_Payment_Token $payment_token The payment token associated with this method entry
* @return array Filtered item
*/
function wc_get_account_saved_payment_methods_list_item_echeck( $item, $payment_token ) {
if ( 'echeck' !== strtolower( $payment_token->get_type() ) ) {
return $item;
}
$item['method']['last4'] = $payment_token->get_last4();
$item['method']['brand'] = esc_html__( 'eCheck', 'woocommerce' );
return $item;
}
add_filter( 'woocommerce_payment_methods_list_item', 'wc_get_account_saved_payment_methods_list_item_echeck', 10, 2 );

View File

@ -936,6 +936,31 @@ function wc_get_shipping_zone( $package ) {
return WC_Shipping_Zones::get_zone_matching_package( $package );
}
/**
* Get a nice name for credit card providers.
*
* @since 2.6.0
* @param string $type Provider Slug/Type
* @return string
*/
function wc_get_credit_card_type_label( $type ) {
// Normalize
$type = strtolower( $type );
$type = str_replace( '-', ' ', $type );
$type = str_replace( '_', ' ', $type );
$labels = apply_filters( 'wocommerce_credit_card_type_labels', array(
'mastercard' => __( 'MasterCard', 'woocommerce' ),
'visa' => __( 'Visa', 'woocommerce' ),
'discover' => __( 'Discover', 'woocommerce' ),
'american express' => __( 'American Express', 'woocommerce' ),
'diners' => __( 'Diners', 'woocommerce' ),
'jcb' => __( 'JCB', 'woocommerce' ),
) );
return apply_filters( 'woocommerce_get_credit_card_type_label', ( array_key_exists( $type, $labels ) ? $labels[ $type ] : ucfirst( $type ) ) );
}
/**
* Outputs a "back" link so admin screens can easily jump back a page.
*

View File

@ -250,3 +250,5 @@ add_action( 'woocommerce_account_edit-address_endpoint', 'woocommerce_account_ed
add_action( 'woocommerce_account_payment-methods_endpoint', 'woocommerce_account_payment_methods' );
add_action( 'woocommerce_account_add-payment-method_endpoint', 'woocommerce_account_add_payment_method' );
add_action( 'woocommerce_account_edit-account_endpoint', 'woocommerce_account_edit_account' );
add_action( 'woocommerce_account_set-default-payment-method_endpoint', array( 'WC_Shortcode_My_Account', 'set_default_payment_method' ) );
add_action( 'woocommerce_account_delete-payment-method_endpoint', array( 'WC_Shortcode_My_Account', 'delete_payment_method' ) );

View File

@ -54,7 +54,7 @@ wc_get_template( 'myaccount/navigation.php' ); ?>
<div class="form-row">
<?php wp_nonce_field( 'woocommerce-add-payment-method' ); ?>
<input type="submit" class="button alt" id="place_order" value="<?php esc_attr_e( 'Add Payment Method', 'woocommerce' ); ?>" />
<input type="hidden" name="woocommerce_add_payment_method" value="1" />
<input type="hidden" name="woocommerce_add_payment_method" id="woocommerce_add_payment_method" value="1" />
</div>
</div>
</form>

View File

@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
$saved_methods = wc_get_customer_saved_methods_list( get_current_user_id() );
$has_methods = (bool) $saved_methods;
$types = wc_get_account_payment_methods_types();
wc_print_notices(); ?>
<?php wc_get_template( 'myaccount/navigation.php' ); ?>
@ -43,27 +43,43 @@ wc_print_notices(); ?>
<?php endforeach; ?>
</tr>
</thead>
<?php foreach ( $saved_methods as $method ) : ?>
<?php foreach ( $saved_methods as $type => $methods ) : ?>
<?php foreach ( $methods as $method ) : ?>
<tr class="method">
<?php foreach ( wc_get_account_payment_methods_columns() as $column_id => $column_name ) : ?>
<td class="payment-method-<?php echo esc_attr( $column_id ); ?>" data-title="<?php echo esc_attr( $column_name ); ?>">
<?php
// @TODO
if ( has_action( 'woocommerce_account_payment_methods_column_' . $column_id ) ) {
do_action( 'woocommerce_account_payment_methods_column_' . $column_id, $method );
} else if ( 'method' === $column_id ) {
if ( ! empty ( $method['method']['last4'] ) ) {
echo sprintf( __( '%s ending in %s', 'woocommerce' ), esc_html( wc_get_credit_card_type_label( $method['method']['brand'] ) ), esc_html( $method['method']['last4'] ) );
} else {
echo esc_html( wc_get_credit_card_type_label( $method['method']['brand'] ) );
}
} else if ( 'expires' === $column_id ) {
echo esc_html( $method['expires'] );
} else if ( 'actions' === $column_id ) {
foreach ( $method['actions'] as $key => $action ) {
echo '<a href="' . esc_url( $action['url'] ) . '" class="button ' . sanitize_html_class( $key ) . '">' . esc_html( $action['name'] ) . '</a>&nbsp;';
}
}
?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
</table>
<?php else : ?>
<p><?php esc_html_e( 'No saved method found.', 'woocommerce' ); ?></p>
<p><?php esc_html_e( 'No saved methods found.', 'woocommerce' ); ?></p>
<?php endif; ?>
<?php do_action( 'woocommerce_after_account_payment_methods', $has_methods ); ?>
<a class="button" href="<?php echo esc_url( wc_get_endpoint_url( 'add-payment-method' ) ); ?>"><?php esc_html_e( 'Add New Payment Method', 'woocommerce' ); ?></a>
<a class="button" href="<?php echo esc_url( wc_get_endpoint_url( 'add-payment-method' ) ); ?>"><?php esc_html_e( 'Add Payment Method', 'woocommerce' ); ?></a>
</div>

View File

@ -91,6 +91,7 @@ class WC_Unit_Tests_Bootstrap {
// framework
require_once( $this->tests_dir . '/framework/class-wc-unit-test-factory.php' );
require_once( $this->tests_dir . '/framework/class-wc-mock-session-handler.php' );
require_once( $this->tests_dir . '/framework/class-wc-payment-token-stub.php' );
// test cases
require_once( $this->tests_dir . '/framework/class-wc-unit-test-case.php' );
@ -104,6 +105,7 @@ class WC_Unit_Tests_Bootstrap {
require_once( $this->tests_dir . '/framework/helpers/class-wc-helper-customer.php' );
require_once( $this->tests_dir . '/framework/helpers/class-wc-helper-order.php' );
require_once( $this->tests_dir . '/framework/helpers/class-wc-helper-shipping-zones.php' );
require_once( $this->tests_dir . '/framework/helpers/class-wc-helper-payment-token.php' );
}
/**

View File

@ -0,0 +1,26 @@
<?php
/**
* Stub/Dummy class to test WC_Payment_Token methods only
*
* @since 2.6
*/
class WC_Payment_Token_Stub extends \WC_Payment_Token {
/** @protected string Token Type String */
protected $type = 'stub';
/**
* Returns meta
* @return string
*/
public function get_extra() {
return isset( $this->meta['extra'] ) ? $this->meta['extra'] : '';
}
/**
* Set meta
* @param string $extra
*/
public function set_extra( $extra ) {
$this->meta['extra'] = $extra;
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Class WC_Helper_Payment_Token
*
* This helper class should ONLY be used for unit tests!
*/
class WC_Helper_Payment_Token {
/**
* Create a new credit card payment token
*
* @since 2.6
* @return WC_Payment_Token_CC object
*/
public static function create_cc_token() {
$token = new WC_Payment_Token_CC();
$token->set_last4( 1234 );
$token->set_expiry_month( '08' );
$token->set_expiry_year( '2016' );
$token->set_card_type( 'visa' );
$token->set_token( time() );
$token->save();
return $token;
}
/**
* Create a new eCheck payment token
*
* @since 2.6
* @return WC_Payment_Token_eCheck object
*/
public static function create_eCheck_token() {
$token = new WC_Payment_Token_eCheck();
$token->set_last4( 1234 );
$token->set_token( time() );
$token->save();
return $token;
}
/**
* Create a new 'stub' payment token
*
* @since 2.6
* @param string $extra A string to insert and get to test the metadata functionality of a token
* @return WC_Payment_Token_Stub object
*/
public static function create_stub_token( $extra ) {
$token = new WC_Payment_Token_Stub();
$token->set_extra( $extra );
$token->set_token( time() );
$token->save();
return $token;
}
}

View File

@ -123,4 +123,36 @@ class Functions extends \WC_Unit_Test_Case {
// Assert the return when $the_order args is a random (incorrect) id.
$this->assertFalse( wc_get_order( 123456 ) );
}
/**
* Test getting an orders payment tokens
*
* @since 2.6
*/
public function test_wc_order_get_payment_tokens() {
$order = \WC_Helper_Order::create_order();
$this->assertEmpty( $order->get_payment_tokens() );
$token = \WC_Helper_Payment_Token::create_cc_token();
update_post_meta( $order->id, '_payment_tokens', array( $token->get_id() ) );
$this->assertCount( 1, $order->get_payment_tokens() );
}
/**
* Test adding a payment token to an order
*
* @since 2.6
*/
public function test_wc_order_add_payment_token() {
$order = \WC_Helper_Order::create_order();
$this->assertEmpty( $order->get_payment_tokens() );
$token = \WC_Helper_Payment_Token::create_cc_token();
$order->add_payment_token( $token );
$this->assertCount( 1, $order->get_payment_tokens() );
}
}

View File

@ -0,0 +1,138 @@
<?php
namespace WooCommerce\Tests\Payment_Tokens;
/**
* Class Payment_Token_CC.
* @package WooCommerce\Tests\Payment_Tokens
*/
class Payment_Token_CC extends \WC_Unit_Test_Case {
/**
* Test validation for empty/unset values.
* @since 2.6.0
*/
function test_wc_payment_token_cc_validate_empty() {
$token = new \WC_Payment_Token_CC( 1 );
$token->set_token( time() . ' ' . __FUNCTION__ );
$this->assertFalse( $token->validate() );
$token->set_last4( '1111' );
$token->set_expiry_year( '2016' );
$token->set_expiry_month( '08' );
$token->set_card_type( 'visa' );
$this->assertTrue( $token->validate() );
}
/**
* Test validation for expiry length.
* @since 2.6.0
*/
function test_wc_payment_token_cc_validate_expiry_length() {
$token = new \WC_Payment_Token_CC( 1 );
$token->set_token( time() . ' ' . __FUNCTION__ );
$this->assertFalse( $token->validate() );
$token->set_last4( '1111' );
$token->set_expiry_year( '16' );
$token->set_expiry_month( '08' );
$token->set_card_type( 'visa' );
$this->assertFalse( $token->validate() );
$token->set_expiry_year( '2016' );
$this->assertTrue( $token->validate() );
$token->set_expiry_month( '8' );
$this->assertFalse( $token->validate() );
}
/**
* Test getting a card type.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_get_card_type() {
$token = new \WC_Payment_Token_CC( 1, array(), array( 'card_type' => 'mastercard' ) );
$this->assertEquals( 'mastercard', $token->get_card_type() );
}
/**
* Test setting a token's card type.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_set_card_type() {
$token = new \WC_Payment_Token_CC( 1 );
$token->set_card_type( 'visa' );
$this->assertEquals( 'visa', $token->get_card_type() );
}
/**
* Test getting expiry year.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_get_expiry_year() {
$token = new \WC_Payment_Token_CC( 1, array(), array( 'expiry_year' => '2016' ) );
$this->assertEquals( '2016', $token->get_expiry_year() );
}
/**
* Test setting a token's expiry year.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_set_expiry_year() {
$token = new \WC_Payment_Token_CC( 1 );
$token->set_expiry_year( '2016' );
$this->assertEquals( '2016', $token->get_expiry_year() );
}
/**
* Test getting expiry month.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_get_expiry_month() {
$token = new \WC_Payment_Token_CC( 1, array(), array( 'expiry_month' => '08' ) );
$this->assertEquals( '08', $token->get_expiry_month() );
}
/**
* Test setting a token's expiry month.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_set_expiry_month() {
$token = new \WC_Payment_Token_CC( 1 );
$token->set_expiry_month( '08' );
$this->assertEquals( '08', $token->get_expiry_month() );
}
/**
* Test getting last4.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_get_last4() {
$token = new \WC_Payment_Token_CC( 1, array(), array( 'last4' => '1111' ) );
$this->assertEquals( '1111', $token->get_last4() );
}
/**
* Test setting a token's last4.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_set_last4() {
$token = new \WC_Payment_Token_CC( 1 );
$token->set_last4( '2222' );
$this->assertEquals( '2222', $token->get_last4() );
}
/**
* Test reading/getting a token from DB correctly sets meta.
* @since 2.6.0
*/
public function test_wc_payment_token_cc_read_pulls_meta() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token_id = $token->get_id();
$token_read = new \WC_Payment_Token_CC();
$token_read->read( $token_id );
$this->assertEquals( '1234', $token_read->get_last4() );
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace WooCommerce\Tests\Payment_Tokens;
/**
* Class Payment_Token_eCheck.
* @package WooCommerce\Tests\Payment_Tokens
*/
class Payment_Token_eCheck extends \WC_Unit_Test_Case {
/**
* Test validation for empty/unset values.
* @since 2.6.0
*/
function test_wc_payment_token_echeck_validate_empty() {
$token = new \WC_Payment_Token_eCheck( 1 );
$token->set_token( time() . ' ' . __FUNCTION__ );
$this->assertFalse( $token->validate() );
$token->set_last4( '1111' );
$this->assertTrue( $token->validate() );
}
/**
* Test getting last4.
* @since 2.6.0
*/
public function test_wc_payment_token_echeck_get_last4() {
$token = new \WC_Payment_Token_eCheck( 1, array(), array( 'last4' => '1111' ) );
$this->assertEquals( '1111', $token->get_last4() );
}
/**
* Test setting a token's last4.
* @since 2.6.0
*/
public function test_wc_payment_token_echeck_set_last4() {
$token = new \WC_Payment_Token_eCheck( 1 );
$token->set_last4( '2222' );
$this->assertEquals( '2222', $token->get_last4() );
}
/**
* Test reading/getting a token from DB correctly sets meta.
* @since 2.6.0
*/
public function test_wc_payment_token_echeck_read_pulls_meta() {
$token = \WC_Helper_Payment_Token::create_eCheck_token();
$token_id = $token->get_id();
$token_read = new \WC_Payment_Token_eCheck();
$token_read->read( $token_id );
$this->assertEquals( '1234', $token_read->get_last4() );
}
}

View File

@ -0,0 +1,216 @@
<?php
namespace WooCommerce\Tests\Payment_Tokens;
/**
* Class Payment_Token
* @package WooCommerce\Tests\Payment_Tokens
*/
class Payment_Token extends \WC_Unit_Test_Case {
/**
* Test get_id to make sure it returns the ID passed into the class.
* @since 2.6.0
*/
public function test_wc_payment_token_get_id() {
$token = new \WC_Payment_Token_Stub( 1 );
$this->assertEquals( 1, $token->get_id() );
}
/**
* Test get type returns the class name/type.
* @since 2.6.0
*/
public function test_wc_payment_token_get_type() {
$token = new \WC_Payment_Token_Stub( 1 );
$this->assertEquals( 'stub', $token->get_type() );
}
/**
* Test get token to make sure it returns the passed token.
* @since 2.6.0
*/
public function test_wc_payment_token_get_token() {
$raw_token = time() . ' ' . __FUNCTION__;
$token = new \WC_Payment_Token_Stub( 1, array( 'token' => $raw_token ) );
$this->assertEquals( $raw_token, $token->get_token() );
}
/**
* Test set token to make sure it sets the pased token.
* @since 2.6.0
*/
public function test_wc_payment_token_set_token() {
$raw_token = time() . ' ' . __FUNCTION__;
$token = new \WC_Payment_Token_Stub( 1 );
$token->set_token( $raw_token );
$this->assertEquals( $raw_token, $token->get_token() );
}
/**
* Test get user ID to make sure it passes the correct ID.
* @since 2.6.0
*/
public function test_wc_payment_get_user_id() {
$token = new \WC_Payment_Token_Stub( 1, array( 'user_id' => 1 ) );
$this->assertEquals( 1, $token->get_user_id() );
}
/**
* Test get user ID to make sure it returns 0 if there is no user ID.
* @since 2.6.0
*/
public function test_wc_payment_get_user_id_defaults_to_0() {
$token = new \WC_Payment_Token_Stub( 1 );
$this->assertEquals( 0, $token->get_user_id() );
}
/**
* Test set user ID to make sure it passes the correct ID.
* @since 2.6.0
*/
public function test_wc_payment_set_user_id() {
$token = new \WC_Payment_Token_Stub( 1 );
$token->set_user_id( 5 );
$this->assertEquals( 5, $token->get_user_id() );
}
/**
* Test getting the gateway ID.
* @since 2.6.0
*/
public function test_wc_payment_get_gateway_id() {
$token = new \WC_Payment_Token_Stub( 1, array( 'gateway_id' => 'paypal' ) );
$this->assertEquals( 'paypal', $token->get_gateway_id() );
}
/**
* Test set the gateway ID.
* @since 2.6.0
*/
public function test_wc_payment_set_gateway_id() {
$token = new \WC_Payment_Token_Stub( 1 );
$token->set_gateway_id( 'paypal' );
$this->assertEquals( 'paypal', $token->get_gateway_id() );
}
/**
* Test setting a token as default.
* @since 2.6.0
*/
public function test_wc_payment_token_set_default() {
$token = new \WC_Payment_Token_Stub( 1 );
$token->set_default( true );
$this->assertTrue( $token->is_default() );
$token->set_default( false );
$this->assertFalse( $token->is_default() );
}
/**
* Test is_default.
* @since 2.6.0
*/
public function test_wc_payment_token_is_default_returns_correct_state() {
$token = new \WC_Payment_Token_Stub( 1, array( 'is_default' => true ) );
$this->assertTrue( $token->is_default() );
$token = new \WC_Payment_Token_Stub( 1 );
$this->assertFalse( $token->is_default() );
$token = new \WC_Payment_Token_Stub( 1, array( 'is_default' => false ) );
$this->assertFalse( $token->is_default() );
}
/**
* Test that get_data returns the correct internal representation for a token.
* @since 2.6.0
*/
public function test_wc_payment_token_get_data() {
$raw_token = time() . ' ' . __FUNCTION__;
$token = new \WC_Payment_Token_Stub( 1, array(
'token' => $raw_token,
'gateway_id' => 'paypal'
) );
$token->set_extra( 'woocommerce' );
$data = $token->get_data();
$this->assertEquals( $raw_token, $data['token'] );
$this->assertEquals( 'paypal', $data['gateway_id'] );
$this->assertEquals( 'stub', $data['type'] );
$this->assertEquals( 'woocommerce', $data['meta']['extra'] );
}
/**
* Test token validation.
* @since 2.6.0
*/
public function test_wc_payment_token_validation() {
$token = new \WC_Payment_Token_Stub( 1 );
$token->set_token( time() . ' ' . __FUNCTION__ );
$this->assertTrue( $token->validate() );
$token = new \WC_Payment_Token_Stub( 1 );
$this->assertFalse( $token->validate() );
}
/**
* Test reading a token from the database.
* @since 2.6.0
*/
public function test_wc_payment_token_read() {
$token = \WC_Helper_Payment_Token::create_stub_token( __FUNCTION__ );
$token_id = $token->get_id();
$token_read = new \WC_Payment_Token_Stub();
$token_read->read( $token_id );
$this->assertEquals( $token->get_token(), $token_read->get_token() );
$this->assertEquals( $token->get_extra(), $token_read->get_extra() );
}
/**
* Test updating a token.
* @since 2.6.0
*/
public function test_wc_payment_token_update() {
$token = \WC_Helper_Payment_Token::create_stub_token( __FUNCTION__ );
$this->assertEquals( __FUNCTION__, $token->get_extra() );
$token->set_extra( ':)' );
$token->update();
$this->assertEquals( ':)', $token->get_extra() );
}
/**
* Test creating a new token.
* @since 2.6.0
*/
public function test_wc_payment_token_create() {
$token = new \WC_Payment_Token_Stub();
$token->set_extra( __FUNCTION__ );
$token->set_token( time() );
$token->create();
$this->assertNotEmpty( $token->get_id() );
$this->assertEquals( __FUNCTION__, $token->get_extra() );
}
/**
* Test deleting a token.
* @since 2.6.0
*/
public function test_wc_payment_token_delete() {
$token = \WC_Helper_Payment_Token::create_stub_token( __FUNCTION__ );
$token_id = $token->get_id();
$token->delete();
$get_token = \WC_Payment_Tokens::get( $token_id );
$this->assertNull( $get_token );
}
/**
* Test a meta function (like CC's last4) doesn't work on the core abstract class.
* @since 2.6.0
*/
public function test_wc_payment_token_last4_doesnt_work() {
$token = new \WC_Payment_Token_Stub();
$this->assertFalse( is_callable( $token, 'get_last4' ) );
}
}

View File

@ -0,0 +1,177 @@
<?php
namespace WooCommerce\Tests\Payment_Tokens;
/**
* Class Payment_Tokens
* @package WooCommerce\Tests\Payment_Tokens
*/
class Payment_Tokens extends \WC_Unit_Test_Case {
/**
* Test getting tokens associated with an order.
* @since 2.6.0
*/
function test_wc_payment_tokens_get_order_tokens() {
$order = \WC_Helper_Order::create_order();
$this->assertEmpty( \WC_Payment_Tokens::get_order_tokens( $order->id ) );
$token = \WC_Helper_Payment_Token::create_cc_token();
update_post_meta( $order->id, '_payment_tokens', array( $token->get_id() ) );
$this->assertCount( 1, \WC_Payment_Tokens::get_order_tokens( $order->id ) );
}
/**
* Test getting tokens associated with a user and no gateway ID.
* @since 2.6.0
*/
function test_wc_payment_tokens_get_customer_tokens_no_gateway() {
$this->assertEmpty( \WC_Payment_Tokens::get_customer_tokens( 1 ) );
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->save();
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->save();
$this->assertCount( 2, \WC_Payment_Tokens::get_customer_tokens( 1 ) );
}
/**
* Test getting tokens associated with a user and for a specific gateway.
* @since 2.6.0
*/
function test_wc_payment_tokens_get_customer_tokens_with_gateway() {
$this->assertEmpty( \WC_Payment_Tokens::get_customer_tokens( 1 ) );
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->set_gateway_id( 'simplify_commerce' );
$token->save();
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->set_gateway_id( 'paypal' );
$token->save();
$this->assertCount( 2, \WC_Payment_Tokens::get_customer_tokens( 1 ) );
$this->assertCount( 1, \WC_Payment_Tokens::get_customer_tokens( 1, 'simplify_commerce' ) );
foreach ( \WC_Payment_Tokens::get_customer_tokens( 1, 'simplify_commerce' ) as $simplify_token ) {
$this->assertEquals( 'simplify_commerce', $simplify_token->get_gateway_id() );
}
}
/**
* Test getting a customers default token.
* @since 2.6.0
*/
function test_wc_get_customer_default_token() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->set_gateway_id( 'simplify_commerce' );
$token->save();
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->set_default( true );
$token->set_gateway_id( 'paypal' );
$token->save();
$this->assertCount( 2, \WC_Payment_Tokens::get_customer_tokens( 1 ) );
$default_token = \WC_Payment_Tokens::get_customer_default_token( 1 );
$this->assertEquals( 'paypal', $default_token->get_gateway_id() );
}
/**
* Test getting a customers default token, when there is no default token.
* @since 2.6.0
*/
function test_wc_get_customer_default_token_returns_null_when_no_default_token() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->set_gateway_id( 'simplify_commerce' );
$token->save();
$token = \WC_Helper_Payment_Token::create_cc_token();
$token->set_user_id( 1 );
$token->set_gateway_id( 'paypal' );
$token->save();
$this->assertCount( 2, \WC_Payment_Tokens::get_customer_tokens( 1 ) );
$default_token = \WC_Payment_Tokens::get_customer_default_token( 1 );
$this->assertNull( $default_token );
}
/**
* Test getting a token by ID.
* @since 2.6.0
*/
function test_wc_payment_tokens_get() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token_id = $token->get_id();
$get_token = \WC_Payment_Tokens::get( $token_id );
$this->assertEquals( $token->get_token(), $get_token->get_token() );
}
/**
* Test deleting a token by ID.
* @since 2.6.0
*/
function test_wc_payment_tokens_delete() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token_id = $token->get_id();
\WC_Payment_Tokens::delete( $token_id );
$get_token = \WC_Payment_Tokens::get( $token_id );
$this->assertNull( $get_token );
}
/**
* Test getting a token's type by ID.
* @since 2.6.0
*/
function test_wc_payment_tokens_get_type_by_id() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token_id = $token->get_id();
$this->assertEquals( 'CC', \WC_Payment_Tokens::get_token_type_by_id( $token_id ) );
}
/**
* Test setting a users default token.
* @since 2.6.0
*/
function test_wc_payment_tokens_set_users_default() {
$token = \WC_Helper_Payment_Token::create_cc_token();
$token_id = $token->get_id();
$token->set_user_id( 1 );
$token->save();
$token2 = \WC_Helper_Payment_Token::create_cc_token();
$token_id_2 = $token2->get_id();
$token2->set_user_id( 1 );
$token2->save();
$this->assertFalse( $token->is_default() );
$this->assertFalse( $token2->is_default() );
\WC_Payment_Tokens::set_users_default( 1, $token_id_2 );
$token->read( $token_id );
$token2->read( $token_id_2 );
$this->assertFalse( $token->is_default() );
$this->assertTrue( $token2->is_default() );
\WC_Payment_Tokens::set_users_default( 1, $token_id );
$token->read( $token_id );
$token2->read( $token_id_2 );
$this->assertTrue( $token->is_default() );
$this->assertFalse( $token2->is_default() );
}
}

View File

@ -50,6 +50,8 @@ if ( ! empty( $status_options['uninstall_data'] ) ) {
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_shipping_zone_locations" );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_shipping_zones" );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_sessions" );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_payment_tokens" );
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_payment_tokenmeta" );
// Delete options.
$wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'woocommerce\_%';");

View File

@ -167,6 +167,7 @@ final class WooCommerce {
add_action( 'init', array( $this, 'init' ), 0 );
add_action( 'init', array( 'WC_Shortcodes', 'init' ) );
add_action( 'init', array( 'WC_Emails', 'init_transactional_emails' ) );
add_action( 'init', array( $this, 'payment_token_metadata_wpdbfix' ), 0 );
}
/**
@ -256,6 +257,7 @@ final class WooCommerce {
include_once( 'includes/class-wc-auth.php' ); // Auth Class
include_once( 'includes/class-wc-post-types.php' ); // Registers post types
include_once( 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens
include_once( 'includes/abstracts/abstract-wc-product.php' ); // Products
include_once( 'includes/abstracts/abstract-wc-order.php' ); // Orders
include_once( 'includes/abstracts/abstract-wc-settings-api.php' ); // Settings API (for gateways, shipping, and integrations)
@ -263,6 +265,9 @@ final class WooCommerce {
include_once( 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway
include_once( 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service
include_once( 'includes/class-wc-product-factory.php' ); // Product factory
include_once( 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller
include_once( 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway
include_once( 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway
include_once( 'includes/class-wc-countries.php' ); // Defines countries and states
include_once( 'includes/class-wc-integrations.php' ); // Loads integrations
include_once( 'includes/class-wc-cache-helper.php' ); // Cache Helper
@ -466,6 +471,15 @@ final class WooCommerce {
}
}
/**
* WooCommerce Payment Token Meta API - set table name
*/
function payment_token_metadata_wpdbfix() {
global $wpdb;
$wpdb->payment_tokenmeta = $wpdb->prefix . 'woocommerce_payment_tokenmeta';
$wpdb->tables[] = 'woocommerce_payment_tokenmeta';
}
/**
* Get Checkout Class.
* @return WC_Checkout