From f6b03ab6eff6e28ae40df2717013cfb6c665421b Mon Sep 17 00:00:00 2001 From: Gerdriaan Mulder Date: Mon, 20 Jan 2020 21:58:22 +0100 Subject: [PATCH] pain: finalized-ish Document, createbatch: helper to create/export xml --- cmd/createbatch/.gitignore | 2 + cmd/createbatch/main.go | 27 +++++++++++ pain/api.go | 91 ++++++++++++++++++++++++++++++++++++++ pain/group_header.go | 8 ++++ pain/pain.go | 47 +++++++++++++++++--- 5 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 cmd/createbatch/.gitignore create mode 100644 cmd/createbatch/main.go create mode 100644 pain/api.go diff --git a/cmd/createbatch/.gitignore b/cmd/createbatch/.gitignore new file mode 100644 index 0000000..23d9223 --- /dev/null +++ b/cmd/createbatch/.gitignore @@ -0,0 +1,2 @@ +createbatch +config.go diff --git a/cmd/createbatch/main.go b/cmd/createbatch/main.go new file mode 100644 index 0000000..f54297f --- /dev/null +++ b/cmd/createbatch/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/xml" + "fmt" + "log" + "time" + + "src.wolkict.net/sepa/pain" +) + +func main() { + d := pain.NewDocument("PayPro B.V") + d.SetMeta(CREDITOR_NAME, CREDITOR_ADDR1, CREDITOR_ADDR2, CREDITOR_IBAN, CREDITOR_BIC, CREDITOR_ID, + time.Date(2020, time.January, 30, 12, 0, 0, 0, time.UTC), "RCUR") + err := d.Finalize("asdfasdf") + if err != nil { + log.Fatalf("finalizing failed: %v", err) + } + + xml, err := xml.Marshal(d) + if err != nil { + log.Fatalf("marshaling failed: %v", err) + } + + fmt.Printf("%v", string(xml)) +} diff --git a/pain/api.go b/pain/api.go new file mode 100644 index 0000000..9973a59 --- /dev/null +++ b/pain/api.go @@ -0,0 +1,91 @@ +package pain + +import ( + // "fmt" + "time" +) + +func NewDocument(initParty string) *Document { + return &Document{ + XmlnsXsi: PAIN_XMLNS_XSI, + Namespace: PAIN_XMLNS, + Contents: &PainXML{ + GroupHeader: NewGrpHdr(initParty), + PaymentInformation: make([]PmtInf, 0), + TransactionInformation: make([]TxInf, 0), + }, + } +} + +func (d *Document) SetMeta(creditorName, creditorAddr1, creditorAddr2, creditorIBAN, creditorBIC, + creditorId string, collectionDate time.Time, sequenceType string) { + creditor := Cdtr{ + Name: creditorName, + PostalAddress: PstlAdr{ + Country: "NL", // XXX: make customizable? + AddressLines: []AdrLine{ + AdrLine(creditorAddr1), + AdrLine(creditorAddr2), + }, + }, + } + creditorAccount := CdtrAcct{ + Id: IBAN{ + IBAN: creditorIBAN, + }, + } + creditorAgent := CdtrAgt{ + InstitutionId: FinInstnId{ + BIC: creditorBIC, + }, + } + creditorSchemeId := CdtrSchmeId{ + Id: NewPartyIdSEPA3(creditorId), + } + + paymentInfo := PmtInf{ + //Id: set in Finalize() + Method: "DD", + PaymentMeta: PmtTpInf{ + ServiceLevel: Code{ + Code: "SEPA", + }, + LocalInstrument: Code{ + Code: "CORE", + }, + SequenceType: sequenceType, + }, + CollectionDate: collectionDate.Format("2006-01-02"), + Creditor: creditor, + CreditorAccount: creditorAccount, + CreditorAgent: creditorAgent, + SchemeId: creditorSchemeId, + Transactions: make([]DrctDbtTxInf, 0), + } + d.Contents.PaymentInformation = append(d.Contents.PaymentInformation, paymentInfo) +} + +func (d *Document) Finalize(msgId string) error { + // GroupHeader: + // Calculate CtrlSum + // Set Timestamp + // Set number of transactions + // Set msgId + + /* + if len(d.Contents.PaymentInformation) == 0 { + return fmt.Errorf("no payment information, aborting") + } + if len(d.Contents.TransactionInformation) == 0 { + return fmt.Errorf("no transaction information, aborting") + } + */ + + //csum := 0.0 + + return d.Valid() +} + +func (d *Document) AddTransaction() { + +} diff --git a/pain/group_header.go b/pain/group_header.go index c1bc1fd..f6e21a1 100644 --- a/pain/group_header.go +++ b/pain/group_header.go @@ -13,6 +13,14 @@ type GrpHdr struct { InitiatingParty PartyIdSEPA1 `xml:"InitgPty"` } +func NewGrpHdr(initParty string) *GrpHdr { + return &GrpHdr{ + InitiatingParty: PartyIdSEPA1{ + Name: initParty, + }, + } +} + func (g *GrpHdr) Valid() error { var err []string if !SEPARegexps["MsgId"].MatchString(g.MessageId) { diff --git a/pain/pain.go b/pain/pain.go index 5021716..a66f694 100644 --- a/pain/pain.go +++ b/pain/pain.go @@ -5,16 +5,37 @@ import ( "strings" ) +const ( + PAIN_XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance" + PAIN_XMLNS = "urn:iso:std:iso:20022:tech:xsd:pain.008.001.02" +) + type Document struct { - xsi string `xml:"xmlns:xsi,attr"` - namespace string `xml:"xmlns,attr"` - Contents PainXML `xml:"CstmrDrctDbtInitn"` + XmlnsXsi string `xml:"xmlns:xsi,attr"` + Namespace string `xml:"xmlns,attr"` + Contents *PainXML `xml:"CstmrDrctDbtInitn"` } -// TODO fill in namespace and xsi +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(); err != 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"` + GroupHeader *GrpHdr `xml:"GrpHdr"` PaymentInformation []PmtInf `xml:"PmtInf"` TransactionInformation []TxInf `xml:"DrctDbtTxInf"` } @@ -85,6 +106,7 @@ func (meta *PmtTpInf) Valid() error { } type TxInf struct { + // TODO } type PartySEPA2 struct { @@ -104,6 +126,21 @@ type PartyIdSEPA3 struct { Id PartySEPA2 `xml:"Id"` } +func NewPartyIdSEPA3(id string) PartyIdSEPA3 { + return PartyIdSEPA3{ + Id: PartySEPA2{ + PrivateId: PersonIdSEPA2{ + Other: RestrictedPersonIdSEPA{ + Id: id, + SchemeName: RestrictedPersonIdSchemeNameSEPA{ + Party: "SEPA", + }, + }, + }, + }, + } +} + func (p *PartyIdSEPA3) Valid() error { return p.Id.Valid() }