class Furs

Constants

FURS_PRODUCTION_ENDPOINT
FURS_TEST_ENDPOINT
HIGH_TAX_RATE
INVOICE_ISSUE_PATH
LOW_TAX_RATE
REGISTER_BUSINESS_UNIT_PATH

Public Class Methods

new(cert_path:, cert_password:, production: false) click to toggle source
# File lib/furs_fiscal_verification.rb, line 19
def initialize(cert_path:, cert_password:, production: false)
  @cert = OpenSSL::PKCS12.new(File.read(cert_path), cert_password)
  @endpoint = production ? FURS_PRODUCTION_ENDPOINT : FURS_TEST_ENDPOINT
end

Public Instance Methods

_build_tax_specification(low_tax_rate_base, low_tax_rate_amount, high_tax_rate_base, high_tax_rate_amount) click to toggle source
# File lib/furs_fiscal_verification.rb, line 148
def _build_tax_specification(low_tax_rate_base,
                             low_tax_rate_amount,
                             high_tax_rate_base,
                             high_tax_rate_amount)
  low_tax_spec = {
    'TaxRate' => LOW_TAX_RATE,
    'TaxableAmount' => low_tax_rate_base,
    'TaxAmount' => low_tax_rate_amount
  }

  high_tax_spec = {
    'TaxRate' => HIGH_TAX_RATE,
    'TaxableAmount' => high_tax_rate_base,
    'TaxAmount' => high_tax_rate_amount
  }

  [low_tax_spec, high_tax_spec].select { |spec| !spec['TaxableAmount'].nil? }
end
calculate_zoi(tax_number:, issued_date:, invoice_number:, business_premise_id:, electronic_device_id:, invoice_amount:) click to toggle source
# File lib/furs_fiscal_verification.rb, line 30
def calculate_zoi(tax_number:, issued_date:, invoice_number:, business_premise_id:, electronic_device_id:,
                  invoice_amount:)
  content = "#{tax_number}#{issued_date.strftime('%d-%m-%Y %H:%M:%S')}#{invoice_number}#{business_premise_id}
             #{electronic_device_id}#{invoice_amount}"

  Digest::MD5.hexdigest(_sign(content))
end
furs_accessible?(msg: 'ping') click to toggle source
# File lib/furs_fiscal_verification.rb, line 24
def furs_accessible?(msg: 'ping')
  data = { 'EchoRequest' => msg }
  response = _post(data: data, path: "/v1/cash_registers/echo", sign: false)
  JSON.parse(response.body)["EchoResponse"] == msg
end
prepare_printable(tax_number, zoi, issued_date) click to toggle source
# File lib/furs_fiscal_verification.rb, line 223
def prepare_printable(tax_number, zoi, issued_date)
  formatted_date = issued_date.strftime('%y%m%d%H%M%S')
  zoi_base_10 = zoi.hex.to_s(10).rjust(39, '0')
  data = "#{zoi_base_10}#{tax_number}#{formatted_date}"
  control = data.chars.map(&:to_i).inject(:+) % 10
  "#{data}#{control}"
end
register_immovable_business_premise(tax_number:, premise_id:, real_estate_cadastral_number:, real_estate_building_number:, real_estate_building_section_number:, street:, house_number:, house_number_additional:, community:, city:, postal_code:, validity_date:, software_supplier_tax_number: nil, foreign_software_supplier_name: nil, special_notes: 'No notes') click to toggle source
# File lib/furs_fiscal_verification.rb, line 167
def register_immovable_business_premise(tax_number:,
                                        premise_id:,
                                        real_estate_cadastral_number:,
                                        real_estate_building_number:,
                                        real_estate_building_section_number:,
                                        street:,
                                        house_number:,
                                        house_number_additional:,
                                        community:,
                                        city:,
                                        postal_code:,
                                        validity_date:,
                                        software_supplier_tax_number: nil,
                                        foreign_software_supplier_name: nil,
                                        special_notes: 'No notes')

  data = {
    "BusinessPremiseRequest" => {
      "Header" => {
        "MessageID" => SecureRandom.uuid,
        "DateTime" => DateTime.now.strftime("%Y-%m-%dT%H:%M:%SZ")
      },
      "BusinessPremise" => {
        "TaxNumber" => tax_number.to_i,
        "BusinessPremiseID" => premise_id,
        "BPIdentifier" => {
          "RealEstateBP" => {
            "PropertyID" => {
              "CadastralNumber" => real_estate_cadastral_number.to_i,
              "BuildingNumber" => real_estate_building_number.to_i,
              "BuildingSectionNumber" => real_estate_building_section_number.to_i
            },
            "Address" => {
              "Street" => street,
              "HouseNumber" => house_number.to_s,
              "HouseNumberAdditional" => house_number_additional,
              "Community" => community,
              "City" => city,
              "PostalCode" => postal_code.to_s
            }
          }
        },
        "ValidityDate" => validity_date.strftime("%Y-%m-%d"),
        "SoftwareSupplier" => [
          {
            "NameForeign" => foreign_software_supplier_name
          }
        ],
        "SpecialNotes" => special_notes
      }
    }
  }

  _post(path: REGISTER_BUSINESS_UNIT_PATH, data: data)
end
report_invoice(zoi:, tax_number:, issued_date:, invoice_number:, business_premise_id:, electronic_device_id:, invoice_amount:, low_tax_rate_base: nil, low_tax_rate_amount: nil, high_tax_rate_base: nil, high_tax_rate_amount: nil, other_taxes_amount: nil, exempt_vat_taxable_amount: nil, reverse_vat_taxable_amount: nil, non_taxable_amount: nil, special_tax_rules_amount: nil, payment_amount: nil, customer_vat_number: nil, returns_amount: nil, operator_tax_number: nil, foreign_operator: nil, subsequent_submit: nil, reference_invoice_number: nil, reference_invoice_business_premise_id: nil, reference_invoice_electronic_device_id: nil, reference_invoice_issued_date: nil, numbering_structure: 'B', special_notes: '') click to toggle source
# File lib/furs_fiscal_verification.rb, line 38
def report_invoice(zoi:,
                   tax_number:,
                   issued_date:,
                   invoice_number:,
                   business_premise_id:,
                   electronic_device_id:,
                   invoice_amount:,
                   low_tax_rate_base: nil,
                   low_tax_rate_amount: nil,
                   high_tax_rate_base: nil,
                   high_tax_rate_amount: nil,
                   other_taxes_amount: nil,
                   exempt_vat_taxable_amount: nil,
                   reverse_vat_taxable_amount: nil,
                   non_taxable_amount: nil,
                   special_tax_rules_amount: nil,
                   payment_amount: nil,
                   customer_vat_number: nil,
                   returns_amount: nil,
                   operator_tax_number: nil,
                   foreign_operator: nil,
                   subsequent_submit: nil,
                   reference_invoice_number: nil,
                   reference_invoice_business_premise_id: nil,
                   reference_invoice_electronic_device_id: nil,
                   reference_invoice_issued_date: nil,
                   numbering_structure: 'B',
                   special_notes: '')

  data = {}
  data['InvoiceRequest'] = {
    "Header" => {
      "MessageID" => SecureRandom.uuid,
      "DateTime" => DateTime.now.strftime("%Y-%m-%dT%H:%M:%SZ")
    },
    'Invoice' => {
      'TaxNumber' => tax_number.to_i,
      'IssueDateTime' => issued_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
      'NumberingStructure' => numbering_structure,
      'InvoiceIdentifier' => {
        'BusinessPremiseID' => business_premise_id,
        'ElectronicDeviceID' => electronic_device_id,
        'InvoiceNumber' => invoice_number
      },
      'InvoiceAmount' => invoice_amount,
      'PaymentAmount' => if payment_amount then payment_amount else invoice_amount end,
      'ProtectedID' => zoi,
      'TaxesPerSeller' => []
    }
  }

  tax_spec = {}
  if low_tax_rate_base || high_tax_rate_base
    tax_spec['VAT'] = _build_tax_specification(
      low_tax_rate_base,
      low_tax_rate_amount,
      high_tax_rate_base,
      high_tax_rate_amount)
  end

  tax_spec['NontaxableAmount'] = non_taxable_amount if non_taxable_amount

  if reverse_vat_taxable_amount
    tax_spec['ReverseVATTaxableAmount'] = reverse_vat_taxable_amount
  end

  if exempt_vat_taxable_amount
    tax_spec['ExemptVATTaxableAmount'] = exempt_vat_taxable_amount
  end

  tax_spec['OtherTaxesAmount'] = other_taxes_amount if other_taxes_amount

  data['InvoiceRequest']['Invoice']['TaxesPerSeller'] << tax_spec

  if customer_vat_number
    data['InvoiceRequest']['Invoice']['CustomerVATNumber'] = customer_vat_number
  end

  if returns_amount
    data['InvoiceRequest']['Invoice']['ReturnsAmount'] = returns_amount
  end

  if operator_tax_number
    data['InvoiceRequest']['Invoice']['OperatorTaxNumber'] = operator_tax_number
  end

  if foreign_operator
    data['InvoiceRequest']['Invoice']['ForeignOperator'] = true
  end

  if subsequent_submit
    data['InvoiceRequest']['Invoice']['SubsequentSubmit'] = true
  end

  if reference_invoice_number
    reference_invoice = [{
      'ReferenceInvoiceIdentifier' => {
        'BusinessPremiseID' => reference_invoice_business_premise_id,
        'ElectronicDeviceID' => reference_invoice_electronic_device_id,
        'InvoiceNumber' => reference_invoice_number
      },
      'ReferenceInvoiceIssueDateTime' => reference_invoice_issued_date.strftime("%Y-%m-%dT%H:%M:%SZ")
    }]

    data['InvoiceRequest']['Invoice']['ReferenceInvoice'] = reference_invoice
  end

  _post(path: INVOICE_ISSUE_PATH, data: data)
end

Private Instance Methods

_jws_header() click to toggle source
# File lib/furs_fiscal_verification.rb, line 252
def _jws_header
  {
    'alg' => 'RS256',
    'subject_name' => @cert.certificate.subject.to_a.map { |subject| "#{subject[0]}=#{subject[1]}" }.join(","),
    'issuer_name' => @cert.certificate.issuer.to_a.map { |subject| "#{subject[0]}=#{subject[1]}" }.join(","),
    'serial' => @cert.certificate.serial.to_i
  }
end
_jwt_sign(header:, payload:) click to toggle source
# File lib/furs_fiscal_verification.rb, line 261
def _jwt_sign(header:, payload:)
  private_key = @cert.key
  JWT.encode(payload, private_key, 'RS256', header)
end
_post(path:, data:, sign: true) click to toggle source
# File lib/furs_fiscal_verification.rb, line 232
def _post(path:, data:, sign: true)
  if sign
    data = {
      'token' => _jwt_sign(header: _jws_header, payload: data)
    }
  end

  url = "#{@endpoint}#{path}"
  uri = URI(url)
  https = Net::HTTP.new(uri.host, uri.port)
  https.use_ssl = true
  https.open_timeout = 60
  https.read_timeout = 60
  https.verify_mode = OpenSSL::SSL::VERIFY_NONE # should probably verify...
  https.cert = @cert.certificate
  https.key = @cert.key
  req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json; charset=UTF-8')
  https.request(req, data.to_json)
end
_sign(content) click to toggle source
# File lib/furs_fiscal_verification.rb, line 266
def _sign(content)
  digest = OpenSSL::Digest::SHA256.new
  @cert.key.sign(digest, content)
end