Skip to content

Commit

Permalink
Fixes #1
Browse files Browse the repository at this point in the history
Add support for IBAN / BIC
  • Loading branch information
pieterclaerhout committed Oct 2, 2019
1 parent ac94c83 commit 793a9cb
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/exchange-rates
/check-iban
/check-vat
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
run-check-iban:
go build -o check-iban github.com/pieterclaerhout/go-finance/cmd/check-iban
./check-iban

run-check-vat:
go build -o check-vat github.com/pieterclaerhout/go-finance/cmd/check-vat
./check-vat
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,30 @@ func main() {

}
```

## IBAN & BIC

There is also a function which converts a regular Belgian Bank Account Number to it's IBAN / BIC equivalent:

```go
package main

import (
"fmt"
"os"

"github.com/pieterclaerhout/go-finance"
)

func main() {

info, err := finance.CheckIBAN("738120256174")
if err != nil {
fmt.Println("ERROR:", err.Error())
os.Exit(1)
}

fmt.Println(info)

}
```
Binary file removed check-vat
Binary file not shown.
20 changes: 20 additions & 0 deletions cmd/check-iban/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"
"os"

"github.com/pieterclaerhout/go-finance"
)

func main() {

info, err := finance.CheckIBAN("738120256174")
if err != nil {
fmt.Println("ERROR:", err.Error())
os.Exit(1)
}

fmt.Println(info)

}
111 changes: 111 additions & 0 deletions iban_bic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package finance

import (
"encoding/xml"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"

"github.com/pkg/errors"
)

// DefaultIBANBICServiceURL is the default IBANBIC service URL to use
const DefaultIBANBICServiceURL = "https://www.ibanbic.be/IBANBIC.asmx"

// IBANBICServiceURL is the SOAP URL to be used when checking a bank account number
var IBANBICServiceURL = DefaultIBANBICServiceURL

// DefaultIBANBICTimeout is the default timeout to use when checking the bank account number
const DefaultIBANBICTimeout = 5 * time.Second

// IBANBICTimeout is the timeout to use when checking the bank account number
var IBANBICTimeout = DefaultIBANBICTimeout

var (
// ErrIBANBICServiceUnreachable is the error returned when the IBANBIC service is unreachable
ErrIBANBICServiceUnreachable = errors.New("IBANBIC service is unreachable")

// ErrIBANBICInvalidInput is the error returned when the bank account number is invalid
ErrIBANBICInvalidInput = errors.New("Number is not a valid bank account number")

// ErrIBANBICServiceError is the error returned when we get a non-standard error from the IBANBIC service
ErrIBANBICServiceError = "IBANBIC service returns an error: "
)

// IBANBICInfo contains the info about a Belgian Bank Account number
type IBANBICInfo struct {
BBAN string // The Belgian Bank Account Number
BankName string // The name of the bank which issues the account
IBAN string // The IBAN number of the bank account
BIC string // The Bank Identification Code of the bank
}

// CheckIBAN checks the Bank Account Number and returns the IBAN and BIC information
func CheckIBAN(number string) (*IBANBICInfo, error) {

if len(number) == 0 {
return nil, ErrIBANBICInvalidInput
}

result := &IBANBICInfo{
BBAN: number,
}

bankName, err := performIBANBICRequest("BBANtoBANKNAME", number)
if err != nil {
return nil, err
}
result.BankName = bankName

ibanAndBic, err := performIBANBICRequest("BBANtoIBANandBIC", number)
if err != nil {
return nil, err
}

ibanBicParts := strings.SplitN(ibanAndBic, "#", 2)
if len(ibanBicParts) < 2 {
return nil, errors.New(ErrIBANBICServiceError + "Failed to get BIC and IBAN code")
}

result.IBAN = ibanBicParts[0]
result.BIC = ibanBicParts[1]

return result, nil

}

func performIBANBICRequest(action string, value string) (string, error) {

url := IBANBICServiceURL + "/" + url.PathEscape(action) + "?Value=" + url.QueryEscape(value)

client := http.Client{
Timeout: VATTimeout,
}

res, err := client.Get(url)
if err != nil {
return "", ErrIBANBICServiceUnreachable
}
defer res.Body.Close()

xmlRes, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}

xmlString := string(xmlRes)
if strings.Contains(xmlString, "Exception") {
exceptionParts := strings.Split(xmlString, "\n")
return "", errors.New(ErrIBANBICServiceError + strings.TrimSpace(exceptionParts[0]))
}

var result string
if err := xml.Unmarshal(xmlRes, &result); err != nil {
return "", err
}

return result, nil

}
166 changes: 166 additions & 0 deletions iban_bic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package finance_test

import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/pieterclaerhout/go-finance"
)

func Test_CheckIBAN(t *testing.T) {

type test struct {
number string
expectedBankName string
expectedIBAN string
expectedBIC string
expectsError bool
}

var tests = []test{
{"738-1202561-74", "KBC Bank", "BE16 7381 2025 6174", "KRED BE BB", false},
{"738120256174", "KBC Bank", "BE16 7381 2025 6174", "KRED BE BB", false},
{"7381202561-74", "KBC Bank", "BE16 7381 2025 6174", "KRED BE BB", false},
{"738-AAAAAAA-74", "", "", "", true},
{"738-AAAAAAA-AA", "", "", "", true},
{"", "", "", "", true},
}

for _, tc := range tests {
t.Run(tc.number, func(t *testing.T) {

info, err := finance.CheckIBAN(tc.number)

if tc.expectsError {

assert.Error(t, err, "error")
assert.Nil(t, info, "info")

} else {

assert.NoError(t, err, "error")
assert.NotNil(t, info, "info")

if info != nil {
assert.Equal(t, tc.expectedBankName, info.BankName, "bank-name")
assert.Equal(t, tc.expectedIBAN, info.IBAN, "IBAN")
assert.Equal(t, tc.expectedBIC, info.BIC, "BIC")
}

}

})
}

}

func Test_CheckIBAN_InvalidURL(t *testing.T) {

finance.IBANBICServiceURL = "ht&@-tp://:aa"
defer func() {
finance.IBANBICServiceURL = finance.DefaultIBANBICServiceURL
}()

result, err := finance.CheckIBAN("738120256174")

assert.Nil(t, result, "result")
assert.Error(t, err, "error")

}

func Test_CheckIBAN_Timeout(t *testing.T) {

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(500 * time.Millisecond)
w.Header().Set("Content-Type", "text/xml")
w.Write([]byte("hello"))
}),
)
defer s.Close()

finance.IBANBICTimeout = 250 * time.Millisecond
finance.IBANBICServiceURL = s.URL
defer func() {
finance.IBANBICTimeout = finance.DefaultIBANBICTimeout
finance.IBANBICServiceURL = finance.DefaultIBANBICServiceURL
}()

result, err := finance.CheckIBAN("738120256174")

assert.Nil(t, result, "result")
assert.Error(t, err, "error")

}

func Test_CheckIBAN_ReadBodyError(t *testing.T) {

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<string xmlns="http://tempuri.org/">KBC Bank</string>`))
}),
)
defer s.Close()

finance.IBANBICServiceURL = s.URL
defer func() {
finance.IBANBICServiceURL = finance.DefaultIBANBICServiceURL
}()

result, err := finance.CheckIBAN("738120256174")

assert.Nil(t, result, "result")
assert.Error(t, err, "error")

}

func Test_CheckIBAN_InvalidBody(t *testing.T) {

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1")
}),
)
defer s.Close()

finance.IBANBICServiceURL = s.URL
defer func() {
finance.IBANBICServiceURL = finance.DefaultIBANBICServiceURL
}()

result, err := finance.CheckIBAN("738120256174")

assert.Nil(t, result, "result")
assert.Error(t, err, "error")

}

func Test_CheckIBAN_PartialFail(t *testing.T) {

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.RequestURI, "BBANtoIBANandBIC") {
w.Header().Set("Content-Length", "1")
return
}
w.Write([]byte(`<string xmlns="http://tempuri.org/">KBC Bank</string>`))
}),
)
defer s.Close()

finance.IBANBICServiceURL = s.URL
defer func() {
finance.IBANBICServiceURL = finance.DefaultIBANBICServiceURL
}()

result, err := finance.CheckIBAN("738120256174")

assert.Nil(t, result, "result")
assert.Error(t, err, "error")

}
Loading

0 comments on commit 793a9cb

Please sign in to comment.