From ac83bb06dd78f51dd36dd2e3d9aaed79a623f8a6 Mon Sep 17 00:00:00 2001 From: Gerdriaan Mulder Date: Sun, 5 Jan 2020 16:50:14 +0100 Subject: [PATCH] Added validation until CdtrAcct --- pain.go | 107 ++++++++++++++++++++++++++++++++++++++++++------- pain_regexp.go | 4 +- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/pain.go b/pain.go index 98ec0a2..aa6a34c 100644 --- a/pain.go +++ b/pain.go @@ -10,6 +10,7 @@ var ( SEPARegexps = map[string]*regexp.Regexp{ "MsgId": RestrictedIdentificationSEPA1, "CreDtTm": ISODateTime, + "ReqdColltnDt": ISODateTime, "NbOfTxs": Max15NumericText, "CtrlSum": RestrictedDecimalNumber, "Nm": Max70Text, @@ -18,6 +19,7 @@ var ( "PmtMetaSvcLvl": ServiceLevelSEPACode, "PmtMetaLclInstrm": LocalInstrumentSEPACode, "SeqTp": SequenceType1Code, + "IBAN": IBAN2007Identifier, } ) @@ -68,16 +70,10 @@ func (g *GrpHdr) Valid() error { if !SEPARegexps["CtrlSum"].MatchString(g.Sum) { err = append(err, "sum did not match expected format") } - // TODO - /* - if g.InitiatingParty == nil { - err = append(err, "no initiating party found") - } else { - if !SEPARegexps["Nm"].MatchString(g.InitiatingParty.Name) { - err = append(err, "initiating party's name did not match expected format") - } - } - */ + // Note: we assume g.InitiatingParty is initialized. + if !SEPARegexps["Nm"].MatchString(g.InitiatingParty.Name) { + err = append(err, "initiating party's name did not match expected format") + } if len(err) > 0 { return fmt.Errorf("group header not valid: %v", strings.Join(err, ", ")) @@ -86,9 +82,12 @@ func (g *GrpHdr) Valid() error { } type PmtInf struct { - Id string `xml:"PmtInfId"` - Method string `xml:"PmtMtd"` - PaymentMeta PmtTpInf `xml:"PmtTpInf"` + Id string `xml:"PmtInfId"` + Method string `xml:"PmtMtd"` + PaymentMeta PmtTpInf `xml:"PmtTpInf"` + CollectionDate string `xml:"ReqdColltnDt"` + Creditor Cdtr `xml:"Cdtr"` + CreditorAccount CdtrAcct `xml:"CdtrAcct"` } func (pmt *PmtInf) Valid() error { @@ -97,11 +96,20 @@ func (pmt *PmtInf) Valid() error { err = append(err, "payment info id does not match expected format") } if !SEPARegexps["PmtMtd"].MatchString(pmt.Method) { - err = append(err, "payment info id does not match expected format") + err = append(err, "payment method does not match expected format") } if e := pmt.PaymentMeta.Valid(); e != nil { err = append(err, fmt.Sprintf("payment meta not valid: %v", e)) } + if !SEPARegexps["ReqdColltnDt"].MatchString(pmt.CollectionDate) { + err = append(err, "payment collection date does not match expected format") + } + if e := pmt.Creditor.Valid(); e != nil { + err = append(err, fmt.Sprintf("payment creditor not valid: %v", e)) + } + if e := pmt.CreditorAccount.Valid(); e != nil { + err = append(err, fmt.Sprintf("payment creditor account not valid: %v", e)) + } if len(err) > 0 { return fmt.Errorf("payment info (Id: %q) not valid: %v", pmt.Id, strings.Join(err, ", ")) @@ -115,6 +123,76 @@ type PmtTpInf struct { SequenceType string `xml:"SeqTp"` } +type Cdtr struct { + Name string `xml:"Nm"` + PostalAddress PstlAddr `xml:"PstlAddr"` +} + +func (c *Cdtr) Valid() error { + var err []string + if !SEPARegexps["Nm"].MatchString(c.Name) { + err = append(err, "creditor name does not match format") + } + if e := c.PostalAddress.Valid(); e != nil { + err = append(err, fmt.Sprintf("creditor postal address not valid: %v", e)) + } + + if len(err) > 0 { + return fmt.Errorf("creditor (Nm: %q) not valid: %v", c.Name, strings.Join(err, ", ")) + } + return nil +} + +type PstlAddr struct { + Country string `xml:"Ctry"` + AddressLines []AdrLine `xml:"AdrLine"` +} + +func (p *PstlAddr) Valid() error { + var err []string + if !SEPARegexps["Ctry"].MatchString(p.Country) { + err = append(err, "country does not match format") + } + if len(p.AddressLines) > 2 { + err = append(err, "expected at most 2 address lines") + } + for i, line := range p.AddressLines { + if !SEPARegexps["AdrLine"].MatchString(string(line)) { + err = append(err, fmt.Sprintf("address line %d (%q) does not match format", i, line)) + } + } + + if len(err) > 0 { + return fmt.Errorf("address not valid: %v", strings.Join(err, ", ")) + } + + return nil +} + +type AdrLine string + +type CdtrAcct struct { + Id IBAN `xml:"Id"` +} + +type IBAN struct { + IBAN string `xml:"IBAN"` +} + +func (c *CdtrAcct) Valid() error { + if e := c.Id.Valid(); e != nil { + return fmt.Errorf("creditor account id not valid: %v", e) + } + return nil +} + +func (i *IBAN) Valid() error { + if !SEPARegexps["IBAN"].MatchString(i.IBAN) { + return fmt.Errorf("IBAN does not match format") + } + return nil +} + type Code struct { Code string `xml:"Cd"` } @@ -144,4 +222,5 @@ type TxInf struct { type InitgPty struct { Name string `xml:"Nm"` + // We do not implement the "Id" element from "PartyIdentificationSEPA1" } diff --git a/pain_regexp.go b/pain_regexp.go index d6ae97e..d19d4d4 100644 --- a/pain_regexp.go +++ b/pain_regexp.go @@ -9,11 +9,13 @@ var ( RestrictedIdentificationSEPA1 = regexp.MustCompile(`([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|'| ]){1,35}`) RestrictedIdentificationSEPA2 = regexp.MustCompile(`([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,35}`) Max15NumericText = regexp.MustCompile(`[0-9]{1,15}`) - RestrictedDecimalNumber = regexp.MustCompile(`[+-]\d+\.\d\d`) + RestrictedDecimalNumber = regexp.MustCompile(`[+-]?\d+\.\d\d`) Max70Text = regexp.MustCompile(`.{1,70}`) ServiceLevelSEPACode = regexp.MustCompile(`SEPA`) SequenceType1Code = regexp.MustCompile(`FRST|RCUR|FNAL|OOFF`) PaymentMethod2Code = regexp.MustCompile(`DD`) LocalInstrumentSEPACode = regexp.MustCompile(`CORE|B2B`) + + IBAN2007Identifier = regexp.MustCompile(`[A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}`) )