From aeec09c3792b617112638a44a72ce30be790b197 Mon Sep 17 00:00:00 2001 From: jlangch Date: Wed, 9 Oct 2024 19:14:18 +0200 Subject: [PATCH] Refactoring :qrref module --- .../modules/ModuleQrRefSection.java | 5 +- .../com/github/jlangch/venice/qrref.venice | 160 ++++++++++-------- 2 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/modules/ModuleQrRefSection.java b/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/modules/ModuleQrRefSection.java index ce0101322..045981726 100644 --- a/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/modules/ModuleQrRefSection.java +++ b/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/modules/ModuleQrRefSection.java @@ -45,9 +45,10 @@ public DocSection section() { final DocSection main = new DocSection("QR Ref", id()); all.addSection(main); - main.addItem(diBuilder.getDocItem("qrref/qr-ref", true)); - main.addItem(diBuilder.getDocItem("qrref/parse", true)); + main.addItem(diBuilder.getDocItem("qrref/create", true)); main.addItem(diBuilder.getDocItem("qrref/format", true)); + main.addItem(diBuilder.getDocItem("qrref/valid?", true)); + main.addItem(diBuilder.getDocItem("qrref/checksum", true)); return section; } diff --git a/src/main/resources/com/github/jlangch/venice/qrref.venice b/src/main/resources/com/github/jlangch/venice/qrref.venice index 8990715be..60a2a73bf 100644 --- a/src/main/resources/com/github/jlangch/venice/qrref.venice +++ b/src/main/resources/com/github/jlangch/venice/qrref.venice @@ -25,10 +25,6 @@ (ns qrref) -(def- qr-ref-len 27) ;; number of QR reference digits, checksum digit included - -(def- qr-ref-raw-len 26) ;; number of QR reference digits, checksum digit excluded - (def- checksum-table [0, 9, 4, 6, 8, 2, 7, 1, 3, 5]) @@ -38,13 +34,6 @@ (defn- digit->char [c] (char (+ c (long #\0)))) ;; 2 -> #\2 -(defn- remove-leading-zeroes [s] - (loop [s s] - (if (and (> (count s) 1) (str/starts-with? s "0")) - (recur (str/rest s)) - s))) - - (defn- remove-whitespaces [s] (apply str (filter #(not (str/whitespace? %)) (seq s)))) @@ -53,67 +42,63 @@ (match? s #"[0-9]+")) -(defn- qr-ref-raw [ref] - (let [padding-zeros (- qr-ref-raw-len (count ref))] +(defn- pad-with-leading-zeroes [ref] + (let [padding-zeros (- 26 (count ref))] (if (neg? padding-zeros) (throw (ex :VncException "The QR-Reference ref number is too long!")) (str (str/repeat "0" padding-zeros) ref)))) -(defn checksum [s] - ^{ :arglists '("(checksum ref)") +(defn + ^{ :arglists '("(create ref-raw)") :doc """ - Calculates the checksum for a raw reference. The reference may - contain spaces. - """ - :examples '( - """ - (do - (load-module :qrref ['qrref :as 'qr]) - (qr/checksum "230 55361 34663 9301") - (qr/checksum "23055361346639301") - (qr/checksum "00 00000 00230 55361 34663 9301") - (qr/checksum "00000000023055361346639301")) - """ ) - :see-also '("qrref/create", "qrref/valid?", "qrref/format") } + Creates a QR reference according to the Swiss payment standards. - (let [s (remove-whitespaces s)] - (if-not (numeric? s) - (throw (ex :VncException "Invalid character in reference (digits allowed only)")) - (loop [carry 0, digits (seq s)] - (if (empty? digits) - (mod (- 10 carry) 10) - (let [digit (digit->long (first digits)) - carry (get checksum-table (mod (+ carry digit) 10))] - (recur carry (rest digits)))))))) + A QR reference has 27 digits. The raw reference plus a checksum digit + as the last digit. + + The raw reference passed must not have more than 26 digits. With less + than 26 digits leading '0' will be used to fill up to 26 digits. + Raw reference: "23055361346639301" -(defn valid? [ref] - ^{ :arglists '("(valid? ref)") - :doc """ - Returns true if ref is a valid QR reference else false. - The reference may contain spaces. + QR reference: "000000000230553613466393013" + + The QR reference can be formatted to "00 00000 00230 55361 34663 93013" + using: + + ``` + (qrref/format "000000000230553613466393013") + ``` + + [Swiss Payment Standards / de](https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-de.pdf) + + [Swiss Payment Standards / en](https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf) """ :examples '( """ (do (load-module :qrref ['qrref :as 'qr]) - (qr/valid? "000000000230553613466393013") - (qr/valid? "00 00000 00230 55361 34663 93013")) + (qr/create "1234") + (qr/create "23055361346639301")) """ ) - :see-also '("qrref/create", "qrref/format", "qrref/checksum") } + :see-also '("qrref/valid?", "qrref/format", "qrref/checksum") } - (let [r (remove-whitespaces ref)] - (if-not (numeric? r) - false - (if-not (== qr-ref-len (count r)) - false - (== 0 (checksum r)))))) + create [ref-raw] + + (let [r (remove-whitespaces ref-raw)] + (when-not (numeric? r) + (throw (ex :VncException "Invalid character in reference (digits allowed only)"))) + (when (> (count r) 26) + (throw (ex :VncException "A raw QR reference must not have more than 26 digits"))) + (let [padded (pad-with-leading-zeroes r) + check-digit (digit->char (checksum padded))] + (str padded check-digit)))) (defn - ^{ :arglists '("(format s)") + ^{ :arglists '("(format ref)") :doc "Format a QR reference." :examples '( """ @@ -123,39 +108,74 @@ """ ) :see-also '("qrref/create", "qrref/valid?", "qrref/checksum") } - format [s] + format [ref] - (->> (seq s) + (->> (seq ref) (reverse) (partition-all 5) (map #(apply str (reverse %))) (reverse) (str/join " "))) -(defn - ^{ :arglists '("(create ref)") + +(defn + ^{ :arglists '("(valid? ref)") :doc """ - Creates a QR reference according to the Swiss payment standards. + Returns true if ref is a valid QR reference else false. + The reference may contain spaces. - [Swiss Payment Standards / de](https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-de.pdf) + A valid QR reference must have 27 digits and the checksum must be + correct. The last digit is the checksum digits for the first 26 digits. + """ + :examples '( + """ + (do + (load-module :qrref ['qrref :as 'qr]) + (qr/valid? "000000000230553613466393013") + (qr/valid? "00 00000 00230 55361 34663 93013")) + """ ) + :see-also '("qrref/create", "qrref/format", "qrref/checksum") } - [Swiss Payment Standards / en](https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf) + valid? [ref] + + (let [r (remove-whitespaces ref)] + (if (and (numeric? r) (== 27 (count r))) + (== 0 (checksum r)) + false))) + + +(defn + ^{ :arglists '("(checksum ref)") + :doc """ + Computes the checksum for a raw reference. + + The passed ref my be a raw QR reference or a QR reference with a + checksum digit. It may contain spaces. + + Returns the computed checksum digit 0..9. + + If the passed ref is a QR reference with a correct checksum digit the + computed checksum digit will always be 0. This fact is used for QR + reference validation! """ :examples '( """ (do (load-module :qrref ['qrref :as 'qr]) - (qr/create "1234") - (qr/create "23055361346639301")) + (qr/checksum "230 55361 34663 9301") + (qr/checksum "23055361346639301") + (qr/checksum "00 00000 00230 55361 34663 9301") + (qr/checksum "00000000023055361346639301")) """ ) - :see-also '("qrref/valid?", "qrref/format", "qrref/checksum") } + :see-also '("qrref/create", "qrref/valid?", "qrref/format") } - create[ref-raw] + checksum [ref] - (let [r (remove-whitespaces ref-raw)] - (if-not (match? r #"[0-9]+") - (throw (ex :VncException "Invalid character in reference (digits allowed only)")) - (if (> (count r) 26) - (throw (ex :VncException "Reference number is longer than 26 digits!")) - (let [r (qr-ref-raw r)] - (str r (digit->char (checksum r)))))))) + (let [r (remove-whitespaces ref)] + (when-not (numeric? r) + (throw (ex :VncException "Invalid character in reference (digits allowed only)"))) + + (let [carry (reduce (fn [carry digit] (get checksum-table (mod (+ carry digit) 10))) + 0 + (map digit->long (seq r)))] + (mod (- 10 carry) 10))))