|
|
|
|
package pain
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
PAIN_XMLNS = "urn:iso:std:iso:20022:tech:xsd:pain.008.002.02"
|
|
|
|
|
PAIN_XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Document struct {
|
|
|
|
|
XmlnsXsi string `xml:"xmlns:xsi,attr"`
|
|
|
|
|
Namespace string `xml:"xmlns,attr"`
|
|
|
|
|
Contents *PainXML `xml:"CstmrDrctDbtInitn"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Document) Valid() error {
|
|
|
|
|
var err []string
|
|
|
|
|
if d.XmlnsXsi != PAIN_XMLNS_XSI {
|
|
|
|
|
err = append(err, "xmlns:xsi does not match PAIN_XMLNS_XSI")
|
|
|
|
|
}
|
|
|
|
|
if d.Namespace != PAIN_XMLNS {
|
|
|
|
|
err = append(err, "xmlns does not match PAIN_XMLNS")
|
|
|
|
|
}
|
|
|
|
|
if e := d.Contents.Valid(); e != nil {
|
|
|
|
|
err = append(err, fmt.Sprintf("%v", e))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(err) > 0 {
|
|
|
|
|
return fmt.Errorf("document not valid: %v", strings.Join(err, ", "))
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type PainXML struct {
|
|
|
|
|
GroupHeader *GrpHdr `xml:"GrpHdr"`
|
|
|
|
|
PaymentInformation []PmtInf `xml:"PmtInf"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) == 0 {
|
|
|
|
|
err = append(err, "no payment information")
|
|
|
|
|
}
|
|
|
|
|
for i, pi := range p.PaymentInformation {
|
|
|
|
|
if e := pi.Valid(); e != nil {
|
|
|
|
|
err = append(err, fmt.Sprintf("payment information at %d has errors: %v", i, e))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(err) > 0 {
|
|
|
|
|
return fmt.Errorf("pain XML not valid: %v", strings.Join(err, ", "))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CdtrAcct struct {
|
|
|
|
|
Id IBAN `xml:"Id"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *CdtrAcct) Valid() error {
|
|
|
|
|
if e := c.Id.Valid(); e != nil {
|
|
|
|
|
return fmt.Errorf("creditor account id not valid: %v", e)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type IBAN struct {
|
|
|
|
|
IBAN string `xml:"IBAN"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 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 {
|
|
|
|
|
// Embed PartySEPA2, because we do not need another level of <Id>
|
|
|
|
|
PartySEPA2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewPartyIdSEPA3(id string) PartyIdSEPA3 {
|
|
|
|
|
return PartyIdSEPA3{
|
|
|
|
|
PartySEPA2{
|
|
|
|
|
PrivateId: PersonIdSEPA2{
|
|
|
|
|
Other: RestrictedPersonIdSEPA{
|
|
|
|
|
Id: id,
|
|
|
|
|
SchemeName: RestrictedPersonIdSchemeNameSEPA{
|
|
|
|
|
Party: "SEPA",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *PartyIdSEPA3) Valid() error {
|
|
|
|
|
return p.PartySEPA2.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:"Prtry"` // IdentificationSchemeNameSEPA
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *RestrictedPersonIdSchemeNameSEPA) Valid() error {
|
|
|
|
|
if r.Party != "SEPA" {
|
|
|
|
|
return fmt.Errorf("party should be 'SEPA', got %v", r.Party)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|