mirror of
				https://github.com/aclindsa/ofxgo.git
				synced 2025-11-03 18:03:25 -05:00 
			
		
		
		
	Add the ability to download Bank Transactions
This commit is contained in:
		
							
								
								
									
										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"`
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user