Update Payment Tokens to follow the same pattern for custom data as other object types.

Payment tokens was introduced in 2.6 and was updated to use the new CRUD code while some of the CRUD system was still in flux.

While most things were correct, the prop handling for custom fields (like a card's last 4 digits) were directly calling meta functions, instead of delegating to the data store/parent.

This PR moves these props to `extra_data` and follows the same pattern as product types or order items. It also updates some version tags to 3.0.0. Finally, it adds an additional test for saving meta after a create which looks like it was lacking.

To Test:
* Run `phpunit`.
* Go to the "My Account" tab and add a new payment method. You need a payment gateway that supports this, like Simplify.
* Test the add a payment method flow.
* Make a test purchase using the saved payment method.
This commit is contained in:
Justin Shreve 2017-03-14 12:21:51 -07:00
parent cbe6934f61
commit fb124232cf
6 changed files with 181 additions and 89 deletions

View File

@ -12,7 +12,7 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
* examples: Credit Card, eCheck.
*
* @class WC_Payment_Token
* @version 2.7.0
* @version 3.0.0
* @since 2.6.0
* @package WooCommerce/Abstracts
* @category Abstract Class
@ -32,6 +32,12 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
'type' => '',
);
/**
* Token Type (CC, eCheck, or a custom type added by an extension).
* Set by child classes.
*/
protected $type = '';
/**
* Initialize a payment token.
*
@ -45,10 +51,7 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
* @param mixed $token
*/
public function __construct( $token = '' ) {
// Set token type (cc, echeck)
if ( ! empty( $this->type ) ) {
$this->set_type( $this->type );
}
parent::__construct( $token );
if ( is_numeric( $token ) ) {
$this->set_id( $token );
@ -86,13 +89,13 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
/**
* Returns the type of this payment token (CC, eCheck, or something else).
* Overwritten by child classes.
*
* @since 2.6.0
* @param string $context
* @return string Payment Token Type (CC, eCheck)
*/
public function get_type( $context = 'view' ) {
return $this->get_prop( 'type', $context );
public function get_type( $deprecated = '' ) {
return $this->type;
}
/**
@ -104,7 +107,7 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
* @return string
*/
public function get_display_name( $context = 'view' ) {
return $this->get_type( $context );
return $this->get_type();
}
/**
@ -156,16 +159,6 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
$this->set_prop( 'token', $token );
}
/**
* Sets the type of this payment token (CC, eCheck, or something else).
*
* @since 2.7.0
* @param string Payment Token Type (CC, eCheck)
*/
public function set_type( $type ) {
return $this->set_prop( 'type', $type );
}
/**
* Set the user ID for the user associated with this order.
*
@ -221,12 +214,6 @@ include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-payment-token.php
if ( empty( $token ) ) {
return false;
}
$type = $this->get_prop( 'type', 'edit' );
if ( empty( $type ) ) {
return false;
}
return true;
}

View File

@ -6,7 +6,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* WC Payment Token Data Store: Custom Table.
*
* @version 2.7.0
* @version 3.0.0
* @category Class
* @author WooThemes
*/
@ -18,10 +18,15 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
*/
protected $meta_type = 'payment_token';
/**
* If we have already saved our extra data, don't do automatic / default handling.
*/
protected $extra_data_saved = false;
/**
* Create a new payment token in the database.
*
* @since 2.7.0
* @since 3.0.0
* @param WC_Payment_Token $token
*/
public function create( &$token ) {
@ -47,6 +52,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
$wpdb->insert( $wpdb->prefix . 'woocommerce_payment_tokens', $payment_token_data );
$token_id = $wpdb->insert_id;
$token->set_id( $token_id );
$this->save_extra_data( $token, true );
$token->save_meta_data();
$token->apply_changes();
@ -61,7 +67,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
/**
* Update a payment token.
*
* @since 2.7.0
* @since 3.0.0
* @param WC_Payment_Token $token
*/
public function update( &$token ) {
@ -72,11 +78,13 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
global $wpdb;
$updated_props = array();
$payment_token_data = array();
$props = array( 'gateway_id', 'token', 'user_id', 'type' );
$core_props = array( 'gateway_id', 'token', 'user_id', 'type' );
$changed_props = array_keys( $token->get_changes() );
foreach ( $changed_props as $prop ) {
if ( ! in_array( $prop, $core_props ) ) {
continue;
}
$updated_props[] = $prop;
$payment_token_data[ $prop ] = $token->{"get_" . $prop}( 'edit' );
}
@ -89,6 +97,8 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
);
}
$updated_extra_props = $this->save_extra_data( $token );
$updated_props = array_merge( $updated_props, $updated_extra_props );
$token->save_meta_data();
$token->apply_changes();
@ -104,7 +114,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
/**
* Remove a payment token from the database.
*
* @since 2.7.0
* @since 3.0.0
* @param WC_Payment_Token $token
* @param bool $force_delete
*/
@ -118,7 +128,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
/**
* Read a token from the database.
*
* @since 2.7.0
* @since 3.0.0
* @param WC_Payment_Token $token
*/
public function read( &$token ) {
@ -130,6 +140,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
'gateway_id' => $data->gateway_id,
'default' => $data->is_default,
) );
$this->read_extra_data( $token );
$token->read_meta_data();
$token->set_object_read( true );
do_action( 'woocommerce_payment_token_loaded', $token );
@ -138,12 +149,60 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
}
}
/**
* Read extra data associated with the token (like last4 digits of a card for expiry dates).
*
* @param WC_Payment_Token
* @since 3.0.0
*/
protected function read_extra_data( &$token ) {
foreach ( $token->get_extra_data_keys() as $key ) {
$function = 'set_' . $key;
if ( is_callable( array( $token, $function ) ) ) {
$token->{$function}( get_post_meta( $token->get_id(), $key, true ) );
}
}
}
/**
* Saves extra token data as meta.
*
* @since 3.0.0
* @param $token WC_Token
* @param $force bool
* @return array List of updated props.
*/
protected function save_extra_data( &$token, $force = false ) {
if ( $this->extra_data_saved ) {
return array();
}
$updated_props = array();
$extra_data_keys = $token->get_extra_data_keys();
$meta_key_to_props = array_combine( $extra_data_keys, $extra_data_keys );
$props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $token, $meta_key_to_props );
foreach ( $extra_data_keys as $key ) {
if ( ! array_key_exists( $key, $props_to_update ) ) {
continue;
}
$function = 'get_' . $key;
if ( is_callable( array( $token, $function ) ) ) {
if ( update_post_meta( $token->get_id(), $key, $token->{$function}( 'edit' ) ) ) {
$updated_props[] = $key;
}
}
}
return $updated_props;
}
/**
* Returns an array of objects (stdObject) matching specific token critera.
* Accepts token_id, user_id, gateway_id, and type.
* Each object should contain the fields token_id, gateway_id, token, user_id, type, is_default.
*
* @since 2.7.0
* @since 3.0.0
* @param array $args
* @return array
*/
@ -191,7 +250,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
* Returns an stdObject of a token for a user's default token.
* Should contain the fields token_id, gateway_id, token, user_id, type, is_default.
*
* @since 2.7.0
* @since 3.0.0
* @param id $user_id
* @return object
*/
@ -207,7 +266,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
* Returns an stdObject of a token.
* Should contain the fields token_id, gateway_id, token, user_id, type, is_default.
*
* @since 2.7.0
* @since 3.0.0
* @param id $token_id
* @return object
*/
@ -222,7 +281,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
/**
* Returns metadata for a specific payment token.
*
* @since 2.7.0
* @since 3.0.0
* @param id $token_id
* @return array
*/
@ -233,7 +292,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
/**
* Get a token's type by ID.
*
* @since 2.7.0
* @since 3.0.0
* @param id $token_id
* @return string
*/
@ -250,7 +309,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
* looping through tokens and setting their statuses instead of creating a bunch
* of objects.
*
* @since 2.7.0
* @since 3.0.0
* @param id $token_id
* @return string
*/

View File

@ -6,23 +6,32 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Legacy Payment Tokens.
* Payment Tokens were introduced in 2.6.0 with create and update as methods.
* Major CRUD changes occurred in 2.7, so these were deprecated (save and delete still work).
* Major CRUD changes occurred in 3.0, so these were deprecated (save and delete still work).
* This legacy class is for backwards compatibility in case any code called ->read, ->update or ->create
* directly on the object.
*
* @version 2.7.0
* @version 3.0.0
* @package WooCommerce/Classes
* @category Class
* @author WooCommerce
*/
abstract class WC_Legacy_Payment_Token extends WC_Data {
/**
* Sets the type of this payment token (CC, eCheck, or something else).
*
* @param string Payment Token Type (CC, eCheck)
*/
public function set_type( $type ) {
wc_deprecated_function( 'WC_Payment_Token::set_type', '3.0.0', 'Type cannot be overwritten.' );
}
/**
* Read a token by ID.
* @deprecated 2.7.0 - Init a token class with an ID.
* @deprecated 3.0.0 - Init a token class with an ID.
*/
public function read( $token_id ) {
wc_deprecated_function( 'WC_Payment_Token::read', '2.7', 'a new token class initialized with an ID.' );
wc_deprecated_function( 'WC_Payment_Token::read', '3.0.0', 'a new token class initialized with an ID.' );
$this->set_id( $token_id );
$data_store = WC_Data_Store::load( 'payment-token' );
$data_store->read( $this );
@ -30,10 +39,10 @@ abstract class WC_Legacy_Payment_Token extends WC_Data {
/**
* Update a token.
* @deprecated 2.7.0 - Use ::save instead.
* @deprecated 3.0.0 - Use ::save instead.
*/
public function update() {
wc_deprecated_function( 'WC_Payment_Token::update', '2.7', '::save instead.' );
wc_deprecated_function( 'WC_Payment_Token::update', '3.0.0', '::save instead.' );
$data_store = WC_Data_Store::load( 'payment-token' );
try {
$data_store->update( $this );
@ -44,10 +53,10 @@ abstract class WC_Legacy_Payment_Token extends WC_Data {
/**
* Create a token.
* @deprecated 2.7.0 - Use ::save instead.
* @deprecated 3.0.0 - Use ::save instead.
*/
public function create() {
wc_deprecated_function( 'WC_Payment_Token::create', '2.7', '::save instead.' );
wc_deprecated_function( 'WC_Payment_Token::create', '3.0.0', '::save instead.' );
$data_store = WC_Data_Store::load( 'payment-token' );
try {
$data_store->create( $this );

View File

@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* Representation of a payment token for credit cards.
*
* @class WC_Payment_Token_CC
* @version 2.7.0
* @version 3.0.0
* @since 2.6.0
* @category PaymentTokens
* @package WooCommerce/PaymentTokens
@ -20,10 +20,41 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
/** @protected string Token Type String. */
protected $type = 'CC';
/**
* Stores Credit Card payment token data.
*
* @var array
*/
protected $extra_data = array(
'last4' => '',
'expiry_year' => '',
'expiry_month' => '',
'card_type' => '',
);
/**
* Get type to display to user.
*
* @since 2.6.0
* @param string $context
* @return string
*/
public function get_display_name( $context = 'view' ) {
/* translators: 1: credit card type 2: last 4 digits 3: expiry month 4: expiry year */
$display = sprintf(
__( '%1$s ending in %2$s (expires %3$s/%4$s)', 'woocommerce' ),
wc_get_credit_card_type_label( $this->get_card_type( $context ) ),
$this->get_last4( $context ),
$this->get_expiry_month( $context ),
substr( $this->get_expiry_year( $context ), 2 )
);
return $display;
}
/**
* Hook prefix
*
* @since 2.7.0
* @since 3.0.0
*/
protected function get_hook_prefix() {
return 'woocommerce_payment_token_cc_get_';
@ -73,25 +104,6 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
return true;
}
/**
* Get type to display to user.
*
* @since 2.6.0
* @param string $context
* @return string
*/
public function get_display_name( $context = 'view' ) {
/* translators: 1: credit card type 2: last 4 digits 3: expiry month 4: expiry year */
$display = sprintf(
__( '%1$s ending in %2$s (expires %3$s/%4$s)', 'woocommerce' ),
wc_get_credit_card_type_label( $this->get_card_type( $context ) ),
$this->get_last4( $context ),
$this->get_expiry_month( $context ),
substr( $this->get_expiry_year( $context ), 2 )
);
return $display;
}
/**
* Returns the card type (mastercard, visa, ...).
*
@ -100,7 +112,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @return string Card type
*/
public function get_card_type( $context = 'view' ) {
return $this->get_meta( 'card_type', true, $context );
return $this->get_prop( 'card_type', $context );
}
/**
@ -109,7 +121,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @param string $type
*/
public function set_card_type( $type ) {
$this->add_meta_data( 'card_type', $type, true );
$this->set_prop( 'card_type', $type );
}
/**
@ -120,7 +132,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @return string Expiration year
*/
public function get_expiry_year( $context = 'view' ) {
return $this->get_meta( 'expiry_year', true, $context );
return $this->get_prop( 'expiry_year', $context );
}
/**
@ -129,7 +141,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @param string $year
*/
public function set_expiry_year( $year ) {
$this->add_meta_data( 'expiry_year', $year, true );
$this->set_prop( 'expiry_year', $year );
}
/**
@ -140,7 +152,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @return string Expiration month
*/
public function get_expiry_month( $context = 'view' ) {
return $this->get_meta( 'expiry_month', true, $context );
return $this->get_prop( 'expiry_month', $context );
}
/**
@ -149,7 +161,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @param string $month
*/
public function set_expiry_month( $month ) {
$this->add_meta_data( 'expiry_month', str_pad( $month, 2, '0', STR_PAD_LEFT ), true );
$this->set_prop( 'expiry_month', str_pad( $month, 2, '0', STR_PAD_LEFT ) );
}
/**
@ -160,7 +172,7 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @return string Last 4 digits
*/
public function get_last4( $context = 'view' ) {
return $this->get_meta( 'last4', true, $context );
return $this->get_prop( 'last4', $context );
}
/**
@ -169,6 +181,6 @@ class WC_Payment_Token_CC extends WC_Payment_Token {
* @param string $last4
*/
public function set_last4( $last4 ) {
$this->add_meta_data( 'last4', $last4, true );
$this->set_prop( 'last4', $last4 );
}
}

View File

@ -18,9 +18,29 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class WC_Payment_Token_eCheck extends WC_Payment_Token {
/** @protected string Token Type String */
/** @protected string Token Type String. */
protected $type = 'eCheck';
/**
* Stores eCheck payment token data.
*
* @var array
*/
protected $extra_data = array(
'last4' => '',
);
/**
* Get type to display to user.
*
* @since 2.6.0
* @param string $context
* @return string
*/
public function get_display_name( $context = 'view' ) {
return __( 'eCheck', 'woocommerce' );
}
/**
* Hook prefix
*
@ -50,17 +70,6 @@ class WC_Payment_Token_eCheck extends WC_Payment_Token {
return true;
}
/**
* Get type to display to user.
*
* @since 2.6.0
* @param string $context
* @return string
*/
public function get_display_name( $context = 'view' ) {
return __( 'eCheck', 'woocommerce' );
}
/**
* Returns the last four digits.
*
@ -69,7 +78,7 @@ class WC_Payment_Token_eCheck extends WC_Payment_Token {
* @return string Last 4 digits
*/
public function get_last4( $context = 'view' ) {
return $this->get_meta( 'last4', true, $context );
return $this->get_prop( 'last4', $context );
}
/**
@ -78,6 +87,6 @@ class WC_Payment_Token_eCheck extends WC_Payment_Token {
* @param string $last4
*/
public function set_last4( $last4 ) {
$this->add_meta_data( 'last4', $last4, true );
$this->set_prop( 'last4', $last4 );
}
}

View File

@ -95,4 +95,20 @@ class WC_Tests_Payment_Token_CC extends WC_Unit_Test_Case {
$token_read = new WC_Payment_Token_CC( $token_id );
$this->assertEquals( '1234', $token_read->get_last4() );
}
/*
* Test saving a new value in a token after it has been created.
* @since 3.0.0
*/
public function test_wc_payment_token_cc_updates_after_create() {
$token = WC_Helper_Payment_Token::create_cc_token();
$token_id = $token->get_id();
$this->assertEquals( '1234', $token->get_last4() );
$token->set_last4( '4321' );
$token->set_user_id( 3 );
$token->save();
$this->assertEquals( '4321', $token->get_last4() );
$this->assertEquals( 3, $token->get_user_id() );
}
}