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.
303 lines
7.7 KiB
303 lines
7.7 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,
|
|
"Id": RestrictedPersonIdentifierSEPA,
|
|
"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 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"`
|
|
}
|
|
|
|
type FinInstnId struct {
|
|
BIC string `xml:"BIC"`
|
|
}
|
|
|
|
type CdtrSchmeId struct {
|
|
Id PartyIdSEPA3 `xml:"Id"`
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
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 PartySEPA2 struct {
|
|
PrivateId PersonIdSEPA2 `xml:"PrvtId"`
|
|
}
|
|
|
|
func (p *PartySEPA2) Valid() error {
|
|
return p.PrivateId.Valid()
|
|
}
|
|
|
|
type PartyIdSEPA1 struct {
|
|
Name string `xml:"Nm"`
|
|
// We do not implement the "Id" element from "PartyIdentificationSEPA1"
|
|
}
|
|
|
|
type PartyIdSEPA3 struct {
|
|
Id PartySEPA2 `xml:"Id"`
|
|
}
|
|
|
|
func (p *PartyIdSEPA3) Valid() error {
|
|
return p.Id.Valid()
|
|
}
|
|
|
|
type PersonIdSEPA2 struct {
|
|
Other RestrictedPersonIdSEPA `xml:"Othr"`
|
|
}
|
|
|
|
func (p *PersonIdSEPA2) Valid() error {
|
|
return p.Other.Valid()
|
|
}
|
|
|
|
type RestrictedPersonIdSEPA struct {
|
|
Id string `xml:"Id"` // RestrictedPersonIdentifierSEPA
|
|
SchemeName RestrictedPersonIdSchemeNameSEPA `xml:"SchmeNm"`
|
|
}
|
|
|
|
func (r *RestrictedPersonIdSEPA) Valid() error {
|
|
var err []string
|
|
if !SEPARegexps["Id"].MatchString(r.Id) {
|
|
err = append(err, "id of RestrictedPersonIdSEPA does not match format")
|
|
}
|
|
if e := r.SchemeName.Valid(); e != nil {
|
|
err = append(err, fmt.Sprintf("SchemeName not valid: %v", e))
|
|
}
|
|
|
|
if len(err) > 0 {
|
|
return fmt.Errorf("restricted person id SEPA not valid: %v", strings.Join(err, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RestrictedPersonIdSchemeNameSEPA struct {
|
|
Party string `xml:"Prty"` // IdentificationSchemeNameSEPA
|
|
}
|
|
|
|
func (r *RestrictedPersonIdSchemeNameSEPA) Valid() error {
|
|
if r.Party != "SEPA" {
|
|
return fmt.Errorf("party should be 'SEPA', got %v", r.Party)
|
|
}
|
|
return nil
|
|
}
|
|
|