Skip to content

Commit 6c12236

Browse files
committed
Implement go version of the Bolivian invoice code algorithm
1 parent efd26b7 commit 6c12236

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 Pablo Crivella.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

complicode.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package complicode
2+
3+
import (
4+
"crypto/rc4"
5+
"encoding/hex"
6+
"math"
7+
"strconv"
8+
"strings"
9+
"time"
10+
11+
"github.com/chtison/baseconverter"
12+
"github.com/osamingo/checkdigit"
13+
)
14+
15+
var verhoeff = checkdigit.NewVerhoeff()
16+
17+
type Invoice struct {
18+
Nit int
19+
Number int
20+
Amount float64
21+
Date time.Time
22+
}
23+
24+
type asciiSums struct {
25+
total int
26+
partials []int
27+
}
28+
29+
const base64 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"
30+
31+
// Generate a code for the given authCode, key and invoice
32+
func Generate(authCode string, key string, inv Invoice) string {
33+
seed1 := strconv.Itoa(inv.Number)
34+
seed2 := strconv.Itoa(inv.Nit)
35+
seed3 := inv.Date.Format("20060102")
36+
seed4 := strconv.FormatFloat(math.Round(inv.Amount), 'f', -1, 64)
37+
38+
seeds := []string{seed1, seed2, seed3, seed4}
39+
seeds = appendVerificationsDigits(seeds, 2)
40+
digits := generateVerificationDigits(seeds)
41+
partialKeys := generatePartialKeys(key, digits)
42+
43+
seeds = append([]string{authCode}, seeds...)
44+
seeds = appendPartialKeys(seeds, partialKeys)
45+
46+
encryptionKey := key + digits
47+
encryptedData := encrypt(strings.Join(seeds, ""), encryptionKey)
48+
asciiSums := generateASCIISums(encryptedData, len(partialKeys))
49+
50+
sum := 0
51+
52+
for i, partialSum := range asciiSums.partials {
53+
sum += (asciiSums.total * partialSum / len(partialKeys[i]))
54+
}
55+
56+
data := changeBase(sum)
57+
code := encrypt(data, encryptionKey)
58+
59+
return format(code)
60+
}
61+
62+
func appendVerificationsDigits(seeds []string, count int) []string {
63+
for index, seed := range seeds {
64+
seeds[index] = appendVerificationDigits(seed, count)
65+
}
66+
67+
return seeds
68+
}
69+
70+
func appendVerificationDigits(seed string, count int) string {
71+
for i := 1; i <= count; i++ {
72+
digit, _ := verhoeff.Generate(seed)
73+
seed += strconv.Itoa(digit)
74+
}
75+
76+
return seed
77+
}
78+
79+
func generateVerificationDigits(seeds []string) string {
80+
sum := sumSeeds(seeds)
81+
digits := appendVerificationDigits(strconv.Itoa(sum), 5)
82+
83+
return digits[len(digits)-5:]
84+
}
85+
86+
func sumSeeds(seeds []string) int {
87+
sum := 0
88+
89+
for _, seed := range seeds {
90+
number, _ := strconv.Atoi(seed)
91+
sum += number
92+
}
93+
94+
return sum
95+
}
96+
97+
func generatePartialKeys(key string, verificationDigits string) []string {
98+
var partialKeySizes []int
99+
100+
for _, digit := range strings.Split(verificationDigits, "") {
101+
size, _ := strconv.Atoi(digit)
102+
partialKeySizes = append(partialKeySizes, size+1)
103+
}
104+
105+
var partialKeys []string
106+
107+
start := 0
108+
for _, size := range partialKeySizes {
109+
end := start + size
110+
pk := key[start:end]
111+
partialKeys = append(partialKeys, pk)
112+
start += size
113+
}
114+
115+
return partialKeys
116+
}
117+
118+
func appendPartialKeys(seeds []string, partialKeys []string) []string {
119+
for index, seed := range seeds {
120+
seeds[index] = seed + partialKeys[index]
121+
}
122+
123+
return seeds
124+
}
125+
126+
func encrypt(data string, key string) string {
127+
cipher, _ := rc4.NewCipher([]byte(key))
128+
encrypted := make([]byte, len(data))
129+
cipher.XORKeyStream(encrypted, []byte(data))
130+
131+
return strings.ToUpper(hex.EncodeToString(encrypted))
132+
}
133+
134+
func generateASCIISums(data string, partialsCount int) asciiSums {
135+
sums := asciiSums{total: 0, partials: make([]int, partialsCount)}
136+
137+
for i, b := range []byte(data) {
138+
sums.total += int(b)
139+
sums.partials[i%partialsCount] += int(b)
140+
}
141+
142+
return sums
143+
}
144+
145+
func changeBase(number int) string {
146+
result, _ := baseconverter.UInt64ToBase(uint64(number), base64)
147+
148+
return result
149+
}
150+
151+
func format(code string) string {
152+
formatted := ""
153+
154+
for i, char := range code {
155+
if i != 0 && i%2 == 0 {
156+
formatted += "-"
157+
}
158+
formatted += string(char)
159+
}
160+
161+
return formatted
162+
}

complicode_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package complicode
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestGenerate(t *testing.T) {
9+
authCode := "29040011007"
10+
key := "9rCB7Sv4X29d)5k7N%3ab89p-3(5[A"
11+
date, _ := time.Parse("20060102", "20070702")
12+
inv := Invoice{Number: 1503, Nit: 4189179011, Date: date, Amount: 2500}
13+
code := Generate(authCode, key, inv)
14+
15+
expected := "6A-DC-53-05-14"
16+
17+
if code != expected {
18+
t.Errorf("Generate() = %q, expected %q", code, expected)
19+
}
20+
}

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/pablocrivella/complicode-go
2+
3+
go 1.13
4+
5+
require (
6+
github.com/chtison/baseconverter v1.1.0
7+
github.com/osamingo/checkdigit v1.0.0
8+
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
github.com/chtison/baseconverter v1.1.0 h1:sQU2ajzqQ9SRL/Bu7pYO8WriYUaGhO2tkJ5mEYVZn2A=
2+
github.com/chtison/baseconverter v1.1.0/go.mod h1:5dP/UBFmWK2kow1iJj1N0HxArisokPO54a5WdI6b/PY=
3+
github.com/osamingo/checkdigit v1.0.0 h1:c5vj+swZrFKWtQEUqj9DbgzTGYa1UscZO5Rwz9L8RKU=
4+
github.com/osamingo/checkdigit v1.0.0/go.mod h1:1YyIqzN+gc5pRmqS39JtDY8Wz0CWY4+VOpR4dtQLUAY=

0 commit comments

Comments
 (0)