Implement Signup message parsing

This commit is contained in:
Aaron Lindsay 2017-03-11 21:13:06 -05:00
parent 74b0ff7816
commit 0e62af64e3
6 changed files with 190 additions and 39 deletions

View File

@ -1,23 +0,0 @@
package ofxgo
import (
"github.com/golang/go/src/encoding/xml"
)
type AcctInfoRequest struct {
XMLName xml.Name `xml:"ACCTINFOTRNRQ"`
TrnUID UID `xml:"TRNUID"`
CltCookie Int `xml:"CLTCOOKIE"`
DtAcctup Date `xml:"ACCTINFORQ>DTACCTUP"`
}
func (r *AcctInfoRequest) Name() string {
return "ACCTINFOTRNRQ"
}
func (r *AcctInfoRequest) Valid() (bool, error) {
if ok, err := r.TrnUID.Valid(); !ok {
return false, err
}
return true, nil
}

16
ofx.go
View File

@ -127,7 +127,7 @@ NEWFILEUID:NONE
}
func (oq *Request) Request() (*Response, error) {
oq.Signon.Dtclient = Date(time.Now())
oq.Signon.DtClient = Date(time.Now())
b, err := oq.Marshal()
if err != nil {
@ -368,12 +368,12 @@ func (or *Response) Unmarshal(reader io.Reader, xmlVersion bool) error {
} else if start, ok := tok.(xml.StartElement); ok {
// TODO decode other types
switch start.Name.Local {
// case "SIGNUPMSGSRSV1":
// msgs, err := DecodeSignupMessageSet(decoder, start)
// if err != nil {
// return err
// }
// or.Signup = msgs
case "SIGNUPMSGSRSV1":
msgs, err := DecodeSignupMessageSet(decoder, start)
if err != nil {
return err
}
or.Signup = msgs
//case "BANKMSGSRSV1":
//case "CREDITCARDMSGSRSV1":
//case "LOANMSGSRSV1":
@ -391,7 +391,7 @@ func (or *Response) Unmarshal(reader io.Reader, xmlVersion bool) error {
return err
}
or.Profile = msgs
//case "IMAGEMSGSRSV1":
//case "IMAGEMSGSRSV1":
default:
return errors.New("Unsupported message set: " + start.Name.Local)
}

View File

@ -9,7 +9,7 @@ type ProfileRequest struct {
XMLName xml.Name `xml:"PROFTRNRQ"`
TrnUID UID `xml:"TRNUID"`
ClientRouting String `xml:"PROFRQ>CLIENTROUTING"` // Forced to NONE
DtProfup Date `xml:"PROFRQ>DTPROFUP"`
DtProfUp Date `xml:"PROFRQ>DTPROFUP"`
}
func (r *ProfileRequest) Name() string {
@ -29,7 +29,7 @@ type SignonInfo struct {
SignonRealm String `xml:"SIGNONREALM"`
Min Int `xml:"MIN"` // Minimum number of password characters
Max Int `xml:"MAX"` // Maximum number of password characters
Chartype String `xml:"CHARTYPE"` // ALPHAONLY, NUMERICONLY, ALPHAORNUMERIC, ALPHAANDNUMERIC
CharType String `xml:"CHARTYPE"` // ALPHAONLY, NUMERICONLY, ALPHAORNUMERIC, ALPHAANDNUMERIC
CaseSen Boolean `xml:"CASESEN"` // Password is case-sensitive?
Special Boolean `xml:"SPECIAL"` // Special characters allowed?
Spaces Boolean `xml:"SPACES"` // Spaces allowed?
@ -101,8 +101,8 @@ type ProfileResponse struct {
TrnUID UID `xml:"TRNUID"`
MessageSetList MessageSetList `xml:"PROFRS>MSGSETLIST"`
SignonInfoList []SignonInfo `xml:"PROFRS>SIGNONINFOLIST>SIGNONINFO"`
DtProfup Date `xml:"PROFRS>DTPROFUP"`
Finame String `xml:"PROFRS>FINAME"`
DtProfUp Date `xml:"PROFRS>DTPROFUP"`
FiName String `xml:"PROFRS>FINAME"`
Addr1 String `xml:"PROFRS>ADDR1"`
Addr2 String `xml:"PROFRS>ADDR2,omitempty"`
Addr3 String `xml:"PROFRS>ADDR3,omitempty"`

View File

@ -7,7 +7,7 @@ import (
type SignonRequest struct {
XMLName xml.Name `xml:"SONRQ"`
Dtclient Date `xml:"DTCLIENT"` // Overridden in Request.Request()
DtClient Date `xml:"DTCLIENT"` // Overridden in Request.Request()
UserId String `xml:"USERID"`
UserPass String `xml:"USERPASS,omitempty"`
UserKey String `xml:"USERKEY,omitempty"`
@ -78,12 +78,12 @@ func (s *Status) Valid() (bool, error) {
type SignonResponse struct {
XMLName xml.Name `xml:"SONRS"`
Status Status `xml:"STATUS"`
Dtserver Date `xml:"DTSERVER"`
DtServer Date `xml:"DTSERVER"`
UserKey String `xml:"USERKEY,omitempty"`
TsKeyExpire Date `xml:"TSKEYEXPIRE,omitempty"`
Language String `xml:"LANGUAGE"`
Dtprofup Date `xml:"DTPROFUP,omitempty"`
Dtacctup Date `xml:"DTACCTUP,omitempty"`
DtProfUp Date `xml:"DTPROFUP,omitempty"`
DtAcctUp Date `xml:"DTACCTUP,omitempty"`
Org String `xml:"FI>ORG"`
Fid String `xml:"FI>FID"`
SessCookie String `xml:"SESSCOOKIE,omitempty"`

170
signup.go Normal file
View File

@ -0,0 +1,170 @@
package ofxgo
import (
"errors"
"fmt"
"github.com/golang/go/src/encoding/xml"
)
type AcctInfoRequest struct {
XMLName xml.Name `xml:"ACCTINFOTRNRQ"`
TrnUID UID `xml:"TRNUID"`
CltCookie Int `xml:"CLTCOOKIE"`
DtAcctUp Date `xml:"ACCTINFORQ>DTACCTUP"`
}
func (r *AcctInfoRequest) Name() string {
return "ACCTINFOTRNRQ"
}
func (r *AcctInfoRequest) Valid() (bool, error) {
if ok, err := r.TrnUID.Valid(); !ok {
return false, err
}
return true, nil
}
type HolderInfo struct {
XMLName xml.Name
FirstName String `xml:"FIRSTNAME"`
MiddleName String `xml:"MIDDLENAME,omitempty"`
LastName String `xml:"LASTNAME"`
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"`
DayPhone String `xml:"DAYPHONE,omitempty"`
EvePhone String `xml:"EVEPHONE,omitempty"`
Email String `xml:"EMAIL,omitempty"`
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 {
XMLName xml.Name `xml:"BANKACCTINFO"`
BankAcctFrom BankAcct `xml:"BANKACCTFROM"`
SupTxDl Boolean `xml:"SUPTXDL"` // Supports downloading transactions (as opposed to balance only)
XferSrc Boolean `xml:"XFERSRC"` // Enabled as source for intra/interbank transfer
XferDest Boolean `xml:"XFERDEST"` // Enabled as destination for intra/interbank transfer
MaturityDate Date `xml:"MATURITYDATE,omitempty"` // Maturity date for CD, if CD
MaturityAmt Amount `xml:"MATURITYAMOUNT,omitempty"` // Maturity amount for CD, if CD
MinBalReq Amount `xml:"MINBALREQ,omitempty"` // Minimum balance required to avoid service fees
AcctClassification String `xml:"ACCTCLASSIFICATION,omitempty"` // One of PERSONAL, BUSINESS, CORPORATE, OTHER
OverdraftLimit Amount `xml:"OVERDRAFTLIMIT,omitempty"`
SvcStatus String `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
}
// Make pointers to these structs print nicely
func (bai *BankAcctInfo) String() string {
return fmt.Sprintf("%+v", *bai)
}
type CCAcct struct {
XMLName xml.Name // CCACCTTO or CCACCTFROM
AcctId String `xml:"ACCTID"`
AcctKey String `xml:"ACCTKEY,omitempty"` // Unused in USA
}
type CCAcctInfo struct {
XMLName xml.Name `xml:"CCACCTINFO"`
CCAcctFrom CCAcct `xml:"CCACCTFROM"`
SupTxDl Boolean `xml:"SUPTXDL"` // Supports downloading transactions (as opposed to balance only)
XferSrc Boolean `xml:"XFERSRC"` // Enabled as source for intra/interbank transfer
XferDest Boolean `xml:"XFERDEST"` // Enabled as destination for intra/interbank transfer
AcctClassification String `xml:"ACCTCLASSIFICATION,omitempty"` // One of PERSONAL, BUSINESS, CORPORATE, OTHER
SvcStatus String `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
}
// Make pointers to these structs print nicely
func (ci *CCAcctInfo) String() string {
return fmt.Sprintf("%+v", *ci)
}
type InvAcct struct {
XMLName xml.Name // INVACCTTO or INVACCTFROM
BrokerId String `xml:"BROKERID"`
AcctId String `xml:"ACCTID"`
}
type InvAcctInfo struct {
XMLName xml.Name `xml:"INVACCTINFO"`
INVAcctFrom InvAcct `xml:"INVACCTFROM"`
UsProductType String `xml:"USPRODUCTTYPE"` // One of 401K, 403B, IRA, KEOGH, OTHER, SARSEP, SIMPLE, NORMAL, TDA, TRUST, UGMA
Checking Boolean `xml:"CHECKING"` // Has check-writing privileges
SvcStatus String `xml:"SVCSTATUS"` // One of AVAIL (available, but not yet requested), PEND (requested, but not yet available), ACTIVE
InvAcctType String `xml:"INVACCTTYPE,omitempty"` // One of INDIVIDUAL, JOINT, TRUST, CORPORATE
OptionLevel String `xml:"OPTIONLEVEL,omitempty"` // Text desribing option trading privileges
}
// Make pointers to these structs print nicely
func (iai *InvAcctInfo) String() string {
return fmt.Sprintf("%+v", *iai)
}
type AcctInfo struct {
XMLName xml.Name `xml:"ACCTINFO"`
Name String `xml:"NAME,omitempty"`
Desc String `xml:"DESC,omitempty"`
Phone String `xml:"PHONE,omitempty"`
PrimaryHolder HolderInfo `xml:"HOLDERINFO>PRIMARYHOLDER,omitempty"`
SecondaryHolder HolderInfo `xml:"HOLDERINFO>SECONDARYHOLDER,omitempty"`
// Only one of the rest of the fields will be valid for any given AcctInfo
BankAcctInfo *BankAcctInfo `xml:"BANKACCTINFO,omitempty"`
CCAcctInfo *CCAcctInfo `xml:"CCACCTINFO,omitempty"`
InvAcctInfo *InvAcctInfo `xml:"INVACCTINFO,omitempty"`
// TODO LOANACCTINFO
// TODO BPACCTINFO?
}
type AcctInfoResponse struct {
XMLName xml.Name `xml:"ACCTINFOTRNRS"`
TrnUID UID `xml:"TRNUID"`
DtAcctUp Date `xml:"ACCTINFORS>DTACCTUP"`
AcctInfo []AcctInfo `xml:"ACCTINFORS>ACCTINFO,omitempty"`
}
func (air AcctInfoResponse) Name() string {
return "ACCTINFORS"
}
func (air AcctInfoResponse) Valid() (bool, error) {
//TODO implement
return true, nil
}
func DecodeSignupMessageSet(d *xml.Decoder, start xml.StartElement) ([]Message, error) {
var msgs []Message
for {
tok, err := d.Token()
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 "ACCTINFOTRNRS":
var info AcctInfoResponse
if err := d.DecodeElement(&info, &startElement); err != nil {
return nil, err
}
msgs = append(msgs, Message(info))
default:
return nil, errors.New("Unsupported signup response tag: " + startElement.Name.Local)
}
} else {
return nil, errors.New("Didn't find an opening element")
}
}
}

View File

@ -27,6 +27,10 @@ func (oi *Int) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return nil
}
type Amount string
// TODO parse Amount into big.Rat?
type Date time.Time
var ofxDateFormats = []string{