Base code and test

This commit is contained in:
Matthias Grosser 2019-05-08 17:39:52 +02:00
parent 3662a5feec
commit 64ac35efcd
14 changed files with 314 additions and 2 deletions

1
.ruby-version Normal file
View File

@ -0,0 +1 @@
2.6.2

6
Gemfile Normal file
View File

@ -0,0 +1,6 @@
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
# Specify your gem's dependencies in girocode.gemspec
gemspec

View File

@ -1,4 +1,4 @@
MIT License The MIT License (MIT)
Copyright (c) 2019 Matthias Grosser Copyright (c) 2019 Matthias Grosser
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# Girocode
Create QR codes for SEPA bank transfers.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'girocode'
```
## Usage
```ruby
code = Girocode.new(iban: 'DE02100500000054540402', name: 'Beispiel AG', currency: 'EUR', amount: 123.45, reference: 'RE 2019/05/445 744507')
code.to_svg
code.to_png
code.to_html
# in your console
puts code.to_ansi
```

10
Rakefile Normal file
View File

@ -0,0 +1,10 @@
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
end
task :default => :test

14
bin/console Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require "bundler/setup"
require "girocode"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)

8
bin/setup Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here

25
girocode.gemspec Normal file
View File

@ -0,0 +1,25 @@
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'girocode/version'
Gem::Specification.new do |s|
s.name = 'girocode'
s.version = Girocode::VERSION
s.authors = ['Matthias Grosser']
s.email = ['mtgrosser@gmx.net']
s.summary = %q{Generate QR codes for SEPA credit transfers}
s.description = %q{EPC QR code for SEPA payments in any format}
s.homepage = 'https://github.com/mtgrosser/girocode'
s.files = Dir['{lib}/**/*.rb', 'LICENSE', 'README.md', 'CHANGELOG', 'Rakefile']
s.require_paths = ['lib']
s.add_dependency 'rqrcode'
s.add_dependency 'bank-contact'
s.add_development_dependency 'bundler', '~> 1.17'
s.add_development_dependency 'rake', '~> 10.0'
s.add_development_dependency 'minitest', '~> 5.0'
s.add_development_dependency 'simplecov'
end

12
lib/girocode.rb Normal file
View File

@ -0,0 +1,12 @@
require 'bigdecimal'
require 'bank/contact'
require 'rqrcode'
require_relative 'girocode/version'
require_relative 'girocode/code'
module Girocode
def self.new(**attrs)
Girocode::Code.new(**attrs)
end
end

119
lib/girocode/code.rb Normal file
View File

@ -0,0 +1,119 @@
module Girocode
class Error < StandardError; end
class Code
ATTRIBUTES = %i[bic name iban currency amount purpose creditor_reference reference bto_info]
attr_reader *ATTRIBUTES
MAX_PAYLOAD_BYTES = 331
AMOUNT_RANGE = BigDecimal('0.01')..BigDecimal('999999999.99')
def initialize(**attrs)
if keys = attrs.keys - ATTRIBUTES and not keys.empty?
raise ArgumentError, "Illegal attributes #{keys.inspect}"
end
attrs.each { |attr, value| send("#{attr}=", value) }
raise ArgumentError, "iban is required" unless iban?
raise ArgumentError, "name is required" unless name?
raise ArgumentError, 'currency is required for amount' if amount && !currency?
raise ArgumentError, "either creditor reference or reference may be set" if creditor_reference? && reference?
raise ArgumentError, "payload too long" if payload.bytesize > MAX_PAYLOAD_BYTES
end
def bic=(value)
if value.nil?
@bic = nil
else
bic = Bank::BIC.new(value)
raise ArgumentError, "Invalid BIC #{value.inspect}" unless bic.valid?
@bic = bic.to_s
end
end
def name=(value)
value = value.strip
raise ArgumentError, 'name is required' unless value
raise ArgumentError, 'name too long' if value.size > 70
raise ArgumentError, 'Illegal name' if value.include?("\n") || value.include?("\r")
@name = value
end
def iban=(value)
iban = Bank::IBAN.new(value)
raise ArgumentError, "Invalid IBAN #{value.inspect}" unless iban.valid?
@iban = iban.to_s
end
def currency=(value)
value = value.to_s.upcase
raise ArgumentError, "Invalid currency" unless value.match?(/\A[A-Z]{3}\z/)
@currency = value
end
def amount=(value)
raise ArgumentError, 'amount is required' unless value
value = BigDecimal(value, Float::DIG + 1)
raise ArgumentError, "invalid amount #{value.inspect}" unless AMOUNT_RANGE.cover?(value)
@amount = value
end
def purpose=(value)
unless value.nil?
raise ArgumentError, "invalid purpose #{value.inspect}" unless value.match?(/\A[A-z0-9]{0,4}\z/)
end
@purpose = value
end
def creditor_reference=(value)
unless value.nil?
raise ArgumentError, "invalid creditor reference #{value.inspect}" if value.include?("\n") || value.include?("\r") || value.size > 35
end
@creditor_reference = value
end
def reference=(value)
unless value.nil?
raise ArgumentError, "invalid reference #{value.inspect}" if value.include?("\n") || value.include?("\r") || value.size > 140
end
@reference = value
end
def bto_info=(value)
unless value.nil?
raise ArgumentError, "invalid bto_info #{value.inspect}" if value.include?("\n") || value.include?("\r") || value.size > 70
end
@bto_info = value
end
ATTRIBUTES.each do |attr|
define_method("#{attr}?") do
value = instance_variable_get("@#{attr}")
value.respond_to?(:empty?) ? !value.empty? : !!value
end
end
def payload
['BCD', '002', '1', 'SCT',
bic, name, iban,formatted_amount, purpose,
creditor_reference || reference, bto_info].map(&:to_s).join("\n")
end
def to_qrcode
RQRCode::QRCode.new(payload, level: :m, mode: :byte_8bit)
end
def to_ascii
to_qrcode.to_s
end
%i[png svg html ansi].each do |format|
define_method("to_#{format}") { |*args| to_qrcode.public_send("as_#{format}", *args) }
end
private
def formatted_amount
"#{currency}#{amount.round(2).to_s('F')}" if currency? && amount
end
end
end

3
lib/girocode/version.rb Normal file
View File

@ -0,0 +1,3 @@
module Girocode
VERSION = "0.1.0"
end

41
test/data.txt Normal file
View File

@ -0,0 +1,41 @@
xxxxxxx xxx xx xx xx xxxx x x xxxxxxx
x x xxxxx xxx xx xxx xx x x
x xxx x x x xxxxx xxx x xxx x xxx x
x xxx x xx xxx x xxxxxxx x xxx x
x xxx x xx x xx x xxxxxx x x x x xxx x
x x xxx xx xx x xxx x xx x x x
xxxxxxx x x x x x x x x x x x x x xxxxxxx
xx xxx xxx x
x xxxxxx x x xxx xxxxx xxxx x xxx
xx x xx xx xx x x x xxxx x x x
xx xxx x xx xxxx xxxxx x x x x
x x xxxx xxxx x xxx xxxxx
xx xxx xx xx x x x x xxx xxxx
x x x xx x xxxx xxxx xxx x xxx xxxxx
x xxxxxx xx x xx xxxxx x x xx xxxx
xx x xx xx xx x x xx x x x x xx x
xxxxxxx xxx xx x x x x x x
xx x xxxx x x xx x x xx x x xx x
xxx x x x xxx x xx xxxxx x
xxx xx x x x x xx x xx x
x x x xx xxx x xxxx x xxxxx xx x x
xxx xx xx x x xx x x x x xxx x
x x x xxxx xx xxxxxx x x x xx
x xxx x xx x xx x xx x x xx
x xxx x xx x xxxx x xx x xxxx xx
x x x x xx xx x x xxxx xxxx
x x xxxxxx xxxxxxx x xx x xx x x x
xxx x xxxx xx xx x x xx xxx xxx xxx
xx x xx xx x xxx xxx xx x xxx
xx x xx x xxx x xx xxx x x x x
xx x xxxxx x x x x xxxx xx x xxx xxx
xx xxx xxx xx xxxx x xx x x x
xxx xxxx x x xxx xxx xx xxxxxxxxx
xx xxx xxxx xxx xx xx x x x
xxxxxxx x x x xx xx x x xx x xx
x x x xxxx xx x xx x x
x xxx x xx x xx x xxxxxx x xxxxxxx x
x xxx x xx x xxx xxx x xxx xxx x
x xxx x x xxxxxxxxx xx xxxxx x x
x x x x x x x xx xxx x x x x x
xxxxxxx x x xx x xxx x x x xx xx

19
test/girocode_test.rb Normal file
View File

@ -0,0 +1,19 @@
require_relative 'test_helper'
class GirocodeTest < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::Girocode::VERSION
end
def test_girocode
attrs = { bic: 'BHBLDEHHXXX', name: 'Franz Mustermänn', iban: 'DE71110220330123456789', currency: :eur, amount: 12.3, purpose: 'GDDS', creditor_reference: 'RF18539007547034' }
code = Girocode.new(attrs)
assert_equal data(:data), code.to_ascii
end
private
def data(name)
Pathname(__dir__).join("#{name}.txt").read
end
end

30
test/test_helper.rb Normal file
View File

@ -0,0 +1,30 @@
ENV["RAILS_ENV"] = "test"
require 'pathname'
if RUBY_VERSION >= '1.9'
require 'simplecov'
SimpleCov.start do
if artifacts_dir = ENV['CC_BUILD_ARTIFACTS']
coverage_dir Pathname.new(artifacts_dir).relative_path_from(Pathname.new(SimpleCov.root)).to_s
end
add_filter '/test/'
add_filter 'vendor'
end
SimpleCov.at_exit do
SimpleCov.result.format!
if result = SimpleCov.result
File.open(File.join(SimpleCov.coverage_path, 'coverage_percent.txt'), 'w') { |f| f << result.covered_percent.to_s }
end
end
end
require 'rubygems'
require 'bundler/setup'
Bundler.require(:default)
#require 'byebug'
require 'minitest/autorun'
require 'girocode'