You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

226 lines
5.9 KiB

package sepa
import (
"fmt"
"regexp"
"strings"
)
var (
SEPARegexps = map[string]*regexp.Regexp{
"MsgId": RestrictedIdentificationSEPA1,
"CreDtTm": ISODateTime,
"ReqdColltnDt": ISODateTime,
"NbOfTxs": Max15NumericText,
"CtrlSum": RestrictedDecimalNumber,
"Nm": Max70Text,
"PmtInfId": RestrictedIdentificationSEPA1,
"PmtMtd": PaymentMethod2Code,
"PmtMetaSvcLvl": ServiceLevelSEPACode,
"PmtMetaLclInstrm": LocalInstrumentSEPACode,
"SeqTp": SequenceType1Code,
"IBAN": IBAN2007Identifier,
}
)
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")
}
// 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"`
}
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 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 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"`
}
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"`
// We do not implement the "Id" element from "PartyIdentificationSEPA1"
}