Skip to content

Commit

Permalink
Merge pull request #19 from naomijub/include-emv-nonbreaking-fields
Browse files Browse the repository at this point in the history
include emv fields
  • Loading branch information
naomijub authored Sep 23, 2020
2 parents 3666589 + 5028824 commit 08424fd
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "brcode"
version = "1.3.0"
version = "1.3.1"
authors = ["Julia Naomi <[email protected]>"]
edition = "2018"
description = "Crate to parse and emit BR Codes"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A crate to parse and emit [PIX BR Code](https://www.bcb.gov.br/content/estabilid

```toml
[dependencies]
brcode = "1.3.0"
brcode = "1.3.1"
```

### Build from source
Expand Down Expand Up @@ -546,7 +546,7 @@ For 10 runs: 378.68780764861793 us.
```

## Goals
- [x] Parse BR Code String;
- [x] Parse BR Code String to `Vec<(usize, Data)>` (more flexible solution);
- [x] Parse BR Code to `BrCode` struct;
- [x] Emit BR Code from `Vec<(usize, Data)>`;
- [x] Emit BR Code from `BrCode` struct;
Expand Down
2 changes: 1 addition & 1 deletion clj-brcode/test/clj_brcode/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[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")
(def edn {:payload-version 1, :initiation-method nil, :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"}]}]})
(def edn {:payload-version 1, :initiation-method nil, :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", :convenience nil, :convenience-fee-fixed nil, :convenience-fee-percentage nil, :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"}]}]})

(deftest brcode-test
(testing "conform rust result to clojure"
Expand Down
15 changes: 8 additions & 7 deletions src/aux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,23 @@ impl FromIterator<(usize, parse::Data)> for HashBrCode {
}

pub fn crc16_ccitt(message: &str) -> String {
let mut crc: u16 = 0xFFFF; // initial value
let polynomial: u16 = 0x1021; // 0001 0000 0010 0001 (0, 5, 12)
let mut crc: u16 = 0xFFFF; // initial value
let polynomial: u16 = 0x1021; // 0001 0000 0010 0001 (0, 5, 12)
let bytes = message.as_bytes();

for b in bytes {
for i in 0u16..8u16 {
let bit = (b >> (7-i) & 1) == 1;
let c15 = (crc >> 15 & 1) == 1;
let bit = (b >> (7 - i) & 1) == 1;
let c15 = (crc >> 15 & 1) == 1;
crc <<= 1;
if c15 ^ bit {crc ^= polynomial};
if c15 ^ bit {
crc ^= polynomial
};
}
}

crc &= 0xffff;



format!("{:X}", crc)
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ pub(crate) mod emit;
pub(crate) mod model;
pub(crate) mod parse;

pub use aux::crc16_ccitt;
pub use model::{BrCode, Info, Label, MerchantInfo, Template};
pub use parse::Data;
pub use aux::crc16_ccitt;

pub fn from_str(code: &str) -> Vec<(usize, parse::Data)> {
parse::parse(code, 99)
Expand Down
92 changes: 92 additions & 0 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@ use serde_derive::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, SerdeDeserialize, SerdeSerialize)]
pub struct BrCode {
pub payload_version: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub initiation_method: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub merchant_account_information: Option<String>,
pub merchant_information: Vec<MerchantInfo>,
pub merchant_category_code: u32,
pub merchant_name: String,
pub merchant_city: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub convenience: Option<String>, //{pub type 55 pub kind pub scalar}
#[serde(skip_serializing_if = "Option::is_none")]
pub convenience_fee_fixed: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] // {pub type 56 pub kind pub scalar}
pub convenience_fee_percentage: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] // {pub type 57 pub kind pub scalar}
pub postal_code: Option<String>,
pub currency: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<f64>,
pub country_code: String,
pub field_template: Vec<Label>,
pub crc1610: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub templates: Option<Vec<Template>>,
}

Expand Down Expand Up @@ -107,6 +118,9 @@ impl From<Vec<(usize, Data)>> for BrCode {
postal_code: hash.get(&61usize).map(crate::aux::Data::to_str),
currency: hash[&53usize].to_str(),
amount: hash.get(&54usize).map(|e| e.to_str().parse().unwrap()),
convenience: hash.get(&55usize).map(crate::aux::Data::to_str),
convenience_fee_fixed: hash.get(&56usize).map(crate::aux::Data::to_str),
convenience_fee_percentage: hash.get(&67usize).map(crate::aux::Data::to_str),
country_code: hash[&58usize].to_str(),
field_template: vec![Label {
reference_label: hash[&62usize].to_hash()[&5usize].to_str(),
Expand All @@ -122,6 +136,66 @@ impl From<Vec<(usize, Data)>> for BrCode {
}

impl BrCode {
pub fn is_pix(&self) -> bool {
self.merchant_information
.iter()
.filter(|e| e.id >= 26 && e.id <= 51)
.any(|e| {
e.info
.iter()
.filter(|i| i.id == 0)
.any(|i| i.info.to_uppercase() == "BR.GOV.BCB.PIX")
})
}

pub fn get_transaction_id(&self) -> Option<String> {
Some(self.field_template.first()?.reference_label.clone())
}

pub fn get_alias(&self) -> Option<Vec<String>> {
if self.is_pix() {
Some(
self.merchant_information
.iter()
.filter(|e| e.id >= 26 && e.id <= 51)
.flat_map(|e| {
e.info
.iter()
.filter(|i| {
i.id == 1 && e.info.first().unwrap().info == "BR.GOV.BCB.PIX"
})
.map(|i| i.info.clone())
.collect::<Vec<String>>()
})
.collect::<Vec<String>>(),
)
} else {
None
}
}

pub fn get_message(&self) -> Option<Vec<String>> {
if self.is_pix() {
Some(
self.merchant_information
.iter()
.filter(|e| e.id >= 26 && e.id <= 51)
.flat_map(|e| {
e.info
.iter()
.filter(|i| {
i.id == 2 && e.info.first().unwrap().info == "BR.GOV.BCB.PIX"
})
.map(|i| i.info.clone())
.collect::<Vec<String>>()
})
.collect::<Vec<String>>(),
)
} else {
None
}
}

pub fn encode(self) -> String {
let mut encode = String::new();
encode.push_str(&format!("0002{:02}", self.payload_version));
Expand All @@ -148,6 +222,18 @@ impl BrCode {
None => (),
Some(a) => encode.push_str(&format!("54{:02}{}", a.to_string().len(), a)),
}
match self.convenience {
None => (),
Some(c) => encode.push_str(&format!("5502{}", c)),
}
match self.convenience_fee_fixed {
None => (),
Some(c) => encode.push_str(&format!("56{:02}{}", c.to_string().len(), c)),
}
match self.convenience_fee_percentage {
None => (),
Some(c) => encode.push_str(&format!("57{:02}{}", c.to_string().len(), c)),
}
encode.push_str(&format!("5802{}", self.country_code));
encode.push_str(&format!(
"59{:02}{}",
Expand Down Expand Up @@ -213,6 +299,9 @@ mod test {
merchant_category_code: 0000u32,
merchant_name: "NOME DO RECEBEDOR".to_string(),
merchant_city: "BRASILIA".to_string(),
convenience: None,
convenience_fee_fixed: None,
convenience_fee_percentage: None,
merchant_information: vec![
MerchantInfo {
id: 26,
Expand Down Expand Up @@ -278,6 +367,9 @@ mod test {
merchant_category_code: 0000u32,
merchant_name: "NOME DO RECEBEDOR".to_string(),
merchant_city: "BRASILIA".to_string(),
convenience: None,
convenience_fee_fixed: None,
convenience_fee_percentage: None,
merchant_information: vec![
MerchantInfo {
id: 26,
Expand Down
47 changes: 43 additions & 4 deletions tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use brcode::{
self, edn_from_brcode, edn_to_brcode, from_str, json_from_brcode, json_to_brcode,
str_to_brcode, BrCode, Data, Info, Label, MerchantInfo, Template, crc16_ccitt_from_message
self, crc16_ccitt_from_message, edn_from_brcode, edn_to_brcode, from_str, json_from_brcode,
json_to_brcode, str_to_brcode, BrCode, Data, Info, Label, MerchantInfo, Template,
};

#[test]
Expand Down Expand Up @@ -47,6 +47,37 @@ fn minimum_breaking_code() {
assert_eq!(from_str(code), expected);
}

#[test]
fn brcode_is_pix() {
let from = str_to_brcode(&code());

assert!(from.is_pix())
}

#[test]
fn brcode_label() {
let from = str_to_brcode(&code());

assert_eq!(
from.get_transaction_id(),
Some("RP12345678-2019".to_string())
)
}

#[test]
fn brcode_get_alias() {
let from = str_to_brcode(&brcode_with_alias_message());

assert_eq!(from.get_alias(), Some(vec!["11999887766".to_string()]))
}

#[test]
fn brcode_get_message() {
let from = str_to_brcode(&brcode_with_alias_message());

assert_eq!(from.get_message(), Some(vec!["Hello message".to_string()]))
}

#[test]
fn json_ffi() {
let code = code();
Expand Down Expand Up @@ -107,12 +138,12 @@ fn code() -> String {
}

fn json() -> String {
"{\"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\"}]}]}"
"{\"payload_version\":1,\"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\"}]}]}"
.to_string()
}

fn edn() -> String {
"{ :payload-version 1, :initiation-method nil, :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\", }], }], }"
"{ :payload-version 1, :initiation-method nil, :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\", :convenience nil, :convenience-fee-fixed nil, :convenience-fee-percentage nil, :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\", }], }], }"
.to_string()
}

Expand All @@ -124,6 +155,9 @@ fn brcode_expected() -> BrCode {
merchant_category_code: 0000u32,
merchant_name: "NOME DO RECEBEDOR".to_string(),
merchant_city: "BRASILIA".to_string(),
convenience: None,
convenience_fee_fixed: None,
convenience_fee_percentage: None,
merchant_information: vec![
MerchantInfo {
id: 26,
Expand Down Expand Up @@ -219,6 +253,11 @@ fn data_expected() -> Vec<(usize, Data)> {
]
}

fn brcode_with_alias_message() -> String {
"00020104141234567890123426500014BR.GOV.BCB.PIX0111119998877660213Hello message27300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ63049059"
.to_string()
}

// FFI Tests
use std::ffi::{CStr, CString};
use std::mem;
Expand Down

0 comments on commit 08424fd

Please sign in to comment.