Skip to content

Commit

Permalink
Ccy.Amt validation: %.2f to conform to spec
Browse files Browse the repository at this point in the history
fixes #8
  • Loading branch information
stapelberg committed Dec 9, 2023
1 parent 0e93366 commit 23c2fd6
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 3 deletions.
26 changes: 23 additions & 3 deletions qrbill.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ package qrbill

import (
"bytes"
"fmt"
"image"
"log"
"regexp"
"strconv"
"strings"

"github.com/makiuchi-d/gozxing/qrcode/decoder"
Expand Down Expand Up @@ -128,11 +131,28 @@ func (a QRCHCcyAmt) Validate() QRCHCcyAmt {
c := a

if c.Amt != "" {
parsed, err := strconv.ParseFloat(c.Amt, 64)
if err != nil {
log.Printf("ParseFloat(%q): %v", c.Amt, err)
}

// The Swiss Payment Standards 2019 Swiss Implementation Guidelines
// QR-bill Version 2.3 explains:
//
// The amount element is to be entered without leading
// zeroes, including decimal separators and two decimal
// places.
// Decimal, maximum 12-digits permitted, including decimal
// separators. Only decimal points (".") are permitted as
// decimal separators. The amount must be between CHF/
// EUR 0.01 and 999,999,999.99
//
// (Notably, the validator is less strict and also permits values
// without decimal separators or with only one decimal place.)
//
// Some banking apps are picky regarding integer numbers (e.g. 50) and
// require a separator plus two digits (e.g. 50.00).
if !strings.Contains(c.Amt, ".") {
c.Amt += ".00"
}
c.Amt = fmt.Sprintf("%.2f", parsed)
}

return c
Expand Down
110 changes: 110 additions & 0 deletions qrbill_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package qrbill_test

import (
"testing"

"github.com/stapelberg/qrbill"
)

func TestAmountValidation(t *testing.T) {
for _, tt := range []struct {
amount string
wantAmount string
}{
{
// ensure empty amount values are not modified
amount: "",
wantAmount: "",
},

{
amount: "50",
wantAmount: "50.00",
},

{
amount: "50.3",
wantAmount: "50.30",
},

{
amount: "50.32",
wantAmount: "50.32",
},

{
amount: "50.32",
wantAmount: "50.32",
},

{
amount: "50.000",
wantAmount: "50.00",
},

{
amount: "50.-",
wantAmount: "0.00", // result of invalid input
},

{
amount: ".30",
wantAmount: "0.30",
},

{
amount: ".3",
wantAmount: "0.30",
},

{
// minimum amount mentioned in the Implementation Guidelines
amount: "0.01",
wantAmount: "0.01",
},

{
// maximum amount mentioned in the Implementation Guidelines
amount: "999999999.99",
wantAmount: "999999999.99",
},
} {
t.Run(tt.amount, func(t *testing.T) {
qrch := &qrbill.QRCH{
CdtrInf: qrbill.QRCHCdtrInf{
IBAN: "CH0209000000870913543",
Cdtr: qrbill.Address{
AdrTp: qrbill.AddressTypeCombined,
Name: "Legalize it",
StrtNmOrAdrLine1: "Quellenstrasse 25",
BldgNbOrAdrLine2: "8005 Zürich",
Ctry: "CH",
},
},
CcyAmt: qrbill.QRCHCcyAmt{
Amt: tt.amount,
Ccy: "CHF",
},
UltmtDbtr: qrbill.Address{
AdrTp: qrbill.AddressTypeCombined,
Name: "Michael Stapelberg",
StrtNmOrAdrLine1: "Stauffacherstr 42",
BldgNbOrAdrLine2: "8004 Zürich",
Ctry: "CH",
},
RmtInf: qrbill.QRCHRmtInf{
Tp: "NON", // Reference type
Ref: "", // Reference
AddInf: qrbill.QRCHRmtInfAddInf{
Ustrd: "test",
},
},
}

validated := qrch.Validate()
if got, want := validated.CcyAmt.Amt, tt.wantAmount; got != want {
t.Errorf("CcyAmt.Amt = %q, want %q", got, want)
}
})
}
}

0 comments on commit 23c2fd6

Please sign in to comment.