mirror of
https://github.com/aclindsa/ofxgo.git
synced 2024-11-22 11:30:05 -05:00
Add the ability to download Bank Transactions
This commit is contained in:
parent
59c8bce519
commit
cd1e7b480a
130
banking.go
Normal file
130
banking.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package ofxgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/golang/go/src/encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BankAcct struct {
|
||||||
|
XMLName xml.Name // BANKACCTTO or BANKACCTFROM
|
||||||
|
BankId String `xml:"BANKID"`
|
||||||
|
BranchId String `xml:"BRANCHID,omitempty"` // Unused in USA
|
||||||
|
AcctId String `xml:"ACCTID"`
|
||||||
|
AcctType String `xml:"ACCTTYPE"` // One of CHECKING, SAVINGS, MONEYMRKT, CREDITLINE, CD
|
||||||
|
AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatementRequest struct {
|
||||||
|
XMLName xml.Name `xml:"STMTTRNRQ"`
|
||||||
|
TrnUID UID `xml:"TRNUID"`
|
||||||
|
BankAcctFrom BankAcct `xml:"STMTRQ>BANKACCTFROM"`
|
||||||
|
DtStart Date `xml:"STMTRQ>INCTRAN>DTSTART,omitempty"`
|
||||||
|
DtEnd Date `xml:"STMTRQ>INCTRAN>DTEND,omitempty"`
|
||||||
|
Include Boolean `xml:"STMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance)
|
||||||
|
IncludePending Boolean `xml:"STMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions
|
||||||
|
IncludeTranImage Boolean `xml:"STMTRQ>INCLUDETRANIMAGE,omitempty"` // Include transaction images
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StatementRequest) Name() string {
|
||||||
|
return "STMTTRNRQ"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StatementRequest) Valid() (bool, error) {
|
||||||
|
if ok, err := r.TrnUID.Valid(); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Payee struct {
|
||||||
|
XMLName xml.Name `xml:"PAYEE"`
|
||||||
|
Name String `xml:"NAME"`
|
||||||
|
Addr1 String `xml:"ADDR1"`
|
||||||
|
Addr2 String `xml:"ADDR2,omitempty"`
|
||||||
|
Addr3 String `xml:"ADDR3,omitempty"`
|
||||||
|
City String `xml:"CITY"`
|
||||||
|
State String `xml:"STATE"`
|
||||||
|
PostalCode String `xml:"POSTALCODE"`
|
||||||
|
Country String `xml:"COUNTRY,omitempty"`
|
||||||
|
Phone String `xml:"PHONE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
XMLName xml.Name `xml:"STMTTRN"`
|
||||||
|
TrnType String `xml:"TRNTYPE"` // One of CREDIT, DEBIT, INT (interest earned or paid. Note: Depends on signage of amount), DIV, FEE, SRVCHG (service charge), DEP (deposit), ATM (Note: Depends on signage of amount), POS (Note: Depends on signage of amount), XFER, CHECK, PAYMENT, CASH, DIRECTDEP, DIRECTDEBIT, REPEATPMT, HOLD (Only valid in <STMTTRNP>), OTHER
|
||||||
|
DtPosted Date `xml:"DTPOSTED"`
|
||||||
|
DtUser Date `xml:"DTUSER,omitempty"`
|
||||||
|
DtAvail Date `xml:"DTAVAIL,omitempty"`
|
||||||
|
TrnAmt Amount `xml:"TRNAMT"`
|
||||||
|
FiTId String `xml:"FITID"`
|
||||||
|
CorrectFiTId String `xml:"CORRECTFITID,omitempty"` // Transaction Id that this transaction corrects, if present
|
||||||
|
CorrectAction String `xml:"CORRECTACTION,omitempty"` // One of DELETE, REPLACE
|
||||||
|
SrvrTId String `xml:"SRVRTID,omitempty"`
|
||||||
|
CheckNum String `xml:"CHECKNUM,omitempty"`
|
||||||
|
RefNum String `xml:"REFNUM,omitempty"`
|
||||||
|
SIC Int `xml:"SIC,omitempty"` // Standard Industrial Code
|
||||||
|
PayeeId String `xml:"PAYEEID,omitempty"`
|
||||||
|
// Note: Servers should provide NAME or PAYEE, but not both
|
||||||
|
Name String `xml:"NAME,omitempty"`
|
||||||
|
Payee Payee `xml:"PAYEE,omitempty"`
|
||||||
|
ExtdName String `xml:"EXTDNAME,omitempty"` // Extended name of payee or transaction description
|
||||||
|
// TODO BANKACCTTO
|
||||||
|
// TODO CCACCTTO
|
||||||
|
Memo String `xml:"MEMO,omitempty"` // Extra information (not in NAME)
|
||||||
|
// TODO IMAGEDATA
|
||||||
|
Currency String `xml:"CURRENCY,omitempty"` // If different from CURDEF in STMTTRS
|
||||||
|
OrigCurrency String `xml:"ORIGCURRENCY,omitempty"` // If different from CURDEF in STMTTRS
|
||||||
|
Inv401kSource String `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST (Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST.)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionList struct {
|
||||||
|
XMLName xml.Name `xml:"BANKTRANLIST"`
|
||||||
|
DtStart Date `xml:"DTSTART"`
|
||||||
|
DtEnd Date `xml:"DTEND"`
|
||||||
|
Transactions []Transaction `xml:"STMTTRN"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatementResponse struct {
|
||||||
|
XMLName xml.Name `xml:"STMTTRNRS"`
|
||||||
|
TrnUID UID `xml:"TRNUID"`
|
||||||
|
CurDef String `xml:"STMTRS>CURDEF"`
|
||||||
|
BankAcctFrom BankAcct `xml:"STMTRS>BANKACCTFROM"`
|
||||||
|
BankTranList TransactionList `xml:"STMTRS>BANKTRANLIST,omitempty"`
|
||||||
|
BalAmt Amount `xml:"STMTRS>LEDGERBAL>BALAMT"`
|
||||||
|
DtAsOf Date `xml:"STMTRS>LEDGERBAL>DTASOF"`
|
||||||
|
// TODO AVAILBAL et. al.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr StatementResponse) Name() string {
|
||||||
|
return "STMTTRNRS"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr StatementResponse) Valid() (bool, error) {
|
||||||
|
//TODO implement
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
func DecodeBankingMessageSet(d *xml.Decoder, start xml.StartElement) ([]Message, error) {
|
||||||
|
var msgs []Message
|
||||||
|
for {
|
||||||
|
tok, err := nextNonWhitespaceToken(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local {
|
||||||
|
// If we found the end of our starting element, we're done parsing
|
||||||
|
return msgs, nil
|
||||||
|
} else if startElement, ok := tok.(xml.StartElement); ok {
|
||||||
|
switch startElement.Name.Local {
|
||||||
|
case "STMTTRNRS":
|
||||||
|
var info StatementResponse
|
||||||
|
if err := d.DecodeElement(&info, &startElement); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msgs = append(msgs, Message(info))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Unsupported banking response tag: " + startElement.Name.Local)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Didn't find an opening element")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
ofx.go
27
ofx.go
@ -21,7 +21,7 @@ type Request struct {
|
|||||||
Version string // String for OFX header, defaults to 203
|
Version string // String for OFX header, defaults to 203
|
||||||
Signon SignonRequest //<SIGNONMSGSETV1>
|
Signon SignonRequest //<SIGNONMSGSETV1>
|
||||||
Signup []Message //<SIGNUPMSGSETV1>
|
Signup []Message //<SIGNUPMSGSETV1>
|
||||||
//<BANKMSGSETV1>
|
Banking []Message //<BANKMSGSETV1>
|
||||||
//<CREDITCARDMSGSETV1>
|
//<CREDITCARDMSGSETV1>
|
||||||
//<LOANMSGSETV1>
|
//<LOANMSGSETV1>
|
||||||
//<INVSTMTMSGSETV1>
|
//<INVSTMTMSGSETV1>
|
||||||
@ -112,6 +112,9 @@ NEWFILEUID:NONE
|
|||||||
if err := oq.marshalMessageSet(encoder, oq.Signup, "SIGNUPMSGSRQV1"); err != nil {
|
if err := oq.marshalMessageSet(encoder, oq.Signup, "SIGNUPMSGSRQV1"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := oq.marshalMessageSet(encoder, oq.Banking, "BANKMSGSRQV1"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := oq.marshalMessageSet(encoder, oq.Profile, "PROFMSGSRQV1"); err != nil {
|
if err := oq.marshalMessageSet(encoder, oq.Profile, "PROFMSGSRQV1"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -126,7 +129,7 @@ NEWFILEUID:NONE
|
|||||||
return &b, nil
|
return &b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (oq *Request) Request() (*Response, error) {
|
func (oq *Request) Request() (*http.Response, error) {
|
||||||
oq.Signon.DtClient = Date(time.Now())
|
oq.Signon.DtClient = Date(time.Now())
|
||||||
|
|
||||||
b, err := oq.Marshal()
|
b, err := oq.Marshal()
|
||||||
@ -137,12 +140,21 @@ func (oq *Request) Request() (*Response, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
if response.StatusCode != 200 {
|
||||||
return nil, errors.New("OFXQuery request status: " + response.Status)
|
return nil, errors.New("OFXQuery request status: " + response.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oq *Request) RequestAndParse() (*Response, error) {
|
||||||
|
response, err := oq.Request()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
// Help the parser out by giving it a clue about what header format to
|
// Help the parser out by giving it a clue about what header format to
|
||||||
// expect
|
// expect
|
||||||
var xmlVersion bool = true
|
var xmlVersion bool = true
|
||||||
@ -163,7 +175,7 @@ type Response struct {
|
|||||||
Version string // String for OFX header, defaults to 203
|
Version string // String for OFX header, defaults to 203
|
||||||
Signon SignonResponse //<SIGNONMSGSETV1>
|
Signon SignonResponse //<SIGNONMSGSETV1>
|
||||||
Signup []Message //<SIGNUPMSGSETV1>
|
Signup []Message //<SIGNUPMSGSETV1>
|
||||||
//<BANKMSGSETV1>
|
Banking []Message //<BANKMSGSETV1>
|
||||||
//<CREDITCARDMSGSETV1>
|
//<CREDITCARDMSGSETV1>
|
||||||
//<LOANMSGSETV1>
|
//<LOANMSGSETV1>
|
||||||
//<INVSTMTMSGSETV1>
|
//<INVSTMTMSGSETV1>
|
||||||
@ -394,7 +406,12 @@ func (or *Response) Unmarshal(reader io.Reader, xmlVersion bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
or.Signup = msgs
|
or.Signup = msgs
|
||||||
//case "BANKMSGSRSV1":
|
case "BANKMSGSRSV1":
|
||||||
|
msgs, err := DecodeBankingMessageSet(decoder, start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
or.Banking = msgs
|
||||||
//case "CREDITCARDMSGSRSV1":
|
//case "CREDITCARDMSGSRSV1":
|
||||||
//case "LOANMSGSRSV1":
|
//case "LOANMSGSRSV1":
|
||||||
//case "INVSTMTMSGSRSV1":
|
//case "INVSTMTMSGSRSV1":
|
||||||
|
@ -42,15 +42,6 @@ type HolderInfo struct {
|
|||||||
HolderType String `xml:"HOLDERTYPE,omitempty"` // One of INDIVIDUAL, JOINT, CUSTODIAL, TRUST, OTHER
|
HolderType String `xml:"HOLDERTYPE,omitempty"` // One of INDIVIDUAL, JOINT, CUSTODIAL, TRUST, OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
type BankAcct struct {
|
|
||||||
XMLName xml.Name // BANKACCTTO or BANKACCTFROM
|
|
||||||
BankId String `xml:"BANKID"`
|
|
||||||
BranchId String `xml:"BRANCHID,omitempty"` // Unused in USA
|
|
||||||
AcctId String `xml:"ACCTID"`
|
|
||||||
AcctType String `xml:"ACCTTYPE"` // One of CHECKING, SAVINGS, MONEYMRKT, CREDITLINE, CD
|
|
||||||
AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA
|
|
||||||
}
|
|
||||||
|
|
||||||
type BankAcctInfo struct {
|
type BankAcctInfo struct {
|
||||||
XMLName xml.Name `xml:"BANKACCTINFO"`
|
XMLName xml.Name `xml:"BANKACCTINFO"`
|
||||||
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
|
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
|
||||||
|
Loading…
Reference in New Issue
Block a user