Is a deleted_at column a good idea?
It is, provided every query filters on it, which is the part that fails. A partial index plus a view is the version that survives:
CREATE VIEW active_users AS SELECT * FROM users WHERE deleted_at IS NULL;
CREATE INDEX users_active_idx ON users (id) WHERE deleted_at IS NULL;
Then application code selects from the view and forgetting the filter becomes impossible rather than merely discouraged.
Should the primary key be the email or a generated id?
A surrogate id, with a unique constraint on the email. Natural keys change: people change emails, countries change codes, ISBNs get reissued. Every one of those becomes a cascading update across every table that referenced it.
Float or integer for prices?
Integer minor units, or a decimal type where the database has one. Floats cannot represent 0.10 exactly, so sums drift by cents and the drift shows up in reconciliation months later. Store 1099, format as 10.99 at the edge.