value( "SELECT credit_balance FROM {$table} WHERE id = %d", array( $customer_id ) ); } /** * Changes a customer's balance by a signed amount and writes a ledger line. * * The balance changes in one statement that refuses to go below zero, so two * requests at once cannot spend the same credit twice. * * @since 0.1.0 * * @param int $customer_id Customer id. * @param int $amount Signed amount in minor units. * @param string $reason Why. * @param string $related_type Related object type, such as `invoice`. * @param int $related_id Related object id. * @param int $created_by User id. * @return int|null New balance, or null when the balance would go below zero. */ public function adjust( int $customer_id, int $amount, string $reason, string $related_type = '', int $related_id = 0, int $created_by = 0 ): ?int { $customers = Tables::name( 'customers' ); $changed = $this->wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from Tables; values prepared. $this->wpdb->prepare( "UPDATE {$customers} SET credit_balance = credit_balance + %d WHERE id = %d AND credit_balance + %d >= 0", $amount, $customer_id, $amount ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared ); if ( 1 !== $changed ) { return null; } $balance = $this->balance( $customer_id ); $row = array( 'customer_id' => $customer_id, 'amount' => $amount, 'balance_after' => $balance, 'reason' => $reason, 'related_type' => $related_type, 'related_id' => $related_id, 'created_by' => $created_by, 'created_at_gmt' => Date::now_gmt(), ); $this->wpdb->insert( Tables::name( 'credits' ), $row, array( '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching return $balance; } /** * Ledger lines of a customer, newest first. * * @since 0.1.0 * * @param int $customer_id Customer id. * @param int $limit Maximum number of lines. * @return array> */ public function for_customer( int $customer_id, int $limit = 100 ): array { $table = Tables::name( 'credits' ); $rows = $this->rows( "SELECT c.*, u.display_name AS actor_name FROM {$table} c LEFT JOIN {$this->wpdb->users} u ON u.ID = c.created_by WHERE c.customer_id = %d ORDER BY c.id DESC LIMIT %d", array( $customer_id, max( 1, min( 500, $limit ) ) ) ); return array_map( static function ( array $row ) { foreach ( array( 'id', 'customer_id', 'amount', 'balance_after', 'related_id', 'created_by' ) as $column ) { $row[ $column ] = (int) $row[ $column ]; } $row['actor_name'] = (string) $row['actor_name']; return $row; }, $rows ); } }