validate() ) { throw new Exception( __( 'Invalid or missing payment token fields.', 'woocommerce' ) ); } global $wpdb; if ( ! $token->is_default() && $token->get_user_id() > 0 ) { $default_token = WC_Payment_Tokens::get_customer_default_token( $token->get_user_id() ); if ( is_null( $default_token ) ) { $token->set_default( true ); } } $payment_token_data = array( 'gateway_id' => $token->get_gateway_id( 'edit' ), 'token' => $token->get_token( 'edit' ), 'user_id' => $token->get_user_id( 'edit' ), 'type' => $token->get_type( 'edit' ), ); $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(); // Make sure all other tokens are not set to default. if ( $token->is_default() && $token->get_user_id() > 0 ) { WC_Payment_Tokens::set_users_default( $token->get_user_id(), $token_id ); } do_action( 'woocommerce_new_payment_token', $token_id ); } /** * Update a payment token. * * @since 3.0.0 * * @param WC_Payment_Token $token Payment token object. * * @throws Exception Throw exception if invalid or missing payment token fields. */ public function update( &$token ) { if ( false === $token->validate() ) { throw new Exception( __( 'Invalid or missing payment token fields.', 'woocommerce' ) ); } global $wpdb; $updated_props = array(); $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, true ) ) { continue; } $updated_props[] = $prop; $payment_token_data[ $prop ] = $token->{'get_' . $prop}( 'edit' ); } if ( ! empty( $payment_token_data ) ) { $wpdb->update( $wpdb->prefix . 'woocommerce_payment_tokens', $payment_token_data, array( 'token_id' => $token->get_id( 'edit' ) ) ); } $updated_extra_props = $this->save_extra_data( $token ); $updated_props = array_merge( $updated_props, $updated_extra_props ); $token->save_meta_data(); $token->apply_changes(); // Make sure all other tokens are not set to default. if ( $token->is_default() && $token->get_user_id() > 0 ) { WC_Payment_Tokens::set_users_default( $token->get_user_id(), $token->get_id() ); } do_action( 'woocommerce_payment_token_object_updated_props', $token, $updated_props ); do_action( 'woocommerce_payment_token_updated', $token->get_id() ); } /** * Remove a payment token from the database. * * @since 3.0.0 * @param WC_Payment_Token $token Payment token object. * @param bool $force_delete Unused param. */ public function delete( &$token, $force_delete = false ) { global $wpdb; $wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokens', array( 'token_id' => $token->get_id() ), array( '%d' ) ); $wpdb->delete( $wpdb->prefix . 'woocommerce_payment_tokenmeta', array( 'payment_token_id' => $token->get_id() ), array( '%d' ) ); do_action( 'woocommerce_payment_token_deleted', $token->get_id(), $token ); } /** * Read a token from the database. * * @since 3.0.0 * * @param WC_Payment_Token $token Payment token object. * * @throws Exception Throw exception if invalid payment token. */ public function read( &$token ) { global $wpdb; $data = $wpdb->get_row( $wpdb->prepare( "SELECT token, user_id, gateway_id, is_default FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d LIMIT 1", $token->get_id() ) ); if ( $data ) { $token->set_props( array( 'token' => $data->token, 'user_id' => $data->user_id, '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 ); } else { throw new Exception( __( 'Invalid payment token.', 'woocommerce' ) ); } } /** * Read extra data associated with the token (like last4 digits of a card for expiry dates). * * @param WC_Payment_Token $token Payment token object. * @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_metadata( 'payment_token', $token->get_id(), $key, true ) ); } } } /** * Saves extra token data as meta. * * @since 3.0.0 * @param WC_Payment_Token $token Payment token object. * @param bool $force By default, only changed props are updated. When this param is true all props are updated. * @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 = ! empty( $extra_data_keys ) ? array_combine( $extra_data_keys, $extra_data_keys ) : array(); $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_metadata( 'payment_token', $token->get_id(), $key, $token->{$function}( 'edit' ) ) ) { $updated_props[] = $key; } } } return $updated_props; } /** * Returns an array of objects (stdObject) matching specific token criteria. * 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 3.0.0 * @param array $args List of accepted args: token_id, gateway_id, user_id, type. * @return array */ public function get_tokens( $args ) { global $wpdb; $args = wp_parse_args( $args, array( 'token_id' => '', 'user_id' => '', 'gateway_id' => '', 'type' => '', ) ); $sql = "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens"; $where = array( '1=1' ); if ( $args['token_id'] ) { $token_ids = array_map( 'absint', is_array( $args['token_id'] ) ? $args['token_id'] : array( $args['token_id'] ) ); $where[] = "token_id IN ('" . implode( "','", array_map( 'esc_sql', $token_ids ) ) . "')"; } if ( $args['user_id'] ) { $where[] = $wpdb->prepare( 'user_id = %d', absint( $args['user_id'] ) ); } if ( $args['gateway_id'] ) { $gateway_ids = array( $args['gateway_id'] ); } else { $gateways = WC_Payment_Gateways::instance(); $gateway_ids = $gateways->get_payment_gateway_ids(); } $page = isset( $args['page'] ) ? absint( $args['page'] ) : 1; $posts_per_page = isset( $args['limit'] ) ? absint( $args['limit'] ) : get_option( 'posts_per_page' ); $pgstrt = absint( ( $page - 1 ) * $posts_per_page ) . ', '; $limits = 'LIMIT ' . $pgstrt . $posts_per_page; $gateway_ids[] = ''; $where[] = "gateway_id IN ('" . implode( "','", array_map( 'esc_sql', $gateway_ids ) ) . "')"; if ( $args['type'] ) { $where[] = $wpdb->prepare( 'type = %s', $args['type'] ); } // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared $token_results = $wpdb->get_results( $sql . ' WHERE ' . implode( ' AND ', $where ) . ' ' . $limits ); return $token_results; } /** * 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 3.0.0 * @param id $user_id User ID. * @return object */ public function get_users_default_token( $user_id ) { global $wpdb; return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE user_id = %d AND is_default = 1", $user_id ) ); } /** * Returns an stdObject of a token. * Should contain the fields token_id, gateway_id, token, user_id, type, is_default. * * @since 3.0.0 * @param id $token_id Token ID. * @return object */ public function get_token_by_id( $token_id ) { global $wpdb; return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d", $token_id ) ); } /** * Returns metadata for a specific payment token. * * @since 3.0.0 * @param id $token_id Token ID. * @return array */ public function get_metadata( $token_id ) { return get_metadata( 'payment_token', $token_id ); } /** * Get a token's type by ID. * * @since 3.0.0 * @param id $token_id Token ID. * @return string */ public function get_token_type_by_id( $token_id ) { global $wpdb; return $wpdb->get_var( $wpdb->prepare( "SELECT type FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token_id = %d", $token_id ) ); } /** * Update's a tokens default status in the database. Used for quickly * looping through tokens and setting their statuses instead of creating a bunch * of objects. * * @since 3.0.0 * * @param id $token_id Token ID. * @param bool $status Whether given payment token is the default payment token or not. * * @return void */ public function set_default_status( $token_id, $status = true ) { global $wpdb; $wpdb->update( $wpdb->prefix . 'woocommerce_payment_tokens', array( 'is_default' => $status ), array( 'token_id' => $token_id, ) ); } }