prawn-swiss_qr_bill 0.4.1 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +39 -6
- data/assets/fonts/inter/Inter-Bold.ttf +0 -0
- data/assets/fonts/inter/Inter-Regular.ttf +0 -0
- data/assets/fonts/inter/OFL.txt +93 -0
- data/{lib/prawn/swiss_qr_bill → assets}/images/scissor.png +0 -0
- data/{lib/prawn/swiss_qr_bill → assets}/images/swiss_cross.png +0 -0
- data/lib/prawn/swiss_qr_bill/bill.rb +19 -2
- data/lib/prawn/swiss_qr_bill/corner_box.rb +2 -0
- data/lib/prawn/swiss_qr_bill/cutting_lines.rb +7 -4
- data/lib/prawn/swiss_qr_bill/extension.rb +2 -2
- data/lib/prawn/swiss_qr_bill/qr/data.rb +76 -13
- data/lib/prawn/swiss_qr_bill/reference.rb +98 -0
- data/lib/prawn/swiss_qr_bill/sections/payment_amount.rb +1 -1
- data/lib/prawn/swiss_qr_bill/sections/qr_code.rb +7 -7
- data/lib/prawn/swiss_qr_bill/sections/section.rb +2 -1
- data/lib/prawn/swiss_qr_bill/sections.rb +2 -2
- data/lib/prawn/swiss_qr_bill/specifications.rb +1 -0
- data/lib/prawn/swiss_qr_bill/version.rb +1 -1
- data/lib/prawn/swiss_qr_bill.rb +1 -0
- data/prawn-swiss_qr_bill.gemspec +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c557c856c20a70bceddb8203bca2fe9f178d089d61ec4e4c834faf744d3ac3be
|
4
|
+
data.tar.gz: 7e80215d72f150895b7ac341b4ccdc03f85187ef68a22811d2fa9da81f61aa17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9be4ccc1aa68143a96a61cdd3c0650a70ea14003d047ea67cebd62065d60f9d9d1740df1cc79033705d4d9e35d95e63ee80ca69c3d9249636bc87a307eb88d9f
|
7
|
+
data.tar.gz: f3f5fe1f9a636bbe5f4091fc943ae38b750c099cf6a4c282a26b47823d7e0a032f652763533b4a7027e2354cf3b5fc97ee33ef18488a06a9e39d10008b12e01c
|
data/README.md
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
A Ruby library for creating Swiss QR-bill payment slips inside a PDF. It is
|
4
4
|
built as a [Prawn](https://github.com/prawnpdf/prawn) extension.
|
5
5
|
|
6
|
+
|
7
|
+
[![Gem](https://img.shields.io/gem/v/prawn-swiss_qr_bill?color=green&label=gem%20version)](https://rubygems.org/gems/prawn-swiss_qr_bill)
|
8
|
+
[![Build Status](https://github.com/mitosch/prawn-swiss_qr_bill/workflows/CI/badge.svg)](https://github.com/mitosch/prawn-swiss_qr_bill/actions/workflows/ci.yml)
|
9
|
+
[![Test Coverage](https://codecov.io/gh/mitosch/prawn-swiss_qr_bill/graph/badge.svg)](https://codecov.io/gh/mitosch/prawn-swiss_qr_bill)
|
10
|
+
|
6
11
|
## Installation
|
7
12
|
|
8
13
|
Add the following line to your `Gemfile`:
|
@@ -25,7 +30,7 @@ Define the relevant information for the Swiss QR-bill and render it inside the P
|
|
25
30
|
require 'prawn'
|
26
31
|
require 'prawn/swiss_qr_bill'
|
27
32
|
|
28
|
-
@
|
33
|
+
@bill_data = {
|
29
34
|
creditor: {
|
30
35
|
iban: 'CH08 3080 8004 1110 4136 9',
|
31
36
|
address: {
|
@@ -44,7 +49,7 @@ require 'prawn/swiss_qr_bill'
|
|
44
49
|
Prawn::Document.generate('output.pdf', page_size: 'A4') do
|
45
50
|
text 'A Swiss QR bill'
|
46
51
|
|
47
|
-
swiss_qr_bill(@
|
52
|
+
swiss_qr_bill(@bill_data)
|
48
53
|
end
|
49
54
|
```
|
50
55
|
|
@@ -52,13 +57,13 @@ This will render the Swiss QR-bill at the bottom of the page:
|
|
52
57
|
|
53
58
|
![Swiss QR-bill Example, PDF](./images/sqb_example_01.png)
|
54
59
|
|
55
|
-
###
|
60
|
+
### Bill data structure
|
56
61
|
|
57
|
-
The following
|
62
|
+
The following data structure for the bill can be specified:
|
58
63
|
|
59
64
|
```ruby
|
60
65
|
# *: mandatory
|
61
|
-
@
|
66
|
+
@bill_data = {
|
62
67
|
creditor: {
|
63
68
|
iban: '<iban>', # *
|
64
69
|
address: { # *
|
@@ -91,9 +96,37 @@ The following options are available:
|
|
91
96
|
|
92
97
|
If `debtor` or `amount` amount is not given, a box will be printed.
|
93
98
|
|
99
|
+
### Options
|
100
|
+
|
101
|
+
Calling `swiss_qr_bill()` method with options:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# ...
|
105
|
+
Prawn::Document.generate('output.pdf', page_size: 'A4') do
|
106
|
+
# ...
|
107
|
+
|
108
|
+
# raises InvalidIBANError when @bill_data[:creditor][:iban] is invalid
|
109
|
+
swiss_qr_bill(@bill_data, validate: true)
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
Available options:
|
114
|
+
|
115
|
+
| Option | Data type | Description | Default |
|
116
|
+
| --- | --- | --- | --- |
|
117
|
+
| `validate` | boolean | Validates IBAN and Reference Number and raises several errors | `false` |
|
118
|
+
|
119
|
+
Errors which can be raised during validation:
|
120
|
+
|
121
|
+
* `MissingIBANError`: When IBAN is missing.
|
122
|
+
* `InvalidIBANError`: When IBAN is invalid. It checks for CH-IBAN only.
|
123
|
+
* `InvalidReferenceError`: When reference is invalid. It checks for a valid QRR or SCOR reference
|
124
|
+
|
94
125
|
## Important
|
95
126
|
|
96
|
-
This library
|
127
|
+
This library can validate IBAN (switzerland only) and reference number (types QRR and SCOR).
|
128
|
+
It does not however validate, if the given data is fully valid according to the implementation guidelines.
|
129
|
+
|
97
130
|
Please refer to the implementation guidelines and the Swiss QR-bill validaton
|
98
131
|
portal by SIX below.
|
99
132
|
|
Binary file
|
Binary file
|
@@ -0,0 +1,93 @@
|
|
1
|
+
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
2
|
+
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
5
|
+
http://scripts.sil.org/OFL
|
6
|
+
|
7
|
+
|
8
|
+
-----------------------------------------------------------
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
10
|
+
-----------------------------------------------------------
|
11
|
+
|
12
|
+
PREAMBLE
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
14
|
+
development of collaborative font projects, to support the font creation
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
17
|
+
with others.
|
18
|
+
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
24
|
+
however, cannot be released under any other type of license. The
|
25
|
+
requirement for fonts to remain under this license does not apply
|
26
|
+
to any document created using the fonts or their derivatives.
|
27
|
+
|
28
|
+
DEFINITIONS
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
31
|
+
include source files, build scripts and documentation.
|
32
|
+
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
34
|
+
copyright statement(s).
|
35
|
+
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
37
|
+
distributed by the Copyright Holder(s).
|
38
|
+
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
42
|
+
new environment.
|
43
|
+
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
45
|
+
writer or other person who contributed to the Font Software.
|
46
|
+
|
47
|
+
PERMISSION & CONDITIONS
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
51
|
+
Software, subject to the following conditions:
|
52
|
+
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
55
|
+
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
58
|
+
contains the above copyright notice and this license. These can be
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
62
|
+
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
66
|
+
presented to the users.
|
67
|
+
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
72
|
+
permission.
|
73
|
+
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
75
|
+
must be distributed entirely under this license, and must not be
|
76
|
+
distributed under any other license. The requirement for fonts to
|
77
|
+
remain under this license does not apply to any document created
|
78
|
+
using the Font Software.
|
79
|
+
|
80
|
+
TERMINATION
|
81
|
+
This license becomes null and void if any of the above conditions are
|
82
|
+
not met.
|
83
|
+
|
84
|
+
DISCLAIMER
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
File without changes
|
File without changes
|
@@ -4,17 +4,34 @@ module Prawn
|
|
4
4
|
module SwissQRBill
|
5
5
|
# Bill renders the Swiss QR-bill at the bottom of a page
|
6
6
|
class Bill
|
7
|
-
|
7
|
+
FONT_DIR = File.expand_path("#{__dir__}/../../../assets/fonts")
|
8
|
+
|
9
|
+
def initialize(document, data, options = {})
|
8
10
|
@doc = document
|
9
11
|
@data = data
|
12
|
+
@options = options || {}
|
10
13
|
end
|
11
14
|
|
12
15
|
def draw
|
16
|
+
set_font
|
17
|
+
|
13
18
|
@doc.canvas do
|
14
|
-
Sections.draw_all(@doc, @data)
|
19
|
+
Sections.draw_all(@doc, @data, @options)
|
15
20
|
CuttingLines.new(@doc).draw
|
16
21
|
end
|
17
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def set_font
|
27
|
+
@doc.font_families.update(
|
28
|
+
'Inter' => {
|
29
|
+
normal: "#{FONT_DIR}/inter/Inter-Regular.ttf",
|
30
|
+
bold: "#{FONT_DIR}/inter/Inter-Bold.ttf"
|
31
|
+
}
|
32
|
+
)
|
33
|
+
@doc.font 'Inter'
|
34
|
+
end
|
18
35
|
end
|
19
36
|
end
|
20
37
|
end
|
@@ -4,7 +4,7 @@ module Prawn
|
|
4
4
|
module SwissQRBill
|
5
5
|
# Horizontal and vertical cutting lines with a scissor symbol
|
6
6
|
class CuttingLines
|
7
|
-
SCISSOR_FILE = File.expand_path("#{__dir__}/images/scissor.png")
|
7
|
+
SCISSOR_FILE = File.expand_path("#{__dir__}/../../../assets/images/scissor.png")
|
8
8
|
|
9
9
|
SCISSOR_WIDTH = 5.mm
|
10
10
|
SCISSOR_HEIGHT = 3.mm
|
@@ -17,7 +17,7 @@ module Prawn
|
|
17
17
|
def initialize(document)
|
18
18
|
@doc = document
|
19
19
|
|
20
|
-
@brain = {}
|
20
|
+
@brain = { border: { color: nil, width: nil } }
|
21
21
|
|
22
22
|
load_specs
|
23
23
|
end
|
@@ -40,8 +40,10 @@ module Prawn
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def set_styles
|
43
|
-
@brain[:
|
43
|
+
@brain[:border][:width] = doc.line_width
|
44
|
+
@brain[:border][:color] = doc.stroke_color
|
44
45
|
|
46
|
+
doc.stroke_color '000000'
|
45
47
|
doc.line_width 0.5
|
46
48
|
doc.dash 2, space: 2
|
47
49
|
end
|
@@ -66,7 +68,8 @@ module Prawn
|
|
66
68
|
end
|
67
69
|
|
68
70
|
def reset_styles
|
69
|
-
doc.line_width = @brain[:
|
71
|
+
doc.line_width = @brain[:border][:width]
|
72
|
+
doc.stroke_color = @brain[:border][:color]
|
70
73
|
doc.undash
|
71
74
|
end
|
72
75
|
end
|
@@ -4,8 +4,8 @@ module Prawn
|
|
4
4
|
module SwissQRBill
|
5
5
|
# Extend prawn with *swiss_qr_bill* methods
|
6
6
|
module Extension
|
7
|
-
def swiss_qr_bill(data)
|
8
|
-
Prawn::SwissQRBill::Bill.new(self, data).draw
|
7
|
+
def swiss_qr_bill(data, options = {})
|
8
|
+
Prawn::SwissQRBill::Bill.new(self, data, options).draw
|
9
9
|
end
|
10
10
|
|
11
11
|
def swiss_qr_bill_sections
|
@@ -3,21 +3,24 @@
|
|
3
3
|
module Prawn
|
4
4
|
module SwissQRBill
|
5
5
|
module QR
|
6
|
+
class MissingIBANError < StandardError; end
|
7
|
+
class InvalidIBANError < StandardError; end
|
8
|
+
class InvalidReferenceError < StandardError; end
|
9
|
+
|
6
10
|
# The data of the Swiss QR-bill
|
7
11
|
#
|
8
12
|
# References:
|
9
13
|
# https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf
|
10
14
|
# * Chapter 4, Page 25
|
11
|
-
#
|
12
|
-
# TODO: implement skippable (alt schemas, bill info. fields after trailer)
|
13
|
-
# TODO: check if addr-type has to be mentioned if not given?
|
14
|
-
# OPTIMIZE: implement non-changable?
|
15
15
|
class Data
|
16
|
-
#
|
16
|
+
# Field structure:
|
17
17
|
# * :default => default value to be set, if key is not given
|
18
18
|
# * :format => Proc to call when generating output
|
19
19
|
# * :skippable => Do not output in QR data if not given
|
20
|
-
|
20
|
+
# * :validation => Proc or symbol for validation
|
21
|
+
# * if Proc given: will be called with value as argument
|
22
|
+
# * if symbol given: method "#{:symbol}_validator" will be called with value as argument
|
23
|
+
Field = Struct.new(:default, :format, :skippable, :validation)
|
21
24
|
|
22
25
|
# Available fields of the QR code data, ordered.
|
23
26
|
FIELDS = {
|
@@ -26,9 +29,7 @@ module Prawn
|
|
26
29
|
version: Field.new('0200'),
|
27
30
|
# fixed: 1
|
28
31
|
coding: Field.new('1'),
|
29
|
-
|
30
|
-
# iban: Field.new(nil, ->(value) { value.delete(' ') }),
|
31
|
-
iban: Field.new,
|
32
|
+
iban: Field.new(nil, nil, false, :iban),
|
32
33
|
# enum: S, K
|
33
34
|
creditor_address_type: Field.new('K'),
|
34
35
|
creditor_address_name: Field.new,
|
@@ -59,7 +60,7 @@ module Prawn
|
|
59
60
|
debtor_address_country: Field.new,
|
60
61
|
# enum: QRR, SCOR, NON
|
61
62
|
reference_type: Field.new('NON'),
|
62
|
-
reference: Field.new(nil, ->(
|
63
|
+
reference: Field.new(nil, ->(v) { v && v.delete(' ') }, false, :reference),
|
63
64
|
unstructured_message: Field.new,
|
64
65
|
# fixed: EPD
|
65
66
|
trailer: Field.new('EPD'),
|
@@ -71,10 +72,11 @@ module Prawn
|
|
71
72
|
alternative_parameters: Field.new(nil, nil, true)
|
72
73
|
}.freeze
|
73
74
|
|
74
|
-
# TODO: check if all fields can be changed by user?
|
75
75
|
attr_accessor(*FIELDS.keys)
|
76
76
|
|
77
|
-
def initialize(fields = {})
|
77
|
+
def initialize(fields = {}, options = {})
|
78
|
+
@options = options || {}
|
79
|
+
|
78
80
|
# set defaults
|
79
81
|
FIELDS.each_key do |field|
|
80
82
|
instance_variable_set("@#{field}", FIELDS[field].default)
|
@@ -87,18 +89,79 @@ module Prawn
|
|
87
89
|
end
|
88
90
|
|
89
91
|
def generate
|
92
|
+
validate if @options[:validate]
|
93
|
+
|
90
94
|
stack = []
|
91
|
-
FIELDS.
|
95
|
+
FIELDS.each_key do |k|
|
92
96
|
var = instance_variable_get("@#{k}")
|
93
97
|
|
98
|
+
# TODO: fix possible wrong format if alt parameters (last one) is given
|
94
99
|
next if FIELDS[k][:skippable] && var.nil?
|
95
100
|
|
101
|
+
# TODO: use #process; generate method should only call validate, then process, then render as string
|
96
102
|
var = FIELDS[k][:format].call(var) if FIELDS[k][:format].is_a?(Proc)
|
97
103
|
|
98
104
|
stack << var
|
99
105
|
end
|
100
106
|
stack.join("\r\n")
|
101
107
|
end
|
108
|
+
|
109
|
+
def process
|
110
|
+
FIELDS.each_key do |k|
|
111
|
+
var = instance_variable_get("@#{k}")
|
112
|
+
|
113
|
+
instance_variable_set("@#{k}", FIELDS[k][:format].call(var)) if FIELDS[k][:format].is_a?(Proc)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate
|
118
|
+
FIELDS.each_key do |k|
|
119
|
+
next unless FIELDS[k][:validation]
|
120
|
+
|
121
|
+
var = instance_variable_get("@#{k}")
|
122
|
+
|
123
|
+
call_validator(FIELDS[k][:validation], var)
|
124
|
+
end
|
125
|
+
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def call_validator(validator, value)
|
132
|
+
case validator
|
133
|
+
when Proc
|
134
|
+
# NOTE: currently not in use
|
135
|
+
# :nocov:
|
136
|
+
validator.call(value)
|
137
|
+
# :nocov:
|
138
|
+
when Symbol
|
139
|
+
send("#{validator}_validator", value)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def iban_validator(value)
|
144
|
+
# IBAN must be given
|
145
|
+
raise MissingIBANError, 'IBAN is missing' if value.nil? || value.empty?
|
146
|
+
|
147
|
+
# IBAN must be valid
|
148
|
+
iban = IBAN.new(value)
|
149
|
+
raise InvalidIBANError, "IBAN #{iban.prettify} is invalid" unless iban.valid?
|
150
|
+
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
def reference_validator(value)
|
155
|
+
reference = Reference.new(value, reference_type)
|
156
|
+
|
157
|
+
unless %w[QRR SCOR].include?(reference_type)
|
158
|
+
raise InvalidReferenceError, "Reference Type #{reference_type} invalid. Allowed: QRR, SCOR"
|
159
|
+
end
|
160
|
+
|
161
|
+
raise InvalidReferenceError, "Reference #{value} is invalid" unless reference.valid?
|
162
|
+
|
163
|
+
true
|
164
|
+
end
|
102
165
|
end
|
103
166
|
end
|
104
167
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prawn
|
4
|
+
module SwissQRBill
|
5
|
+
# Check validity of reference number
|
6
|
+
#
|
7
|
+
# QRR reference:
|
8
|
+
# Refer to the implementation guides of SIX: https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf
|
9
|
+
class Reference
|
10
|
+
MODULO_TABLE = [
|
11
|
+
[0, 9, 4, 6, 8, 2, 7, 1, 3, 5],
|
12
|
+
[9, 4, 6, 8, 2, 7, 1, 3, 5, 0],
|
13
|
+
[4, 6, 8, 2, 7, 1, 3, 5, 0, 9],
|
14
|
+
[6, 8, 2, 7, 1, 3, 5, 0, 9, 4],
|
15
|
+
[8, 2, 7, 1, 3, 5, 0, 9, 4, 6],
|
16
|
+
[2, 7, 1, 3, 5, 0, 9, 4, 6, 8],
|
17
|
+
[7, 1, 3, 5, 0, 9, 4, 6, 8, 2],
|
18
|
+
[1, 3, 5, 0, 9, 4, 6, 8, 2, 7],
|
19
|
+
[3, 5, 0, 9, 4, 6, 8, 2, 7, 1],
|
20
|
+
[5, 0, 9, 4, 6, 8, 2, 7, 1, 3]
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
def initialize(reference, type = 'QRR')
|
24
|
+
@type = type
|
25
|
+
@reference = standardize(reference)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Number without check digit
|
29
|
+
def number
|
30
|
+
case @type
|
31
|
+
when 'QRR'
|
32
|
+
@reference[0...-1]
|
33
|
+
when 'SCOR'
|
34
|
+
@reference[4..-1]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# QRR: Last number, the check digit
|
39
|
+
# SCOR: 2 numbers after RF
|
40
|
+
def check_digits
|
41
|
+
case @type
|
42
|
+
when 'QRR'
|
43
|
+
@reference[-1].to_i
|
44
|
+
when 'SCOR'
|
45
|
+
@reference[2..3]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def valid?
|
50
|
+
valid_check_digits? && valid_length?
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid_check_digits?
|
54
|
+
case @type
|
55
|
+
when 'QRR'
|
56
|
+
check_digits == Reference.modulo10_recursive(number)
|
57
|
+
when 'SCOR'
|
58
|
+
scor_to_i % 97 == 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# NOTE: for SCOR only
|
63
|
+
def scor_to_i
|
64
|
+
"#{number}RF#{check_digits}".gsub(/[A-Z]/) { |c| c.ord - 55 }.to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
# According to the payment standards (PDF, Annex B):
|
68
|
+
# The QR reference consists of 27 positions and is numerical.
|
69
|
+
def valid_length?
|
70
|
+
case @type
|
71
|
+
when 'QRR'
|
72
|
+
@reference.length <= 27
|
73
|
+
when 'SCOR'
|
74
|
+
@reference.length <= 25
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Generate a check digit with modulo 10 recursive:
|
79
|
+
#
|
80
|
+
# Can be used as an instance method:
|
81
|
+
#
|
82
|
+
# Prawn::SwissQRBill::Reference.modulo10_recursive("2202202029999")
|
83
|
+
# # will return 1
|
84
|
+
def self.modulo10_recursive(reference)
|
85
|
+
numbers = reference.to_s.chars.map(&:to_i)
|
86
|
+
report = numbers.inject(0) { |memo, c| MODULO_TABLE[memo][c] }
|
87
|
+
|
88
|
+
(10 - report) % 10
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def standardize(reference)
|
94
|
+
reference.to_s.strip.gsub(/\s+/, '').upcase
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -11,14 +11,12 @@ module Prawn
|
|
11
11
|
#
|
12
12
|
# swiss_cross.png => 166px ~> 7.mm
|
13
13
|
# QR-code: 1090px <~ 46.mm
|
14
|
-
#
|
15
|
-
# TODO: iban check -> raise exception if configured
|
16
14
|
class QRCode < Section
|
17
15
|
KEY = 'payment.qr_code'
|
18
16
|
|
19
17
|
QR_PX_SIZE = 1160
|
20
18
|
|
21
|
-
SWISS_CROSS_FILE = File.expand_path("#{__dir__}
|
19
|
+
SWISS_CROSS_FILE = File.expand_path("#{__dir__}/../../../../assets/images/swiss_cross.png")
|
22
20
|
|
23
21
|
MAPPING = {
|
24
22
|
creditor_address_type: %i[creditor address type],
|
@@ -26,6 +24,7 @@ module Prawn
|
|
26
24
|
creditor_address_line1: %i[creditor address line1],
|
27
25
|
creditor_address_line2: %i[creditor address line2],
|
28
26
|
creditor_address_postal_code: %i[creditor address postal_code],
|
27
|
+
creditor_address_city: %i[creditor address city],
|
29
28
|
creditor_address_country: %i[creditor address country],
|
30
29
|
debtor_address_type: %i[debtor address type],
|
31
30
|
debtor_address_name: %i[debtor address name],
|
@@ -71,16 +70,17 @@ module Prawn
|
|
71
70
|
end
|
72
71
|
|
73
72
|
def generate_qr_data(data)
|
74
|
-
|
73
|
+
flat_data = {}
|
75
74
|
MAPPING.each_key do |key|
|
76
|
-
# check if the exists
|
75
|
+
# check if the key exists
|
77
76
|
next unless deep_key?(data, MAPPING[key])
|
78
77
|
|
79
|
-
|
78
|
+
flat_data[key] = data.dig(*MAPPING[key])
|
80
79
|
end
|
81
80
|
|
82
81
|
iban = IBAN.new(data[:creditor][:iban])
|
83
|
-
|
82
|
+
|
83
|
+
qr_data = QR::Data.new(flat_data.merge(iban: iban.code), validate: @options[:validate])
|
84
84
|
qr_data.generate
|
85
85
|
end
|
86
86
|
|
@@ -19,13 +19,14 @@ module Prawn
|
|
19
19
|
# specifications for subclass' section, @see Specification for details
|
20
20
|
attr_accessor :specs
|
21
21
|
|
22
|
-
def initialize(document, data)
|
22
|
+
def initialize(document, data, options = {})
|
23
23
|
unless self.class.const_defined?(:KEY)
|
24
24
|
raise NotImplementedError, "constant KEY not defined in class #{self.class}"
|
25
25
|
end
|
26
26
|
|
27
27
|
@doc = document
|
28
28
|
@data = data
|
29
|
+
@options = options || {}
|
29
30
|
|
30
31
|
load_specs
|
31
32
|
end
|
@@ -19,9 +19,9 @@ module Prawn
|
|
19
19
|
].freeze
|
20
20
|
|
21
21
|
# Draw all sections in the right order.
|
22
|
-
def self.draw_all(document, data)
|
22
|
+
def self.draw_all(document, data, options = {})
|
23
23
|
SECTION_CLASSES.each do |klass|
|
24
|
-
klass.new(document, data).draw
|
24
|
+
klass.new(document, data, options).draw
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/prawn/swiss_qr_bill.rb
CHANGED
@@ -17,6 +17,7 @@ require 'prawn/swiss_qr_bill/helpers/box_helper'
|
|
17
17
|
|
18
18
|
require 'prawn/swiss_qr_bill/qr/data'
|
19
19
|
require 'prawn/swiss_qr_bill/iban'
|
20
|
+
require 'prawn/swiss_qr_bill/reference'
|
20
21
|
require 'prawn/swiss_qr_bill/padded_box'
|
21
22
|
require 'prawn/swiss_qr_bill/corner_box'
|
22
23
|
require 'prawn/swiss_qr_bill/cutting_lines'
|
data/prawn-swiss_qr_bill.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.files = Dir['lib/**/**/*.rb',
|
18
18
|
'lib/prawn/swiss_qr_bill/specs.yml',
|
19
|
-
'
|
19
|
+
'assets/images/*', 'assets/fonts/inter/*',
|
20
20
|
'config/locales/*.yml',
|
21
21
|
'prawn-swiss_qr_bill.gemspec',
|
22
22
|
'Gemfile', 'README.md', 'CHANGELOG.mg'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prawn-swiss_qr_bill
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mischa Schindowski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -159,6 +159,11 @@ extra_rdoc_files: []
|
|
159
159
|
files:
|
160
160
|
- Gemfile
|
161
161
|
- README.md
|
162
|
+
- assets/fonts/inter/Inter-Bold.ttf
|
163
|
+
- assets/fonts/inter/Inter-Regular.ttf
|
164
|
+
- assets/fonts/inter/OFL.txt
|
165
|
+
- assets/images/scissor.png
|
166
|
+
- assets/images/swiss_cross.png
|
162
167
|
- config/locales/de.yml
|
163
168
|
- config/locales/en.yml
|
164
169
|
- config/locales/fr.yml
|
@@ -172,10 +177,9 @@ files:
|
|
172
177
|
- lib/prawn/swiss_qr_bill/helpers/box_helper.rb
|
173
178
|
- lib/prawn/swiss_qr_bill/helpers/number_helper.rb
|
174
179
|
- lib/prawn/swiss_qr_bill/iban.rb
|
175
|
-
- lib/prawn/swiss_qr_bill/images/scissor.png
|
176
|
-
- lib/prawn/swiss_qr_bill/images/swiss_cross.png
|
177
180
|
- lib/prawn/swiss_qr_bill/padded_box.rb
|
178
181
|
- lib/prawn/swiss_qr_bill/qr/data.rb
|
182
|
+
- lib/prawn/swiss_qr_bill/reference.rb
|
179
183
|
- lib/prawn/swiss_qr_bill/sections.rb
|
180
184
|
- lib/prawn/swiss_qr_bill/sections/payment_amount.rb
|
181
185
|
- lib/prawn/swiss_qr_bill/sections/payment_further_information.rb
|
@@ -211,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
215
|
- !ruby/object:Gem::Version
|
212
216
|
version: '0'
|
213
217
|
requirements: []
|
214
|
-
rubygems_version: 3.
|
218
|
+
rubygems_version: 3.2.32
|
215
219
|
signing_key:
|
216
220
|
specification_version: 4
|
217
221
|
summary: Swiss QR-Bill PDFs in Ruby with Prawn
|