diff --git a/pain.go b/pain.go new file mode 100644 index 0000000..98ec0a2 --- /dev/null +++ b/pain.go @@ -0,0 +1,147 @@ +package sepa + +import ( + "fmt" + "regexp" + "strings" +) + +var ( + SEPARegexps = map[string]*regexp.Regexp{ + "MsgId": RestrictedIdentificationSEPA1, + "CreDtTm": ISODateTime, + "NbOfTxs": Max15NumericText, + "CtrlSum": RestrictedDecimalNumber, + "Nm": Max70Text, + "PmtInfId": RestrictedIdentificationSEPA1, + "PmtMtd": PaymentMethod2Code, + "PmtMetaSvcLvl": ServiceLevelSEPACode, + "PmtMetaLclInstrm": LocalInstrumentSEPACode, + "SeqTp": SequenceType1Code, + } +) + +type PainXML struct { + GroupHeader GrpHdr `xml:"GrpHdr"` + PaymentInformation []PmtInf `xml:"PmtInf"` + TransactionInformation []TxInf `xml:"DrctDbtTxInf"` +} + +func (p *PainXML) Valid() error { + var err []string + if e := p.GroupHeader.Valid(); e != nil { + err = append(err, fmt.Sprintf("%v", e)) + } + if len(p.PaymentInformation) < 1 { + err = append(err, "no payment information") + } + if len(p.TransactionInformation) < 1 { + err = append(err, "no transaction information") + } + + if len(err) > 0 { + return fmt.Errorf("pain XML not valid: %v", strings.Join(err, ", ")) + } + + return nil +} + +type GrpHdr struct { + MessageId string `xml:"MsgId"` + Timestamp string `xml:"CreDtTm"` + NumTx string `xml:"NbOfTxs"` + Sum string `xml:"CtrlSum"` + InitiatingParty InitgPty `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") + } + // 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") + } + } + */ + + 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"` +} + +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 info id does not match expected format") + } + if e := pmt.PaymentMeta.Valid(); e != nil { + err = append(err, fmt.Sprintf("payment meta 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 Code struct { + Code string `xml:"Cd"` +} + +func (meta *PmtTpInf) Valid() error { + var err []string + // TODO: check meta.ServiceLevel for nil + if !SEPARegexps["PmtMetaSvcLvl"].MatchString(meta.ServiceLevel.Code) { + err = append(err, "incorrect service level present (must be 'SEPA')") + } + // TODO: check meta.LocalInstrument for nil + if !SEPARegexps["PmtMetaLclInstrm"].MatchString(meta.LocalInstrument.Code) { + err = append(err, "incorrect local instrument present") + } + if !SEPARegexps["SeqTp"].MatchString(meta.SequenceType) { + err = append(err, "sequence type has incorrect format") + } + + if len(err) > 0 { + return fmt.Errorf("payment meta not valid: %v", strings.Join(err, ", ")) + } + return nil +} + +type TxInf struct { +} + +type InitgPty struct { + Name string `xml:"Nm"` +} diff --git a/pain_regexp.go b/pain_regexp.go new file mode 100644 index 0000000..d6ae97e --- /dev/null +++ b/pain_regexp.go @@ -0,0 +1,19 @@ +package sepa + +import ( + "regexp" +) + +var ( + ISODateTime = regexp.MustCompile(`\d{4}(-\d\d){2}T\d\d(:\d\d){2}`) + 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`) + 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`) +)