diff --git a/banking.go b/banking.go index 48f12e9..6260ea4 100644 --- a/banking.go +++ b/banking.go @@ -6,14 +6,14 @@ import ( ) 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 + 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 + IncTranImg Boolean `xml:"STMTRQ>INCTRANIMG,omitempty"` // Include transaction images } func (r *StatementRequest) Name() string { @@ -27,28 +27,6 @@ func (r *StatementRequest) Valid() (bool, error) { return true, nil } -type CCStatementRequest struct { - XMLName xml.Name `xml:"CCSTMTTRNRQ"` - TrnUID UID `xml:"TRNUID"` - CCAcctFrom CCAcct `xml:"CCSTMTRQ>CCACCTFROM"` - DtStart Date `xml:"CCSTMTRQ>INCTRAN>DTSTART,omitempty"` - DtEnd Date `xml:"CCSTMTRQ>INCTRAN>DTEND,omitempty"` - Include Boolean `xml:"CCSTMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance) - IncludePending Boolean `xml:"CCSTMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions - IncludeTranImage Boolean `xml:"CCSTMTRQ>INCLUDETRANIMAGE,omitempty"` // Include transaction images -} - -func (r *CCStatementRequest) Name() string { - return "CCSTMTTRNRQ" -} - -func (r *CCStatementRequest) 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"` @@ -173,37 +151,6 @@ func (sr StatementResponse) Valid() (bool, error) { return true, nil } -type CCStatementResponse struct { - XMLName xml.Name `xml:"CCSTMTTRNRS"` - TrnUID UID `xml:"TRNUID"` - CurDef String `xml:"CCSTMTRS>CURDEF"` - CCAcctFrom CCAcct `xml:"CCSTMTRS>CCACCTFROM"` - BankTranList TransactionList `xml:"CCSTMTRS>BANKTRANLIST,omitempty"` - //BANKTRANLISTP - BalAmt Amount `xml:"CCSTMTRS>LEDGERBAL>BALAMT"` - DtAsOf Date `xml:"CCSTMTRS>LEDGERBAL>DTASOF"` - AvailBalAmt Amount `xml:"CCSTMTRS>AVAILBAL>BALAMT,omitempty"` - AvailDtAsOf Date `xml:"CCSTMTRS>AVAILBAL>DTASOF,omitempty"` - CashAdvBalAmt Amount `xml:"CCSTMTRS>CASHADVBALAMT,omitempty"` // Only for CREDITLINE accounts, available balance for cash advances - IntRatePurch Amount `xml:"CCSTMTRS>INTRATEPURCH,omitempty"` // Current interest rate for purchases - IntRateCash Amount `xml:"CCSTMTRS>INTRATECASH,omitempty"` // Current interest rate for cash advances - IntRateXfer Amount `xml:"CCSTMTRS>INTRATEXFER,omitempty"` // Current interest rate for cash advances - RewardName String `xml:"CCSTMTRS>REWARDINFO>NAME,omitempty"` // Name of the reward program referred to by the next two elements - RewardBal Amount `xml:"CCSTMTRS>REWARDINFO>REWARDBAL,omitempty"` // Current balance of the reward program - RewardEarned Amount `xml:"CCSTMTRS>REWARDINFO>REWARDEARNED,omitempty"` // Reward amount earned YTD - BalList []Balance `xml:"CCSTMTRS>BALLIST>BAL,omitempty"` - MktgInfo String `xml:"CCSTMTRS>MKTGINFO,omitempty"` // Marketing information -} - -func (sr CCStatementResponse) Name() string { - return "CCSTMTTRNRS" -} - -func (sr CCStatementResponse) Valid() (bool, error) { - //TODO implement - return true, nil -} - func DecodeBankingMessageSet(d *xml.Decoder, start xml.StartElement) ([]Message, error) { var msgs []Message for { @@ -221,12 +168,6 @@ func DecodeBankingMessageSet(d *xml.Decoder, start xml.StartElement) ([]Message, return nil, err } msgs = append(msgs, Message(info)) - case "CCSTMTTRNRS": - var info CCStatementResponse - 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) } diff --git a/creditcards.go b/creditcards.go new file mode 100644 index 0000000..0e35e65 --- /dev/null +++ b/creditcards.go @@ -0,0 +1,85 @@ +package ofxgo + +import ( + "errors" + "github.com/golang/go/src/encoding/xml" +) + +type CCStatementRequest struct { + XMLName xml.Name `xml:"CCSTMTTRNRQ"` + TrnUID UID `xml:"TRNUID"` + CCAcctFrom CCAcct `xml:"CCSTMTRQ>CCACCTFROM"` + DtStart Date `xml:"CCSTMTRQ>INCTRAN>DTSTART,omitempty"` + DtEnd Date `xml:"CCSTMTRQ>INCTRAN>DTEND,omitempty"` + Include Boolean `xml:"CCSTMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance) + IncludePending Boolean `xml:"CCSTMTRQ>INCLUDEPENDING,omitempty"` // Include pending transactions + IncTranImg Boolean `xml:"CCSTMTRQ>INCTRANIMG,omitempty"` // Include transaction images +} + +func (r *CCStatementRequest) Name() string { + return "CCSTMTTRNRQ" +} + +func (r *CCStatementRequest) Valid() (bool, error) { + if ok, err := r.TrnUID.Valid(); !ok { + return false, err + } + return true, nil +} + +type CCStatementResponse struct { + XMLName xml.Name `xml:"CCSTMTTRNRS"` + TrnUID UID `xml:"TRNUID"` + CurDef String `xml:"CCSTMTRS>CURDEF"` + CCAcctFrom CCAcct `xml:"CCSTMTRS>CCACCTFROM"` + BankTranList TransactionList `xml:"CCSTMTRS>BANKTRANLIST,omitempty"` + //BANKTRANLISTP + BalAmt Amount `xml:"CCSTMTRS>LEDGERBAL>BALAMT"` + DtAsOf Date `xml:"CCSTMTRS>LEDGERBAL>DTASOF"` + AvailBalAmt Amount `xml:"CCSTMTRS>AVAILBAL>BALAMT,omitempty"` + AvailDtAsOf Date `xml:"CCSTMTRS>AVAILBAL>DTASOF,omitempty"` + CashAdvBalAmt Amount `xml:"CCSTMTRS>CASHADVBALAMT,omitempty"` // Only for CREDITLINE accounts, available balance for cash advances + IntRatePurch Amount `xml:"CCSTMTRS>INTRATEPURCH,omitempty"` // Current interest rate for purchases + IntRateCash Amount `xml:"CCSTMTRS>INTRATECASH,omitempty"` // Current interest rate for cash advances + IntRateXfer Amount `xml:"CCSTMTRS>INTRATEXFER,omitempty"` // Current interest rate for cash advances + RewardName String `xml:"CCSTMTRS>REWARDINFO>NAME,omitempty"` // Name of the reward program referred to by the next two elements + RewardBal Amount `xml:"CCSTMTRS>REWARDINFO>REWARDBAL,omitempty"` // Current balance of the reward program + RewardEarned Amount `xml:"CCSTMTRS>REWARDINFO>REWARDEARNED,omitempty"` // Reward amount earned YTD + BalList []Balance `xml:"CCSTMTRS>BALLIST>BAL,omitempty"` + MktgInfo String `xml:"CCSTMTRS>MKTGINFO,omitempty"` // Marketing information +} + +func (sr CCStatementResponse) Name() string { + return "CCSTMTTRNRS" +} + +func (sr CCStatementResponse) Valid() (bool, error) { + //TODO implement + return true, nil +} + +func DecodeCCMessageSet(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 "CCSTMTTRNRS": + var info CCStatementResponse + 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") + } + } +} diff --git a/request.go b/request.go index c1c402d..00b2fb2 100644 --- a/request.go +++ b/request.go @@ -7,12 +7,12 @@ import ( ) type Request struct { - URL string - Version string // OFX version string, overwritten in Client.Request() - Signon SignonRequest // - Signup []Message // - Banking []Message // - // + URL string + Version string // OFX version string, overwritten in Client.Request() + Signon SignonRequest // + Signup []Message // + Banking []Message // + CreditCards []Message // // Investments []Message // // @@ -106,6 +106,9 @@ NEWFILEUID:NONE if err := marshalMessageSet(encoder, oq.Banking, "BANKMSGSRQV1"); err != nil { return nil, err } + if err := marshalMessageSet(encoder, oq.CreditCards, "CREDITCARDMSGSRQV1"); err != nil { + return nil, err + } if err := marshalMessageSet(encoder, oq.Investments, "INVSTMTMSGSRQV1"); err != nil { return nil, err } diff --git a/response.go b/response.go index 9268f6b..3b36b47 100644 --- a/response.go +++ b/response.go @@ -10,11 +10,11 @@ import ( ) type Response struct { - Version string // String for OFX header, defaults to 203 - Signon SignonResponse // - Signup []Message // - Banking []Message // - // + Version string // String for OFX header, defaults to 203 + Signon SignonResponse // + Signup []Message // + Banking []Message // + CreditCards []Message // // Investments []Message // // @@ -269,7 +269,12 @@ func ParseResponse(reader io.Reader) (*Response, error) { return nil, err } or.Banking = msgs - //case "CREDITCARDMSGSRSV1": + case "CREDITCARDMSGSRSV1": + msgs, err := DecodeCCMessageSet(decoder, start) + if err != nil { + return nil, err + } + or.CreditCards = msgs //case "LOANMSGSRSV1": case "INVSTMTMSGSRSV1": msgs, err := DecodeInvestmentsMessageSet(decoder, start)