From 2e662f09af981cd3c1e0d257fb40d280c485b46a Mon Sep 17 00:00:00 2001 From: Gerdriaan Mulder Date: Sun, 5 Jan 2020 21:44:17 +0100 Subject: [PATCH] Restructure PAIN package into group_header, payment_information (WIP) --- pain/group_header.go | 38 ++++++++++ pain/pain.go | 146 ------------------------------------ pain/pain_regexp.go | 20 +++++ pain/payment_information.go | 130 ++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 146 deletions(-) create mode 100644 pain/group_header.go create mode 100644 pain/payment_information.go diff --git a/pain/group_header.go b/pain/group_header.go new file mode 100644 index 0000000..f0d134e --- /dev/null +++ b/pain/group_header.go @@ -0,0 +1,38 @@ +package pain + +import ( + "fmt" +) + +type GrpHdr struct { + MessageId string `xml:"MsgId"` + Timestamp string `xml:"CreDtTm"` + NumTx string `xml:"NbOfTxs"` + Sum string `xml:"CtrlSum"` + InitiatingParty PartyIdSEPA1 `xml:"InitgPty"` +} + +func (g *GrpHdr) Valid() error { + var err []string + if !SEPARegexps["MsgId"].MatchString(g.MessageId) { + err = append(err, "msgId did not match expected format") + } + if !SEPARegexps["CreDtTm"].MatchString(g.Timestamp) { + err = append(err, "timestamp did not match expected format") + } + if !SEPARegexps["NbOfTxs"].MatchString(g.NumTx) { + err = append(err, "numtx did not match expected format") + } + if !SEPARegexps["CtrlSum"].MatchString(g.Sum) { + err = append(err, "sum 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, ", ")) + } + return nil +} diff --git a/pain/pain.go b/pain/pain.go index 53bbee5..e1d4c35 100644 --- a/pain/pain.go +++ b/pain/pain.go @@ -6,24 +6,6 @@ import ( "strings" ) -var ( - SEPARegexps = map[string]*regexp.Regexp{ - "CreDtTm": ISODateTime, - "CtrlSum": RestrictedDecimalNumber, - "IBAN": IBAN2007Identifier, - "Id": RestrictedPersonIdentifierSEPA, - "MsgId": RestrictedIdentificationSEPA1, - "NbOfTxs": Max15NumericText, - "Nm": Max70Text, - "PmtInfId": RestrictedIdentificationSEPA1, - "PmtMetaLclInstrm": LocalInstrumentSEPACode, - "PmtMetaSvcLvl": ServiceLevelSEPACode, - "PmtMtd": PaymentMethod2Code, - "ReqdColltnDt": ISODateTime, - "SeqTp": SequenceType1Code, - } -) - type PainXML struct { GroupHeader GrpHdr `xml:"GrpHdr"` PaymentInformation []PmtInf `xml:"PmtInf"` @@ -49,106 +31,6 @@ func (p *PainXML) Valid() error { return nil } -type GrpHdr struct { - MessageId string `xml:"MsgId"` - Timestamp string `xml:"CreDtTm"` - NumTx string `xml:"NbOfTxs"` - Sum string `xml:"CtrlSum"` - InitiatingParty PartyIdSEPA1 `xml:"InitgPty"` -} - -func (g *GrpHdr) Valid() error { - var err []string - if !SEPARegexps["MsgId"].MatchString(g.MessageId) { - err = append(err, "msgId did not match expected format") - } - if !SEPARegexps["CreDtTm"].MatchString(g.Timestamp) { - err = append(err, "timestamp did not match expected format") - } - if !SEPARegexps["NbOfTxs"].MatchString(g.NumTx) { - err = append(err, "numtx did not match expected format") - } - if !SEPARegexps["CtrlSum"].MatchString(g.Sum) { - err = append(err, "sum 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, ", ")) - } - return nil -} - -type PmtInf struct { - Id string `xml:"PmtInfId"` - Method string `xml:"PmtMtd"` - PaymentMeta PmtTpInf `xml:"PmtTpInf"` - CollectionDate string `xml:"ReqdColltnDt"` - Creditor Cdtr `xml:"Cdtr"` - CreditorAccount CdtrAcct `xml:"CdtrAcct"` - CreditorAgent CdtrAgt `xml:"CdtrAgt"` - SchemeId CdtrSchmeId `xml:"CdtrSchmeId"` -} - -func (pmt *PmtInf) Valid() error { - var err []string - if !SEPARegexps["PmtInfId"].MatchString(pmt.Id) { - err = append(err, "payment info id does not match expected format") - } - if !SEPARegexps["PmtMtd"].MatchString(pmt.Method) { - 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 e := pmt.SchemeId.Valid(); e != nil { - err = append(err, fmt.Sprintf("payment scheme id not valid: %v", e)) - } - - if len(err) > 0 { - return fmt.Errorf("payment info (Id: %q) not valid: %v", pmt.Id, strings.Join(err, ", ")) - } - return nil -} - -type PmtTpInf struct { - ServiceLevel Code `xml:"SvcLvl"` - LocalInstrument Code `xml:"LclIntrm"` - 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 CdtrAgt struct { InstitutionId FinInstnId `xml:"FinInstId"` } @@ -165,34 +47,6 @@ func (c *CdtrSchmeId) Valid() error { return c.Id.Valid() } -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"` } diff --git a/pain/pain_regexp.go b/pain/pain_regexp.go index bb01bd7..8debdd6 100644 --- a/pain/pain_regexp.go +++ b/pain/pain_regexp.go @@ -19,4 +19,24 @@ var ( IBAN2007Identifier = regexp.MustCompile(`[A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}`) RestrictedPersonIdentifierSEPA = regexp.MustCompile(`[a-zA-Z]{2,2}[0-9]{2,2}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){3,3}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,28}`) + BICIdentifier = regexp.MustCompile(`[A-Z]{6,6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3,3}){0,1}`) +) + +var ( + SEPARegexps = map[string]*regexp.Regexp{ + "BIC": BICIdentifier, + "CreDtTm": ISODateTime, + "CtrlSum": RestrictedDecimalNumber, + "IBAN": IBAN2007Identifier, + "Id": RestrictedPersonIdentifierSEPA, + "MsgId": RestrictedIdentificationSEPA1, + "NbOfTxs": Max15NumericText, + "Nm": Max70Text, + "PmtInfId": RestrictedIdentificationSEPA1, + "PmtMetaLclInstrm": LocalInstrumentSEPACode, + "PmtMetaSvcLvl": ServiceLevelSEPACode, + "PmtMtd": PaymentMethod2Code, + "ReqdColltnDt": ISODateTime, + "SeqTp": SequenceType1Code, + } ) diff --git a/pain/payment_information.go b/pain/payment_information.go new file mode 100644 index 0000000..6e24e9a --- /dev/null +++ b/pain/payment_information.go @@ -0,0 +1,130 @@ +package pain + +import ( + "fmt" +) + +type PmtInf struct { + Id string `xml:"PmtInfId"` + Method string `xml:"PmtMtd"` + PaymentMeta PmtTpInf `xml:"PmtTpInf"` + CollectionDate string `xml:"ReqdColltnDt"` + Creditor Cdtr `xml:"Cdtr"` + CreditorAccount CdtrAcct `xml:"CdtrAcct"` + CreditorAgent CdtrAgt `xml:"CdtrAgt"` + SchemeId CdtrSchmeId `xml:"CdtrSchmeId"` + Transactions []DrctDbtTxInf `xml:"DrctDbtTxInf"` +} + +func (pmt *PmtInf) Valid() error { + var err []string + if !SEPARegexps["PmtInfId"].MatchString(pmt.Id) { + err = append(err, "payment info id does not match expected format") + } + if !SEPARegexps["PmtMtd"].MatchString(pmt.Method) { + 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 e := pmt.CreditorAgent.Valid(); e != nil { + err = append(err, fmt.Sprintf("payment creditor agent not valid: %v", e)) + } + if e := pmt.SchemeId.Valid(); e != nil { + err = append(err, fmt.Sprintf("payment scheme id not valid: %v", e)) + } + + if len(err) > 0 { + return fmt.Errorf("payment info (Id: %q) not valid: %v", pmt.Id, strings.Join(err, ", ")) + } + return nil +} + +type PmtTpInf struct { + ServiceLevel Code `xml:"SvcLvl"` + LocalInstrument Code `xml:"LclIntrm"` + 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 AdrLine string +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 CdtrAgt struct { + InstitutionId FinInstnId `xml:"FinInstId"` +} + +func (c *CdtrAgt) Valid() error { + return c.InstituionId.Valid() +} + +type FinInstnId struct { + BIC string `xml:"BIC"` +} + +func (f *FinInstnId) Valid() error { + if !SEPARegexps["BIC"].MatchString(f.BIC) { + return fmt.Errorf("BIC not in expected format") + } + return nil +} + +type CdtrSchmeId struct { + Id PartyIdSEPA3 `xml:"Id"` +} + +func (c *CdtrSchmeId) Valid() error { + return c.Id.Valid() +}