Base code and test
This commit is contained in:
parent
3662a5feec
commit
64ac35efcd
1
.ruby-version
Normal file
1
.ruby-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
2.6.2
|
6
Gemfile
Normal file
6
Gemfile
Normal 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
|
4
LICENSE
4
LICENSE
@ -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
24
README.md
Normal 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
10
Rakefile
Normal 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
14
bin/console
Executable 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
8
bin/setup
Executable 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
25
girocode.gemspec
Normal 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
12
lib/girocode.rb
Normal 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
119
lib/girocode/code.rb
Normal 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
3
lib/girocode/version.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module Girocode
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
41
test/data.txt
Normal file
41
test/data.txt
Normal 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
19
test/girocode_test.rb
Normal 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
30
test/test_helper.rb
Normal 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'
|
Loading…
x
Reference in New Issue
Block a user