From d84eb6b39f9a1680a819bf72d3e1edcb84a7e466 Mon Sep 17 00:00:00 2001 From: BlockChyp SDK Builder Date: Wed, 6 Nov 2024 21:20:34 +0000 Subject: [PATCH] Bump version to 2.20.1 --- Cargo.toml | 2 +- README.md | 74 +++++++++ examples/card_metadata_example.rs | 39 +++++ src/blockchyp.rs | 57 +++++++ src/models.rs | 261 +++++++++++++++++++++++++++++- 5 files changed, 427 insertions(+), 6 deletions(-) create mode 100644 examples/card_metadata_example.rs diff --git a/Cargo.toml b/Cargo.toml index 2f97d7c..022bf44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blockchyp" -version = "2.18.8" +version = "2.20.1" edition = "2021" description = "This is the SDK for Rust. Like all BlockChyp SDKs, it provides a full client for the BlockChyp gateway and BlockChyp payment terminals." documentation = "https://docs.blockchyp.com/#overview" diff --git a/README.md b/README.md index 8cb3263..aa0a9bb 100644 --- a/README.md +++ b/README.md @@ -503,6 +503,80 @@ fn main() -> Result<(), Box> { ``` +#### Card Metadata + + + +* **API Credential Types:** Merchant +* **Required Role:** Payment API Access + +This API allows you to retrieve card metadata. + +Card metadata requests can use a payment terminal to retrieve metadata or +use a previously enrolled payment token. + +**Terminal Transactions** + +For terminal transactions, make sure you pass in the terminal name using the `terminalName` property. + +**Token Transactions** + +If you have a payment token, omit the `terminalName` property and pass in the token with the `token` +property instead. + +**Card Numbers and Mag Stripes** + +You can also pass in PANs and Mag Stripes, but you probably shouldn't, as this will +put you in PCI scope and the most common vector for POS breaches is keylogging. +If you use terminals for manual card entry, you'll bypass any keyloggers that +might be maliciously running on the point-of-sale system. + + + + +```rust +use blockchyp; +use std::error::Error; + +fn card_metadata_example() -> Result<(), Box> { + // sample credentials + let creds = blockchyp::APICredentials { + api_key: "ZDSMMZLGRPBPRTJUBTAFBYZ33Q".to_string(), + bearer_token: "ZLBW5NR4U5PKD5PNP3ZP3OZS5U".to_string(), + signing_key: "9c6a5e8e763df1c9256e3d72bd7f53dfbd07312938131c75b3bfd254da787947".to_string(), + }; + + // instantiate the client + let client = blockchyp::Client::new(creds); + + let mut request = blockchyp::CardMetadataRequest{ + test: true, + terminal_name: "Test Terminal".to_string(), + ..Default::default() + }; + let (response, err) = client.card_metadata(&mut request); + + if let Some(e) = err { + eprintln!("Unexpected error occurred: {:?}", e); + return Err(e) + } + + if response.success { + println!("success"); + } + + println!("Response: {:?}", response); + Ok(()) +} + +fn main() -> Result<(), Box> { + card_metadata_example()?; + println!("Example completed successfully!"); + Ok(()) +} + +``` + #### Time Out Reversal diff --git a/examples/card_metadata_example.rs b/examples/card_metadata_example.rs new file mode 100644 index 0000000..910121c --- /dev/null +++ b/examples/card_metadata_example.rs @@ -0,0 +1,39 @@ +use blockchyp; +use std::error::Error; + +fn card_metadata_example() -> Result<(), Box> { + // sample credentials + let creds = blockchyp::APICredentials { + api_key: "ZDSMMZLGRPBPRTJUBTAFBYZ33Q".to_string(), + bearer_token: "ZLBW5NR4U5PKD5PNP3ZP3OZS5U".to_string(), + signing_key: "9c6a5e8e763df1c9256e3d72bd7f53dfbd07312938131c75b3bfd254da787947".to_string(), + }; + + // instantiate the client + let client = blockchyp::Client::new(creds); + + let mut request = blockchyp::CardMetadataRequest{ + test: true, + terminal_name: "Test Terminal".to_string(), + ..Default::default() + }; + let (response, err) = client.card_metadata(&mut request); + + if let Some(e) = err { + eprintln!("Unexpected error occurred: {:?}", e); + return Err(e) + } + + if response.success { + println!("success"); + } + + println!("Response: {:?}", response); + Ok(()) +} + +fn main() -> Result<(), Box> { + card_metadata_example()?; + println!("Example completed successfully!"); + Ok(()) +} diff --git a/src/blockchyp.rs b/src/blockchyp.rs index 567f59f..0b099c2 100644 --- a/src/blockchyp.rs +++ b/src/blockchyp.rs @@ -500,6 +500,63 @@ impl Client { (response, err) } + /// Retrieves card metadata. + pub fn card_metadata(&self, request: &mut CardMetadataRequest) -> (CardMetadataResponse, Option>) { + let mut response = CardMetadataResponse::default(); + let response_err: Result<(), Box>; + + if let Err(e) = self.populate_signature_options(request) { + return (response, Some(e)); + } + + if !request.terminal_name.is_empty() { + match self.resolve_terminal_route(&request.terminal_name) { + Ok(route) => { + if route.cloud_relay_enabled { + response_err = self.relay_request("/api/card-metadata", "POST", &request, &mut response, request.test, Some(request.timeout)); + } else { + let auth_request = TerminalCardMetadataRequest { + api_credentials: route.transient_credentials.clone().unwrap_or_default(), + request: request.clone(), + }; + response_err = self.terminal_request(route, "/api/card-metadata", "POST", &auth_request, &mut response, Some(request.timeout)); + } + } + Err(e) => { + if e.downcast_ref::().is_some() { + response.response_description = RESPONSE_UNKNOWN_TERMINAL.to_string(); + return (response, Some(e)) + } else { + return (response, Some(e)) + } + } + } + } else { + response_err = self.gateway_request("/api/card-metadata", "POST", request, &mut response, request.test, Some(request.timeout)); + } + + let err = if let Err(e) = response_err { + if let Some(reqwest_err) = e.downcast_ref::() { + if reqwest_err.is_timeout() { + response.response_description = RESPONSE_TIMED_OUT.to_string(); + } else { + response.response_description = e.to_string(); + } + } else { + response.response_description = e.to_string(); + } + Some(e) + } else { + None + }; + + if let Err(e) = self.handle_signature(&request, &mut response) { + return (response, Some(e)); + } + + (response, err) + } + /// Activates or recharges a gift card. pub fn gift_activate(&self, request: &mut GiftActivateRequest) -> (GiftActivateResponse, Option>) { let mut response = GiftActivateResponse::default(); diff --git a/src/models.rs b/src/models.rs index 6e341ce..769dd23 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1038,6 +1038,9 @@ pub struct TokenMetadataResponse { /// The token metadata for a given query. #[serde(rename = "token")] pub token: CustomerToken, + /// Details about a payment card derived from its BIN/IIN. + #[serde(rename = "cardMetadata", default)] + pub card_metadata: Option, } @@ -1315,8 +1318,8 @@ pub struct AuthorizationRequest { #[serde(rename = "roundingMode")] pub rounding_mode: Option, /// Details for HSA/FSA transactions. - #[serde(rename = "healthcare", default)] - pub healthcare: Option, + #[serde(rename = "healthcareMetadata", default)] + pub healthcare_metadata: Option, /// That the transaction should be a cryptocurrency transaction. Value should be a crypto /// currency code (ETH, BTC) or ANY to prompt the user to choose from supported /// cryptocurrencies. @@ -1354,6 +1357,233 @@ pub struct AuthorizationRequest { /// gateway and is not directly calculated. #[serde(rename = "passthroughSurcharge", default)] pub passthrough_surcharge: String, + /// Marks a transaction as HSA/FSA. + #[serde(rename = "healthcare", default)] + pub healthcare: bool, + /// The total amount to process as healthcare. + #[serde(rename = "healthcareTotal", default)] + pub healthcare_total: String, + /// The total amount to process as ebt. + #[serde(rename = "ebtTotal", default)] + pub ebt_total: String, + /// That this transaction will include a card metadata lookup. + #[serde(rename = "cardMetadataLookup", default)] + pub card_metadata_lookup: bool, + +} + +/// Essential information about a payment card derived from its BIN/IIN. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CardMetadata { + /// The brand or network of the card (e.g., Visa, Mastercard, Amex). + #[serde(rename = "cardBrand")] + pub card_brand: String, + /// The name of the financial institution that issued the card. + #[serde(rename = "issuerName")] + pub issuer_name: String, + /// Whether the card supports Level 3 processing for detailed transaction data. + #[serde(rename = "l3")] + pub l_3: bool, + /// Whether the card supports Level 2 processing for additional transaction data. + #[serde(rename = "l2")] + pub l_2: bool, + /// The general category or type of the card product. + #[serde(rename = "productType")] + pub product_type: String, + /// The specific name or designation of the card product. + #[serde(rename = "productName")] + pub product_name: String, + /// Whether the card is an Electronic Benefit Transfer (EBT) card. + #[serde(rename = "ebt")] + pub ebt: bool, + /// Whether the card is a debit card. + #[serde(rename = "debit")] + pub debit: bool, + /// Whether the card is a healthcare-specific payment card. + #[serde(rename = "healthcare")] + pub healthcare: bool, + /// Whether the card is a prepaid card. + #[serde(rename = "prepaid")] + pub prepaid: bool, + /// The geographical region associated with the card's issuer. + #[serde(rename = "region")] + pub region: String, + /// The country associated with the card's issuer. + #[serde(rename = "country")] + pub country: String, + +} + +/// Retrieves card metadata. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CardMetadataRequest { + /// The request timeout in seconds. + #[serde(rename = "timeout")] + pub timeout: i32, + /// Whether or not to route transaction to the test gateway. + #[serde(rename = "test")] + pub test: bool, + /// A user-assigned reference that can be used to recall or reverse transactions. + #[serde(rename = "transactionRef", default)] + pub transaction_ref: String, + /// That the transaction reference was autogenerated and should be ignored for the +/// purposes of duplicate detection. + #[serde(rename = "autogeneratedRef")] + pub autogenerated_ref: bool, + /// Defers the response to the transaction and returns immediately. Callers should +/// retrive the transaction result using the Transaction Status API. + #[serde(rename = "async")] + pub async_yo: bool, + /// Adds the transaction to the queue and returns immediately. Callers should retrive the +/// transaction result using the Transaction Status API. + #[serde(rename = "queue")] + pub queue: bool, + /// Whether or not the request should block until all cards have been removed from the card +/// reader. + #[serde(rename = "waitForRemovedCard", default)] + pub wait_for_removed_card: bool, + /// Override any in-progress transactions. + #[serde(rename = "force", default)] + pub force: bool, + /// An identifier from an external point of sale system. + #[serde(rename = "orderRef", default)] + pub order_ref: String, + /// The settlement account for merchants with split settlements. + #[serde(rename = "destinationAccount", default)] + pub destination_account: String, + /// Can include a code used to trigger simulated conditions for the purposes of testing and +/// certification. Valid for test merchant accounts only. + #[serde(rename = "testCase", default)] + pub test_case: String, + /// The payment token to be used for this transaction. This should be used for recurring +/// transactions. + #[serde(rename = "token", default)] + pub token: String, + /// Track 1 magnetic stripe data. + #[serde(rename = "track1", default)] + pub track_1: String, + /// Track 2 magnetic stripe data. + #[serde(rename = "track2", default)] + pub track_2: String, + /// The primary account number. We recommend using the terminal or e-commerce +/// tokenization libraries instead of passing account numbers in directly, as this would +/// put your application in PCI scope. + #[serde(rename = "pan", default)] + pub pan: String, + /// The ACH routing number for ACH transactions. + #[serde(rename = "routingNumber", default)] + pub routing_number: String, + /// The cardholder name. Only required if the request includes a primary account number or +/// track data. + #[serde(rename = "cardholderName", default)] + pub cardholder_name: String, + /// The card expiration month for use with PAN based transactions. + #[serde(rename = "expMonth", default)] + pub exp_month: String, + /// The card expiration year for use with PAN based transactions. + #[serde(rename = "expYear", default)] + pub exp_year: String, + /// The card CVV for use with PAN based transactions. + #[serde(rename = "cvv", default)] + pub cvv: String, + /// The cardholder address for use with address verification. + #[serde(rename = "address", default)] + pub address: String, + /// The cardholder postal code for use with address verification. + #[serde(rename = "postalCode", default)] + pub postal_code: String, + /// That the payment entry method is a manual keyed transaction. If this is true, no other +/// payment method will be accepted. + #[serde(rename = "manualEntry", default)] + pub manual_entry: bool, + /// The key serial number used for DUKPT encryption. + #[serde(rename = "ksn", default)] + pub ksn: String, + /// The encrypted pin block. + #[serde(rename = "pinBlock", default)] + pub pin_block: String, + /// Designates categories of cards: credit, debit, EBT. + #[serde(rename = "cardType", default)] + pub card_type: CardType, + /// Designates brands of payment methods: Visa, Discover, etc. + #[serde(rename = "paymentType", default)] + pub payment_type: String, + /// The name of the target payment terminal. + #[serde(rename = "terminalName", default)] + pub terminal_name: String, + /// Forces the terminal cloud connection to be reset while a transactions is in flight. +/// This is a diagnostic settings that can be used only for test transactions. + #[serde(rename = "resetConnection")] + pub reset_connection: bool, + /// Marks a transaction as HSA/FSA. + #[serde(rename = "healthcare", default)] + pub healthcare: bool, + +} + +/// The response to a card metadata request. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CardMetadataResponse { + /// Whether or not the request succeeded. + #[serde(rename = "success")] + pub success: bool, + /// The error, if an error occurred. + #[serde(rename = "error")] + pub error: String, + /// A narrative description of the transaction result. + #[serde(rename = "responseDescription")] + pub response_description: String, + /// The payment token, if the payment was enrolled in the vault. + #[serde(rename = "token", default)] + pub token: String, + /// The entry method for the transaction (CHIP, MSR, KEYED, etc). + #[serde(rename = "entryMethod", default)] + pub entry_method: String, + /// The card brand (VISA, MC, AMEX, DEBIT, etc). + #[serde(rename = "paymentType", default)] + pub payment_type: String, + /// Provides network level detail on how a transaction was routed, especially for debit +/// transactions. + #[serde(rename = "network", default)] + pub network: String, + /// Identifies the card association based on bin number. Used primarily used to indicate +/// the major logo on a card, even when debit transactions are routed on a different +/// network. + #[serde(rename = "logo", default)] + pub logo: String, + /// The masked primary account number. + #[serde(rename = "maskedPan", default)] + pub masked_pan: String, + /// The BlockChyp public key if the user presented a BlockChyp payment card. + #[serde(rename = "publicKey", default)] + pub public_key: String, + /// That the transaction did something that would put the system in PCI scope. + #[serde(rename = "ScopeAlert", default)] + pub scope_alert: bool, + /// The cardholder name. + #[serde(rename = "cardHolder", default)] + pub card_holder: String, + /// The card expiration month in MM format. + #[serde(rename = "expMonth", default)] + pub exp_month: String, + /// The card expiration year in YY format. + #[serde(rename = "expYear", default)] + pub exp_year: String, + /// Address verification results if address information was submitted. + #[serde(rename = "avsResponse")] + pub avs_response: AVSResponse, + /// Suggested receipt fields. + #[serde(rename = "receiptSuggestions")] + pub receipt_suggestions: ReceiptSuggestions, + /// Customer data, if any. Preserved for reverse compatibility. + #[serde(rename = "customer")] + pub customer: Option, + /// Customer data, if any. + #[serde(rename = "customers")] + pub customers: Option>, + /// Details about a payment card derived from its BIN/IIN. + #[serde(rename = "cardMetadata", default)] + pub card_metadata: Option, } @@ -1699,8 +1929,8 @@ pub struct RefundRequest { #[serde(rename = "resetConnection")] pub reset_connection: bool, /// Details for HSA/FSA transactions. - #[serde(rename = "healthcare", default)] - pub healthcare: Option, + #[serde(rename = "healthcareMetadata", default)] + pub healthcare_metadata: Option, /// Instructs the terminal to simulate a post auth chip rejection that would trigger an /// automatic reversal. #[serde(rename = "simulateChipRejection", default)] @@ -1718,6 +1948,9 @@ pub struct RefundRequest { /// Manually sets the MIT (Merchant Initiated Transaction) flag. #[serde(rename = "mit", default)] pub mit: bool, + /// That this transaction will include a card metadata lookup. + #[serde(rename = "cardMetadataLookup", default)] + pub card_metadata_lookup: bool, } @@ -2201,6 +2434,9 @@ pub struct EnrollRequest { /// recurring transaction. #[serde(rename = "subscription", default)] pub subscription: bool, + /// That this transaction will include a card metadata lookup. + #[serde(rename = "cardMetadataLookup", default)] + pub card_metadata_lookup: bool, } @@ -2307,6 +2543,9 @@ pub struct EnrollResponse { /// The hex encoded signature data. #[serde(rename = "sigFile", default)] pub sig_file: String, + /// Details about a payment card derived from its BIN/IIN. + #[serde(rename = "cardMetadata", default)] + pub card_metadata: Option, } @@ -2900,6 +3139,9 @@ pub struct AuthorizationResponse { /// The current status of a transaction. #[serde(rename = "status")] pub status: String, + /// Details about a payment card derived from its BIN/IIN. + #[serde(rename = "cardMetadata", default)] + pub card_metadata: Option, } @@ -5117,7 +5359,7 @@ pub struct UnlinkTokenRequest { /// Fields for HSA/FSA transactions. #[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Healthcare { +pub struct HealthcareMetadata { /// A list of healthcare categories in the transaction. #[serde(rename = "types")] pub types: Option>, @@ -7735,6 +7977,15 @@ pub struct TerminalAuthorizationRequest { pub request: AuthorizationRequest, } +/// Retrieves card metadata. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TerminalCardMetadataRequest { + #[serde(flatten)] + pub api_credentials: APICredentials, + #[serde(rename = "request")] + pub request: CardMetadataRequest, +} + /// A request for the remaining balance on a payment type. #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct TerminalBalanceRequest {