Skip to content

Commit a1c016b

Browse files
aodhan-domhnaillAidan Macdonald
andauthored
Adding support for CAMT XML data imports (#68)
Co-authored-by: Aidan Macdonald <aidan@apmac.us>
1 parent 981339a commit a1c016b

File tree

5 files changed

+566
-133
lines changed

5 files changed

+566
-133
lines changed

ledger/camt/camt.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package camt
2+
3+
import (
4+
"encoding/xml"
5+
"io"
6+
)
7+
8+
// XML structures for CAMT.053 format
9+
type Document struct {
10+
XMLName xml.Name `xml:"Document"`
11+
BkToCstmrStmt BkToCstmrStmt `xml:"BkToCstmrStmt"`
12+
}
13+
14+
type BkToCstmrStmt struct {
15+
Stmt Stmt `xml:"Stmt"`
16+
}
17+
18+
type Stmt struct {
19+
Acct Acct `xml:"Acct"`
20+
Ntry []Ntry `xml:"Ntry"`
21+
}
22+
23+
type Acct struct {
24+
Id Id `xml:"Id"`
25+
Ccy string `xml:"Ccy"`
26+
Ownr Ownr `xml:"Ownr"`
27+
}
28+
29+
type Id struct {
30+
IBAN string `xml:"IBAN"`
31+
}
32+
33+
type Ownr struct {
34+
Nm string `xml:"Nm"`
35+
}
36+
37+
type Ntry struct {
38+
Amt Amount `xml:"Amt"`
39+
CdtDbtInd string `xml:"CdtDbtInd"`
40+
BookgDt BookgDt `xml:"BookgDt"`
41+
BkTxCd BkTxCd `xml:"BkTxCd"`
42+
NtryRef string `xml:"NtryRef"`
43+
AddtlNtryInf string `xml:"AddtlNtryInf"`
44+
NtryDtls *NtryDtls `xml:"NtryDtls"`
45+
}
46+
47+
type Amount struct {
48+
Value string `xml:",chardata"`
49+
Ccy string `xml:"Ccy,attr"`
50+
}
51+
52+
type BookgDt struct {
53+
DtTm string `xml:"DtTm"`
54+
}
55+
56+
type BkTxCd struct {
57+
Prtry Prtry `xml:"Prtry"`
58+
}
59+
60+
type Prtry struct {
61+
Cd string `xml:"Cd"`
62+
}
63+
64+
type NtryDtls struct {
65+
TxDtls TxDtls `xml:"TxDtls"`
66+
}
67+
68+
type TxDtls struct {
69+
RltdPties RltdPties `xml:"RltdPties"`
70+
}
71+
72+
type RltdPties struct {
73+
Cdtr *Cdtr `xml:"Cdtr"`
74+
}
75+
76+
type Cdtr struct {
77+
Pty Pty `xml:"Pty"`
78+
}
79+
80+
type Pty struct {
81+
Nm string `xml:"Nm"`
82+
}
83+
84+
func ParseCamt(reader io.Reader) ([]Ntry, error) {
85+
var doc Document
86+
if err := xml.NewDecoder(reader).Decode(&doc); err != nil {
87+
return nil, err
88+
}
89+
90+
return doc.BkToCstmrStmt.Stmt.Ntry, nil
91+
}

ledger/camt/camt_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package camt_test
2+
3+
import (
4+
"bytes"
5+
_ "embed"
6+
"testing"
7+
8+
"github.com/howeyc/ledger/ledger/camt"
9+
)
10+
11+
//go:embed sample.xml
12+
var camtSample []byte
13+
14+
func TestParseCamt(t *testing.T) {
15+
entries, err := camt.ParseCamt(bytes.NewBuffer(camtSample))
16+
if err != nil {
17+
t.Error(err)
18+
}
19+
if len(entries) != 2 {
20+
t.Error("Expected 2 got ", len(entries))
21+
}
22+
}

ledger/camt/sample.xml

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.10">
3+
<BkToCstmrStmt>
4+
<GrpHdr>
5+
<MsgId>1111111-000000</MsgId>
6+
<CreDtTm>2025-07-31T12:37:01.152446900Z</CreDtTm>
7+
</GrpHdr>
8+
<Stmt>
9+
<Id>1111111-000000-99999999</Id>
10+
<CreDtTm>2025-07-31T12:37:01.152446900Z</CreDtTm>
11+
<FrToDt>
12+
<FrDtTm>2025-07-12T00:00:00+01:00</FrDtTm>
13+
<ToDtTm>2025-07-14T00:00:00+01:00</ToDtTm>
14+
</FrToDt>
15+
<Acct>
16+
<Id>
17+
<IBAN>BE00000000000</IBAN>
18+
</Id>
19+
<Ccy>EUR</Ccy>
20+
<Ownr>
21+
<Nm>Sample</Nm>
22+
<PstlAdr>
23+
<AdrTp>
24+
<Cd>ADDR</Cd>
25+
</AdrTp>
26+
<PstCd>EU-0000</PstCd>
27+
<TwnNm>Fake</TwnNm>
28+
<AdrLine>Happy lane</AdrLine>
29+
</PstlAdr>
30+
<Id>
31+
<OrgId>
32+
<Othr>
33+
<Id>0000000001234</Id>
34+
<SchmeNm>
35+
<Cd>COID</Cd>
36+
</SchmeNm>
37+
</Othr>
38+
</OrgId>
39+
</Id>
40+
</Ownr>
41+
<Svcr>
42+
<FinInstnId>
43+
<Nm>Wise Europe SA</Nm>
44+
<PstlAdr>
45+
<AdrTp>
46+
<Cd>ADDR</Cd>
47+
</AdrTp>
48+
<PstCd>1050</PstCd>
49+
<TwnNm>Brussels</TwnNm>
50+
<AdrLine>Rue du Trône 100, 3rd floor</AdrLine>
51+
</PstlAdr>
52+
</FinInstnId>
53+
</Svcr>
54+
</Acct>
55+
<Bal>
56+
<Tp>
57+
<CdOrPrtry>
58+
<Cd>CLBD</Cd>
59+
</CdOrPrtry>
60+
</Tp>
61+
<Amt Ccy="EUR">67.71</Amt>
62+
<CdtDbtInd>CRDT</CdtDbtInd>
63+
<Dt>
64+
<DtTm>2025-07-14T00:00:00+01:00</DtTm>
65+
</Dt>
66+
</Bal>
67+
<Bal>
68+
<Tp>
69+
<CdOrPrtry>
70+
<Cd>OPBD</Cd>
71+
</CdOrPrtry>
72+
</Tp>
73+
<Amt Ccy="EUR">306.61</Amt>
74+
<CdtDbtInd>CRDT</CdtDbtInd>
75+
<Dt>
76+
<DtTm>2025-07-12T00:00:00+01:00</DtTm>
77+
</Dt>
78+
</Bal>
79+
<Bal>
80+
<Tp>
81+
<CdOrPrtry>
82+
<Prtry>Unrealised gains and losses</Prtry>
83+
</CdOrPrtry>
84+
</Tp>
85+
<Amt Ccy="EUR">2.26</Amt>
86+
<CdtDbtInd>CRDT</CdtDbtInd>
87+
<Dt>
88+
<DtTm>2025-07-14T00:00:00+01:00</DtTm>
89+
</Dt>
90+
</Bal>
91+
<TxsSummry>
92+
<TtlNtries>
93+
<NbOfNtries>2</NbOfNtries>
94+
<Sum>-38.90</Sum>
95+
<TtlNetNtry>
96+
<Amt>38.90</Amt>
97+
<CdtDbtInd>DBIT</CdtDbtInd>
98+
</TtlNetNtry>
99+
</TtlNtries>
100+
<TtlCdtNtries>
101+
<NbOfNtries>0</NbOfNtries>
102+
<Sum>0</Sum>
103+
</TtlCdtNtries>
104+
<TtlDbtNtries>
105+
<NbOfNtries>2</NbOfNtries>
106+
<Sum>-38.90</Sum>
107+
</TtlDbtNtries>
108+
</TxsSummry>
109+
<Ntry>
110+
<Amt Ccy="EUR">3.90</Amt>
111+
<CdtDbtInd>DBIT</CdtDbtInd>
112+
<Sts>
113+
<Cd>BOOK</Cd>
114+
</Sts>
115+
<BookgDt>
116+
<DtTm>2025-07-13T05:32:45.916737+01:00</DtTm>
117+
</BookgDt>
118+
<BkTxCd>
119+
<Prtry>
120+
<Cd>CARD-675</Cd>
121+
</Prtry>
122+
</BkTxCd>
123+
<AddtlNtryInf>Card transaction of EUR issued</AddtlNtryInf>
124+
</Ntry>
125+
<Ntry>
126+
<NtryRef>00001/2025</NtryRef>
127+
<Amt Ccy="EUR">35.00</Amt>
128+
<CdtDbtInd>DBIT</CdtDbtInd>
129+
<Sts>
130+
<Cd>BOOK</Cd>
131+
</Sts>
132+
<BookgDt>
133+
<DtTm>2025-07-12T08:58:01.327701+01:00</DtTm>
134+
</BookgDt>
135+
<BkTxCd>
136+
<Prtry>
137+
<Cd>TRANSFER-0000</Cd>
138+
</Prtry>
139+
</BkTxCd>
140+
<NtryDtls>
141+
<TxDtls>
142+
<RltdPties>
143+
<Cdtr>
144+
<Pty>
145+
<Nm>LLC Company</Nm>
146+
</Pty>
147+
</Cdtr>
148+
</RltdPties>
149+
</TxDtls>
150+
</NtryDtls>
151+
<AddtlNtryInf>Sent money to LLC Company</AddtlNtryInf>
152+
</Ntry>
153+
</Stmt>
154+
</BkToCstmrStmt>
155+
</Document>

0 commit comments

Comments
 (0)