#qrcode #emv

brcode

用于解析和生成BR码的Crate

14个版本 (稳定版)

1.4.2 2021年4月5日
1.4.1 2020年10月13日
1.3.1 2020年9月23日
0.2.0 2020年9月13日
0.1.2 2020年9月12日

#637 in 编码

每月下载27次

LGPL-3.0

84KB
1.5K SLoC

Rust 1K SLoC // 0.0% comments Dart 126 SLoC Batch 78 SLoC Clojure 55 SLoC Java 52 SLoC // 0.1% comments

包含 (JAR文件, 59KB) gradle-wrapper.jar

BR码

用于解析和生成PIX BR码的Crate。

重要变更

  • 版本 1.2BrCode 字段进行了一些小的改动。 PR 修复了 model::BrCode 字段 initiation_method 的命名。

使用方法

[dependencies]
brcode = "1.4.2"

从源代码构建

  1. 安装 rustup
  2. 对于macos和ios文件,使用 make build-macos;对于linux和android文件,使用 make build-linux
  3. 文件将位于 target/release/libbrcode.*target/<target-platform>/release/libbrcode.so
  4. 将它们复制到项目的根目录。
  5. 对于JVM,需要在项目根目录下运行 export LD_LIBRARY_PATH=.

从GitHub发行版复制文件

Shell脚本用于从发行版获取文件

所以

curl -s https://api.github.com/repos/naomijub/brcode/releases/latest \
| grep "browser_download_url.*so" \
| cut -d : -f 2,3 \
| tr -d \" \
| wget -qi -

dylib

curl -s https://api.github.com/repos/naomijub/brcode/releases/latest \
| grep "browser_download_url.*dylib" \
| cut -d : -f 2,3 \
| tr -d \" \
| wget -qi -

示例

解析字符串

use brcode::{from_str, Data};

fn main() {
    let code = "00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38";

    assert_eq!(from_str(code), expected());
}

fn expected() -> Vec<(usize, Data)> {
    vec![
        (0, Data::Single("01".to_string())), 
        (4, Data::Single("12345678901234".to_string())), 
        (26, Data::Vector(vec![
            (0, Data::Single("BR.GOV.BCB.PIX".to_string())), 
            (1, Data::Single("123e4567-e12b-12d1-a456-426655440000".to_string()))])), 
        (27, Data::Vector(vec![
            (0, Data::Single("BR.COM.OUTRO".to_string())), 
            (1, Data::Single("0123456789".to_string()))])), 
        (52, Data::Single("0000".to_string())), 
        (53, Data::Single("986".to_string())), 
        (54, Data::Single("123.45".to_string())), 
        (58, Data::Single("BR".to_string())), 
        (59, Data::Single("NOME DO RECEBEDOR".to_string())), 
        (60, Data::Single("BRASILIA".to_string())), 
        (61, Data::Single("70074900".to_string())), 
        (62, Data::Vector(vec![
            (5, Data::Single("RP12345678-2019".to_string()))])), 
        (80, Data::Vector(vec![(
            0, Data::Single("BR.COM.OUTRO".to_string())), 
            (1, Data::Single("0123.ABCD.3456.WXYZ".to_string()))])), 
        (63, Data::Single("AD38".to_string()))]
}

str_to_brcode

use brcode::{str_to_brcode, BrCode, Template, Info, MerchantInfo, Label};


fn main() {
    let code = "00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38";

    assert_eq!(from_str(code), expected());
}

fn expected() -> BrCode {
    BrCode {
        payload_version: 1,
        initiation_method: None,
        merchant_category_code: 0000u32,
        merchant_name: "NOME DO RECEBEDOR".to_string(),
        merchant_city: "BRASILIA".to_string(),
        merchant_information: vec![
            MerchantInfo {
                id: 26,
                info: vec![
                    Info {
                        id: 0,
                        info: "BR.GOV.BCB.PIX".to_string(),
                    },
                    Info {
                        id: 1,
                        info: "123e4567-e12b-12d1-a456-426655440000".to_string(),
                    },
                ],
            },
            MerchantInfo {
                id: 27,
                info: vec![
                    Info {
                        id: 0,
                        info: "BR.COM.OUTRO".to_string(),
                    },
                    Info {
                        id: 1,
                        info: "0123456789".to_string(),
                    },
                ],
            },
        ],
        currency: "986".to_string(),
        postal_code: Some("70074900".to_string()),
        amount: Some(123.45),
        country_code: "BR".to_string(),
        field_template: vec![Label {
            reference_label: "RP12345678-2019".to_string(),
        }],
        crc1610: "AD38".to_string(),
        templates: Some(vec![Template {
            id: 80usize,
            info: vec![
                Info {
                    id: 0usize,
                    info: "BR.COM.OUTRO".to_string(),
                },
                Info {
                    id: 1usize,
                    info: "0123.ABCD.3456.WXYZ".to_string(),
                },
            ],
        }]),
    }
}

brcode::to_string 来自 Vec<(usize, Data)>

use brcode::{
    self, BrCode, 
};

fn main() {
    let actual = brcode::to_string(brcode_vec());
    let code = "00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38";

    assert_eq!(actual, code);
}

fn brcode_vec() -> Vec<(usize, Data)> {
    vec![
        (0, Data::Single("01".to_string())),
        (4, Data::Single("12345678901234".to_string())),
        (
            26,
            Data::Vector(vec![
                (0, Data::Single("BR.GOV.BCB.PIX".to_string())),
                (
                    1,
                    Data::Single("123e4567-e12b-12d1-a456-426655440000".to_string()),
                ),
            ]),
        ),
        (
            27,
            Data::Vector(vec![
                (0, Data::Single("BR.COM.OUTRO".to_string())),
                (1, Data::Single("0123456789".to_string())),
            ]),
        ),
        (52, Data::Single("0000".to_string())),
        (53, Data::Single("986".to_string())),
        (54, Data::Single("123.45".to_string())),
        (58, Data::Single("BR".to_string())),
        (59, Data::Single("NOME DO RECEBEDOR".to_string())),
        (60, Data::Single("BRASILIA".to_string())),
        (61, Data::Single("70074900".to_string())),
        (
            62,
            Data::Vector(vec![(5, Data::Single("RP12345678-2019".to_string()))]),
        ),
        (
            80,
            Data::Vector(vec![
                (0, Data::Single("BR.COM.OUTRO".to_string())),
                (1, Data::Single("0123.ABCD.3456.WXYZ".to_string())),
            ]),
        ),
        (63, Data::Single("AD38".to_string())),
    ]
}

brcode::brcode_to_string 对于结构 BrCode

use brcode::{
    self, BrCode, 
};

fn main() {
    let actual = brcode::brcode_to_string(brcode_value());
    let code = "00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38";

    assert_eq!(actual, code);
}

fn brcode_value() -> BrCode {
    BrCode {
        payload_version: 1,
        initiation_method: None,
        merchant_account_information: Some(String::from("12345678901234")),
        merchant_category_code: 0000u32,
        merchant_name: "NOME DO RECEBEDOR".to_string(),
        merchant_city: "BRASILIA".to_string(),
        merchant_information: vec![
            MerchantInfo {
                id: 26,
                info: vec![
                    Info {
                        id: 0,
                        info: "BR.GOV.BCB.PIX".to_string(),
                    },
                    Info {
                        id: 1,
                        info: "123e4567-e12b-12d1-a456-426655440000".to_string(),
                    },
                ],
            },
            MerchantInfo {
                id: 27,
                info: vec![
                    Info {
                        id: 0,
                        info: "BR.COM.OUTRO".to_string(),
                    },
                    Info {
                        id: 1,
                        info: "0123456789".to_string(),
                    },
                ],
            },
        ],
        currency: "986".to_string(),
        postal_code: Some("70074900".to_string()),
        amount: Some(123.45),
        country_code: "BR".to_string(),
        field_template: vec![Label {
            reference_label: "RP12345678-2019".to_string(),
        }],
        crc1610: "AD38".to_string(),
        templates: Some(vec![Template {
            id: 80usize,
            info: vec![
                Info {
                    id: 0usize,
                    info: "BR.COM.OUTRO".to_string(),
                },
                Info {
                    id: 1usize,
                    info: "0123.ABCD.3456.WXYZ".to_string(),
                },
            ],
        }]),
    }
}
  • pub fn is_pix(&self) -> bool 判断BrCode是否是 PIX 交易。
  • pub fn get_transaction_id(&self) -> Option<String> 获取 transaction_id 值(第65项的第5个字段)。
  • pub fn get_alias(&self) -> Option<Vec<String>> 获取 PIX 别名的所有可能值。通常只有第26项的第1个字段是有效的。检查别名是否为类型 "BR.GOV.BCB.PIX"
  • pub fn get_message(&self) -> Option<Vec<String>> 获取 PIX 别名的所有可能消息。通常只有第26项的第2个字段是有效的。检查别名是否为类型 "BR.GOV.BCB.PIX"
  • to_svg_string(&self, ecc: QrCodeEcc, size: usize) -> String 在字符串中生成 SVG xml。

  • to_svg_standard_string(&self) -> String 在字符串中生成具有 QrCodeEcc::Lowsize = 1024 的 SVG xml。

  • to_vec_u8(&self, ecc: QrCodeEcc, size: usize) -> Vec<u8> 生成一个格式为 Vec<u8> 的 PNG。

  • to_svg_file(&self, file_path: &str, ecc: QrCodeEcc, size: usize) 在文件路径 file_path 创建一个格式为 .svg 的 QR 码。

  • to_standard_svg_file(&self, file_path: &str) 在文件路径 file_path 创建一个格式为 .svg 的 QR 码,其错误纠正等级为 QrCodeEcc::Low 且大小为 size = 1024

  • to_png_file(&self, file_path: &str, ecc: QrCodeEcc, size: usize) 在文件路径 file_path 创建一个格式为 .png 的 QR 码。

  • file_path 的示例包括从项目根目录开始的 tests/data/file_output.pngtests/data/file_output.svg。如果目录不存在,则会抛出错误。

  • QrCodeEcc 是 QR 码符号中的错误纠正等级,而 size 是图像大小。

    • QrCodeEcc::Low QR 码可以容忍大约 7% 的错误码词。
    • QrCodeEcc::Medium QR 码可以容忍大约 15% 的错误码词。
    • QrCodeEcc::Quartile QR 码可以容忍大约 25% 的错误码词。
    • QrCodeEcc::High QR 码可以容忍大约 30% 的错误码词。

基准测试

from_strbenches/parse.rs

time:   [15.734 us 15.758 us 15.782 us]

str_to_brcodebenches/to_brcode

time:   [24.886 us 24.931 us 24.977 us]

edn_from_brcodebenches/to_brcode

time:   [52.670 us 52.795 us 52.929 us]

json_from_brcodebenches/to_brcode

time:    [28.229 us 28.284 us 28.339 us]

使用 BrCode 进行双向操作

time:   [33.238 us 33.555 us 33.924 us]                          

使用 Vec<(usize, Data)>

time:   [22.867 us 22.958 us 23.107 us]

crc16_ccittbenches/crc16

time:   [3.0738 us 3.0825 us 3.0938 us]

FFI

Clojure FFI

DOCS

BR Code 作为 Edn 调用函数 FFI edn_from_brcode 或使用 clojar [clj-brcode "1.1.0-SNAPSHOT"]。示例

(ns example.core
  (:require [clj-brcode.core :refer :all]))

(def code "00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38")

(brcode->edn code)

; {:payload-version 1, :initiation-method nil, :merchant-information [{:id 26, :info [{:id 0, :info "BR.GOV.BCB.PIX"}, {:id 1, :info "123e4567-e12b-12d1-a456-426655440000"}]}, {:id 27, :info [{:id 0, :info "BR.COM.OUTRO"}, {:id 1, :info "0123456789"}]}], :merchant-category-code 0, :merchant-name "NOME DO RECEBEDOR", :merchant-city "BRASILIA", :postal-code "70074900", :currency "986", :amount 123.45, :country-code "BR", :field-template [{:reference-label "RP12345678-2019"}], :crc1610 "AD38", :templates [{:id 80, :info [{:id 0, :info "BR.COM.OUTRO"}, {:id 1, :info "0123.ABCD.3456.WXYZ"}]}]}

输入

"00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38"

期望的 Edn

{:payload-version 1,:initiation-method nil, :merchant-information [
  {:id 26, :info [{ :id 0, :info "BR.GOV.BCB.PIX",}, {:id 1, :info "123e4567-e12b-12d1-a456-426655440000",}]},
  {:id 27, :info [{ :id 0, :info "BR.COM.OUTRO",}, {:id 1, :info "0123456789",}]}
 ],:merchant-category-code 0, :merchant-name "NOME DO RECEBEDOR", :merchant-city "BRASILIA", 
 :postal-code "70074900", :currency "986", :amount 123.45, :country-code "BR", 
 :field-template [{ :reference-label "RP12345678-2019", }], :crc1610 "AD38", :templates [
   { :id 80, :info [{ :id 0, :info "BR.COM.OUTRO", },{ :id 1, :info "0123.ABCD.3456.WXYZ", }], }]
 }

作为 BR 代码 调用函数 FFI edn_to_brcode 或使用 clojar [clj-brcode "1.1.0-SNAPSHOT"]。示例

(ns example.core
  (:require [clj-brcode.core :refer :all]))

(def edn {:payload-version 1, :initiation-method nil, :merchant-information [{:id 26, :info [{:id 0, :info "BR.GOV.BCB.PIX"}, {:id 1, :info "123e4567-e12b-12d1-a456-426655440000"}]}, {:id 27, :info [{:id 0, :info "BR.COM.OUTRO"}, {:id 1, :info "0123456789"}]}], :merchant-category-code 0, :merchant-name "NOME DO RECEBEDOR", :merchant-city "BRASILIA", :postal-code "70074900", :currency "986", :amount 123.45, :country-code "BR", :field-template [{:reference-label "RP12345678-2019"}], :crc1610 "AD38", :templates [{:id 80, :info [{:id 0, :info "BR.COM.OUTRO"}, {:id 1, :info "0123.ABCD.3456.WXYZ"}]}]})

(brcode->edn edn)

; "00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38"

其他可用函数:

  • json->brcode
  • brcode->json
  • crc16-ccitt

使用 Criterium 的 Clojure 基准测试

brcode->edn

Evaluation count : 4644 in 6 samples of 774 calls.
             Execution time mean : 131.416626 µs
    Execution time std-deviation : 2.218919 µs
   Execution time lower quantile : 130.073353 µs ( 2.5%)
   Execution time upper quantile : 135.212868 µs (97.5%)
                   Overhead used : 8.079635 ns

edn->brcode

Evaluation count : 3816 in 6 samples of 636 calls.
             Execution time mean : 157.407924 µs
    Execution time std-deviation : 3.556917 µs
   Execution time lower quantile : 154.338082 µs ( 2.5%)
   Execution time upper quantile : 162.800564 µs (97.5%)
                   Overhead used : 8.102766 ns

(-> brcode brcode->edn edn->brcode)

Evaluation count : 1920 in 6 samples of 320 calls.
             Execution time mean : 344.903181 µs
    Execution time std-deviation : 26.518055 µs
   Execution time lower quantile : 328.923528 µs ( 2.5%)
   Execution time upper quantile : 390.059255 µs (97.5%)
                   Overhead used : 8.071450 ns

Node FFI

neon-brcode

Dart FFI

DOCS

解析

import 'package:dartbrcode/dartbrcode.dart';

final json = '{"payload_version":1,"initiation_method":null,"merchant_account_information":"12345678901234","merchant_information":[{"id":26,"info":[{"id":0,"info":"BR.GOV.BCB.PIX"},{"id":1,"info":"123e4567-e12b-12d1-a456-426655440000"}]},{"id":27,"info":[{"id":0,"info":"BR.COM.OUTRO"},{"id":1,"info":"0123456789"}]}],"merchant_category_code":0,"merchant_name":"NOME DO RECEBEDOR","merchant_city":"BRASILIA","postal_code":"70074900","currency":"986","amount":123.45,"country_code":"BR","field_template":[{"reference_label":"RP12345678-2019"}],"crc1610":"AD38","templates":[{"id":80,"info":[{"id":0,"info":"BR.COM.OUTRO"},{"id":1,"info":"0123.ABCD.3456.WXYZ"}]}]}';

void main() {
  jsonToBrcode(json);
  // '00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38'
}

输出

import 'package:dartbrcode/dartbrcode.dart';

final brcode = '00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304AD38';

void main() {
  jsonFromBrcode(brcode);
  // '{"payload_version":1,"initiation_method":null,"merchant_account_information":"12345678901234","merchant_information":[{"id":26,"info":[{"id":0,"info":"BR.GOV.BCB.PIX"},{"id":1,"info":"123e4567-e12b-12d1-a456-426655440000"}]},{"id":27,"info":[{"id":0,"info":"BR.COM.OUTRO"},{"id":1,"info":"0123456789"}]}],"merchant_category_code":0,"merchant_name":"NOME DO RECEBEDOR","merchant_city":"BRASILIA","postal_code":"70074900","currency":"986","amount":123.45,"country_code":"BR","field_template":[{"reference_label":"RP12345678-2019"}],"crc1610":"AD38","templates":[{"id":80,"info":[{"id":0,"info":"BR.COM.OUTRO"},{"id":1,"info":"0123.ABCD.3456.WXYZ"}]}]}'
}

其他可用函数:

  • crc16Ccitt

基准测试

  • 使用 dart_benchmarkjsonToBrcode
For 100 runs: peak: 371 us,	bottom: 048 us,	avg: ~083 us

brcodeToJson

For 100 runs: peak: 327 us,	bottom: 069 us,	avg: ~101 us
  • 使用 benchmark_harnessjsonToBrcode
For 10 runs: 207.51774227018055 us.

brcodeToJson

For 10 runs: 378.68780764861793 us.

目标

  • 将 BR 代码字符串解析为 Vec<(usize, Data)>(更灵活的解决方案);
  • 将 BR 代码解析为 BrCode 结构体;
  • Vec<(usize, Data)> 输出 BR 代码;
  • BrCode 结构体输出 BR 代码;
  • CRC16_CCITT
  • FFI

依赖项

~12MB
~86K SLoC