Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions ledger/camt/camt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package camt

import (
"encoding/xml"
"io"
)

// XML structures for CAMT.053 format
type Document struct {
XMLName xml.Name `xml:"Document"`
BkToCstmrStmt BkToCstmrStmt `xml:"BkToCstmrStmt"`
}

type BkToCstmrStmt struct {
Stmt Stmt `xml:"Stmt"`
}

type Stmt struct {
Acct Acct `xml:"Acct"`
Ntry []Ntry `xml:"Ntry"`
}

type Acct struct {
Id Id `xml:"Id"`
Ccy string `xml:"Ccy"`
Ownr Ownr `xml:"Ownr"`
}

type Id struct {
IBAN string `xml:"IBAN"`
}

type Ownr struct {
Nm string `xml:"Nm"`
}

type Ntry struct {
Amt Amount `xml:"Amt"`
CdtDbtInd string `xml:"CdtDbtInd"`
BookgDt BookgDt `xml:"BookgDt"`
BkTxCd BkTxCd `xml:"BkTxCd"`
NtryRef string `xml:"NtryRef"`
AddtlNtryInf string `xml:"AddtlNtryInf"`
NtryDtls *NtryDtls `xml:"NtryDtls"`
}

type Amount struct {
Value string `xml:",chardata"`
Ccy string `xml:"Ccy,attr"`
}

type BookgDt struct {
DtTm string `xml:"DtTm"`
}

type BkTxCd struct {
Prtry Prtry `xml:"Prtry"`
}

type Prtry struct {
Cd string `xml:"Cd"`
}

type NtryDtls struct {
TxDtls TxDtls `xml:"TxDtls"`
}

type TxDtls struct {
RltdPties RltdPties `xml:"RltdPties"`
}

type RltdPties struct {
Cdtr *Cdtr `xml:"Cdtr"`
}

type Cdtr struct {
Pty Pty `xml:"Pty"`
}

type Pty struct {
Nm string `xml:"Nm"`
}

func ParseCamt(reader io.Reader) ([]Ntry, error) {
var doc Document
if err := xml.NewDecoder(reader).Decode(&doc); err != nil {
return nil, err
}

return doc.BkToCstmrStmt.Stmt.Ntry, nil
}
22 changes: 22 additions & 0 deletions ledger/camt/camt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package camt_test

import (
"bytes"
_ "embed"
"testing"

"github.com/howeyc/ledger/ledger/camt"
)

//go:embed sample.xml
var camtSample []byte

func TestParseCamt(t *testing.T) {
entries, err := camt.ParseCamt(bytes.NewBuffer(camtSample))
if err != nil {
t.Error(err)
}
if len(entries) != 2 {
t.Error("Expected 2 got ", len(entries))
}
}
155 changes: 155 additions & 0 deletions ledger/camt/sample.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.10">
<BkToCstmrStmt>
<GrpHdr>
<MsgId>1111111-000000</MsgId>
<CreDtTm>2025-07-31T12:37:01.152446900Z</CreDtTm>
</GrpHdr>
<Stmt>
<Id>1111111-000000-99999999</Id>
<CreDtTm>2025-07-31T12:37:01.152446900Z</CreDtTm>
<FrToDt>
<FrDtTm>2025-07-12T00:00:00+01:00</FrDtTm>
<ToDtTm>2025-07-14T00:00:00+01:00</ToDtTm>
</FrToDt>
<Acct>
<Id>
<IBAN>BE00000000000</IBAN>
</Id>
<Ccy>EUR</Ccy>
<Ownr>
<Nm>Sample</Nm>
<PstlAdr>
<AdrTp>
<Cd>ADDR</Cd>
</AdrTp>
<PstCd>EU-0000</PstCd>
<TwnNm>Fake</TwnNm>
<AdrLine>Happy lane</AdrLine>
</PstlAdr>
<Id>
<OrgId>
<Othr>
<Id>0000000001234</Id>
<SchmeNm>
<Cd>COID</Cd>
</SchmeNm>
</Othr>
</OrgId>
</Id>
</Ownr>
<Svcr>
<FinInstnId>
<Nm>Wise Europe SA</Nm>
<PstlAdr>
<AdrTp>
<Cd>ADDR</Cd>
</AdrTp>
<PstCd>1050</PstCd>
<TwnNm>Brussels</TwnNm>
<AdrLine>Rue du Trône 100, 3rd floor</AdrLine>
</PstlAdr>
</FinInstnId>
</Svcr>
</Acct>
<Bal>
<Tp>
<CdOrPrtry>
<Cd>CLBD</Cd>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">67.71</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<DtTm>2025-07-14T00:00:00+01:00</DtTm>
</Dt>
</Bal>
<Bal>
<Tp>
<CdOrPrtry>
<Cd>OPBD</Cd>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">306.61</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<DtTm>2025-07-12T00:00:00+01:00</DtTm>
</Dt>
</Bal>
<Bal>
<Tp>
<CdOrPrtry>
<Prtry>Unrealised gains and losses</Prtry>
</CdOrPrtry>
</Tp>
<Amt Ccy="EUR">2.26</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Dt>
<DtTm>2025-07-14T00:00:00+01:00</DtTm>
</Dt>
</Bal>
<TxsSummry>
<TtlNtries>
<NbOfNtries>2</NbOfNtries>
<Sum>-38.90</Sum>
<TtlNetNtry>
<Amt>38.90</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
</TtlNetNtry>
</TtlNtries>
<TtlCdtNtries>
<NbOfNtries>0</NbOfNtries>
<Sum>0</Sum>
</TtlCdtNtries>
<TtlDbtNtries>
<NbOfNtries>2</NbOfNtries>
<Sum>-38.90</Sum>
</TtlDbtNtries>
</TxsSummry>
<Ntry>
<Amt Ccy="EUR">3.90</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<Sts>
<Cd>BOOK</Cd>
</Sts>
<BookgDt>
<DtTm>2025-07-13T05:32:45.916737+01:00</DtTm>
</BookgDt>
<BkTxCd>
<Prtry>
<Cd>CARD-675</Cd>
</Prtry>
</BkTxCd>
<AddtlNtryInf>Card transaction of EUR issued</AddtlNtryInf>
</Ntry>
<Ntry>
<NtryRef>00001/2025</NtryRef>
<Amt Ccy="EUR">35.00</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<Sts>
<Cd>BOOK</Cd>
</Sts>
<BookgDt>
<DtTm>2025-07-12T08:58:01.327701+01:00</DtTm>
</BookgDt>
<BkTxCd>
<Prtry>
<Cd>TRANSFER-0000</Cd>
</Prtry>
</BkTxCd>
<NtryDtls>
<TxDtls>
<RltdPties>
<Cdtr>
<Pty>
<Nm>LLC Company</Nm>
</Pty>
</Cdtr>
</RltdPties>
</TxDtls>
</NtryDtls>
<AddtlNtryInf>Sent money to LLC Company</AddtlNtryInf>
</Ntry>
</Stmt>
</BkToCstmrStmt>
</Document>
Loading