mirror of
https://github.com/luxfi/captable.git
synced 2026-10-30 17:01:02 +00:00
G-15 — IRS FIRE + IRIS e-file adapter for 1099 series, with CFSF state routing and per-issuer TCC management. pkg/tax/iris — IRIS A2A adapter (mandatory for TY 2024+): - Client with JWT bearer auth (OAuth2 password-grant exchange against IRS Identity), env switch (Production / AATS sandbox), per-call re-auth on 401, exponential-backoff + jitter retry with Retry-After honoring, X-IRS-TCC header on every submission - SubmitForm / GetSubmissionStatus / ListSubmissions / CorrectSubmission cover all 8 form types: 1099-DIV / B / INT / MISC / NEC / OID / K / R - Typed Form1099*Data per form covering every IRS-published box - IRIS schema XML marshaling (header + per-payee Form1099* element with per-form payload element name; FormTypeCd discriminator; correction envelope with PaymentYearTypeCd=G and OriginalReceiptID) - Validation: TCC format, EIN 9-digit, TIN 9-digit per payee, payee data type compatible with FormType, max 100,000 payees per submission (Pub 5717 §3.1), original_receipt_id required for corrections - Acknowledgment + Status parsing for both XML and JSON wire formats pkg/tax/fire — FIRE legacy adapter (pre-TY-2024 corrections): - Client with multipart upload, env switch (Production / Test) - Fixed-width 750-byte records per Publication 1220 Part C — T (Transmitter), A (Payer), B (Payee), C (End-of-A), F (End-of-File), K (State Totals) with positional layout: numeric right-justified zero-padded, alphanumeric left-justified blank-padded, dollar amounts in cents - Per-payee amount codes mapped to positions 55..187 (12-position fields, codes '1'..'9' / 'A'..'I' = 18 codes total) and per-amount- code totals carried on the C and K records - All 8 form codes: 1099-DIV (1), 1099-INT (6), 1099-B (B), 1099-MISC (A), 1099-NEC (NE), 1099-OID (D), 1099-K (MC), 1099-R (9) - CFSF state-totals block on B records (positions 663-748) and per- state K records with CombinedFedStateCode pkg/tax/cfsf — Combined Federal/State Filing routing: - StateProfile registry covering 32 CFSF participants + DC + WV with their IRS-assigned numeric codes (CA=06, NY=n/a, MA=25, etc.) plus 19 non-CFSF states with the per-state DOR portal references (PortalDORWebsite / PortalFSET / PortalNone for no-income-tax states) - RouteToStates(submission, []State) -> []*StateFiling distinguishing cfsf (federal filing auto-routes), direct (operator hand-off to named portal), and none (no broad state income tax) - IRISWrapper adapts *iris.FormSubmission to FormSubmissionForRouting by reading StateCd / StateTaxWithheld / StateIncome from each payee's typed Form1099*Data pkg/tax/tcc — Transmitter Control Code registry: - Per-(issuer, system, env) scoping: same issuer may hold separate IRIS / FIRE TCCs and separate Sandbox / Production TCCs - Agency split: AgencyIssuer (issuer files own returns) vs AgencyTransmitter (Lux files as agent under transmitter TCC) - Validation: TCC 5-char, EIN 9-digit, agency requires entity id when transmitter pkg/tax — integration hook (submit.go): - tax.SubmitForm + tax.SubmitForm1099B route by default to IRIS for TY >= 2024 (IRISMandatoryTaxYear per IRS final regs), fall back to FIRE only when ForceLegacyFIRE is set or TaxYear < 2024 - SubmissionOptions binds the chosen IRIS / FIRE clients plus a TCCRegistry for per-issuer TCC resolution - Builders translate captable Form1099DIV / Form1099B into the transport's typed submission shape Coverage: go build / vet / -race clean across the full module. 68 tests in the four new packages (16 IRIS happy paths × form types + validation suite + correction round trip + 429 retry + re-auth + XML ack parsing; 8 FIRE happy paths × form codes + record-layout tests + CFSF K-record test + rejection path; 9 CFSF routing tests covering the spec-required CA/MA/OH/OK/IN CFSF participants + NY/IL direct + TX/FL no-tax; 9 TCC scoping + validation tests), all passing. Source-of-design: Public-Spec Source-ref: https://www.irs.gov/e-file-providers/iris-online-portal Source-ref: IRS Publication 5717 (IRIS A2A Specifications) Source-ref: IRS Publication 5718 (IRIS Electronic Filing Application Guide) Source-ref: IRS Publication 1220 (FIRE Specifications) Source-ref: https://www.irs.gov/pub/irs-pdf/p1220.pdf Clean-Team-Member: dev-agent (P0 G-15 implementation) Signed-off-by: dev <[email protected]>
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
// CFSF routing engine — given a federal 1099 submission, produce the
|
|
// per-state filing decisions (CFSF-satisfied / direct-file / no-tax
|
|
// state).
|
|
//
|
|
// Source-of-design: Public-Spec
|
|
// Source-ref: IRS Publication 1220 Part A §10
|
|
package cfsf
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// RouteToStates produces the per-state filing decisions for a federal
|
|
// 1099 submission. Each requested state appears in the output:
|
|
//
|
|
// - CFSF participant: routing_type="cfsf", CFSFCode populated; the
|
|
// federal IRIS/FIRE filing carries the per-payee state slice and
|
|
// the IRS auto-routes to the state. No direct-file action needed.
|
|
//
|
|
// - Non-CFSF state with a DOR portal: routing_type="direct", Portal
|
|
// and PortalURL populated; the operator must hand-file (CSV upload
|
|
// or FSET) at the named portal.
|
|
//
|
|
// - Non-CFSF state with no broad state income tax (FL/TX/NV/etc.):
|
|
// routing_type="none"; no action.
|
|
//
|
|
// If the requested states slice is empty, the function infers states
|
|
// from the submission's StateBreakdown — any state that appears in the
|
|
// federal payee data is routed automatically.
|
|
func RouteToStates(fs FormSubmissionForRouting, states []State) ([]*StateFiling, error) {
|
|
if fs == nil {
|
|
return nil, fmt.Errorf("cfsf: submission is nil")
|
|
}
|
|
breakdown := fs.StateBreakdown()
|
|
if len(states) == 0 {
|
|
states = make([]State, 0, len(breakdown))
|
|
for s := range breakdown {
|
|
states = append(states, s)
|
|
}
|
|
sort.Slice(states, func(i, j int) bool { return states[i] < states[j] })
|
|
}
|
|
|
|
out := make([]*StateFiling, 0, len(states))
|
|
for _, s := range states {
|
|
filing := buildFiling(s, breakdown[s])
|
|
out = append(out, filing)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// buildFiling produces a single StateFiling from a state code + the
|
|
// per-payee state breakdown for that state.
|
|
func buildFiling(s State, amounts []StateAmount) *StateFiling {
|
|
prof, known := GetProfile(s)
|
|
filing := &StateFiling{
|
|
State: s,
|
|
PayeeCount: len(amounts),
|
|
}
|
|
for _, a := range amounts {
|
|
filing.TotalStateTaxWithheld += a.TaxWithheld
|
|
filing.TotalStateIncome += a.Income
|
|
}
|
|
if !known {
|
|
filing.RoutingType = "direct"
|
|
filing.Portal = PortalDORWebsite
|
|
filing.Notes = fmt.Sprintf("state %s not in CFSF registry — verify per-state portal manually", s)
|
|
return filing
|
|
}
|
|
switch {
|
|
case prof.ParticipatesInCFSF:
|
|
filing.RoutingType = "cfsf"
|
|
filing.CFSFCode = prof.CFSFCode
|
|
filing.Notes = fmt.Sprintf("federal filing auto-routes to %s under CFSF (state code %s)", s, prof.CFSFCode)
|
|
case prof.FallbackPortal == PortalNone:
|
|
filing.RoutingType = "none"
|
|
filing.Notes = prof.FallbackURL
|
|
default:
|
|
filing.RoutingType = "direct"
|
|
filing.Portal = prof.FallbackPortal
|
|
filing.PortalURL = prof.FallbackURL
|
|
if prof.FormThreshold > 0 && filing.PayeeCount < prof.FormThreshold {
|
|
filing.Notes = fmt.Sprintf("below %s e-file threshold (%d forms); paper filing permitted",
|
|
s, prof.FormThreshold)
|
|
} else {
|
|
filing.Notes = fmt.Sprintf("file directly at %s DOR portal (%s)", s, string(prof.FallbackPortal))
|
|
}
|
|
}
|
|
return filing
|
|
}
|