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

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'