mirror of
https://github.com/luxfi/bank.git
synced 2026-10-30 17:02:07 +00:00
RegisterAccountHooks opens a balance when an account is created, setting available and held to 0. The available field was Required, and a required number field refuses its zero value — "available: cannot be blank" — so the save failed on EVERY account. The hook logs the error and returns nil, which is the right shape for a side effect that must not fail account creation, and is also why this was silent: the account existed, the balance did not, and the first thing to look one up got "no rows in result set". Zero is not a missing balance. It is the balance every account opens with, and requiring the field made that state unrepresentable. held was never marked required, which is what the pair should always have looked like. Found by binding the hooks in a test and asking whether the balance was there. It was not. Also covered on the same path, because it is the one that decides whether money moves: a transaction in a currency the bank cannot price is refused on create, and one in USD, EUR or BTC still passes. That closes the loop on the earlier CanPrice fix — it was proved at the function, and is now proved through the hook a request actually travels. hooks 12.5% -> 18.3%. Co-authored-by: Hanzo Dev <[email protected]>
67 lines
1.8 KiB
Go
67 lines
1.8 KiB
Go
package collections
|
|
|
|
import "github.com/hanzoai/base/core"
|
|
|
|
const BalanceCollectionName = "balances"
|
|
|
|
// EnsureBalanceCollection creates the balances collection if it does not exist.
|
|
// Each record tracks a single currency balance for an account.
|
|
// Accounts can have multiple balance records (one per currency).
|
|
func EnsureBalanceCollection(app core.App) error {
|
|
_, err := app.FindCollectionByNameOrId(BalanceCollectionName)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
c := core.NewBaseCollection(BalanceCollectionName, BalanceCollectionName)
|
|
|
|
// Superuser only (nil rules) in every mode — balances are read through the
|
|
// authenticated /v1/bank routes, never as anonymous collection reads.
|
|
|
|
c.Fields.Add(
|
|
&core.RelationField{
|
|
Name: "account",
|
|
CollectionId: AccountCollectionName,
|
|
Required: true,
|
|
MaxSelect: 1,
|
|
},
|
|
|
|
// ISO 4217 currency code.
|
|
&core.TextField{
|
|
Name: "currency",
|
|
Required: true,
|
|
Min: 3,
|
|
Max: 3,
|
|
},
|
|
|
|
// Available balance in minor units (cents).
|
|
//
|
|
// NOT Required: a required number field refuses its zero value ("available:
|
|
// cannot be blank"), and zero is the balance every account opens with. The
|
|
// hook that creates a balance on account creation set it to 0 and was
|
|
// refused every time — it logs and returns nil, so each new customer was
|
|
// created with no balance row at all and nothing said so. `held` was never
|
|
// marked required, which is what the pair should always have looked like.
|
|
&core.NumberField{
|
|
Name: "available",
|
|
},
|
|
|
|
// Held (pending) balance in minor units.
|
|
&core.NumberField{
|
|
Name: "held",
|
|
},
|
|
|
|
&core.AutodateField{
|
|
Name: "created",
|
|
OnCreate: true,
|
|
},
|
|
&core.AutodateField{
|
|
Name: "updated",
|
|
OnCreate: true,
|
|
OnUpdate: true,
|
|
},
|
|
)
|
|
|
|
return app.Save(c)
|
|
}
|