Payouts CSV: amounts in major units
Go · Go · beginner · modification
Accountants kept asking why the statement shows `123400` for a 1,234.00 payout, so the amount column is now in major units, the way a spreadsheet import expects it. The conversion is plain integer arithmetic on the minor-unit amount, with no `float64` round trip, so large payouts keep every digit. Checked against last month's statement for a few accounts: amounts match the dashboard.
Go 1.22 payouts service, standard library only. `Payout` (existing, `types.go`) has `PaidAt time.Time`, `Currency string` (an ISO 4217 code), `AmountMinor int64` and `Reference string`. Payouts go out in USD, EUR, GBP, JPY, KRW, KWD and BHD; `AmountMinor` is always positive (refunds are a separate statement) and below 10^15. `money.MinorUnits(code string) int` (existing, package `example.com/payouts/internal/money`) returns the ISO 4217 minor-unit digits of every supported currency: 2 for most, 0 for JPY and KRW, 3 for KWD and BHD. References are generated by us (`po_` + 24 letters and digits). The existing handler calls `WritePayoutsCSV` with the account's payouts, already sorted.
Requirements
- Accountants import the payouts statement into spreadsheets and keep dividing `amount_minor` by hand. Replace that column with `amount`: the payout in major units, written as a plain decimal with exactly as many fraction digits as the payout currency has minor units (`money.MinorUnits(p.Currency)`), and no decimal point when it has none. Examples: USD 1234 → `12.34`, USD 5 → `0.05`, JPY 1500 → `1500`, KWD 12345 → `12.345`.
- No thousands separators and no currency symbol: the `currency` column already carries the code. The other columns and the row order stay as they are.
- Writing stays streaming through `encoding/csv`; a write error is returned to the handler, as before.
Files touched
- internal/export/payouts.go
--- internal/export/payouts.go
import (
"encoding/csv"
+ "fmt"
"io"
- "strconv"
"time"
)
func WritePayoutsCSV(w io.Writer, payouts []Payout) error {
cw := csv.NewWriter(w)
- if err := cw.Write([]string{"paid_at", "currency", "amount_minor", "reference"}); err != nil {
+ if err := cw.Write([]string{"paid_at", "currency", "amount", "reference"}); err != nil {
return err
}
p.PaidAt.UTC().Format(time.DateOnly),
p.Currency,
- strconv.FormatInt(p.AmountMinor, 10),
+ formatMajor(p.AmountMinor),
p.Reference,
}
}
+// formatMajor renders an amount held in minor units as a plain decimal in
+// major units, the way spreadsheet imports expect it.
+func formatMajor(minor int64) string {
+ return fmt.Sprintf("%d.%02d", minor/100, minor%100)
+}
+