Skip to main content

Payment Terminals

Setup, configuration, and integration for card readers and payment terminal devices.

Stripe Terminal

Configuration

# config/hardware/card_readers.yaml
stripe_terminal:
location_id: "tml_location_xxx"
readers:
- id: "reader_001"
name: "Counter 1"
serial: "STRM12345"
type: "bbpos_wisepos_e"

- id: "reader_002"
name: "Counter 2"
serial: "STRM12346"
type: "stripe_m2"

Stripe Terminal Integration

// src/hardware/card_readers/stripe.rs
use stripe_terminal::{Reader, PaymentIntent};

pub struct StripeTerminalService {
location_id: String,
}

impl StripeTerminalService {
pub async fn discover_readers(&self) -> Result<Vec<ReaderInfo>> {
let readers = stripe::Reader::list(&stripe::ListReaders {
location: Some(&self.location_id),
..Default::default()
}).await?;

Ok(readers.data.into_iter().map(|r| ReaderInfo {
id: r.id.to_string(),
serial: r.serial_number,
model: r.device_type,
status: map_status(&r.status),
battery_level: r.battery_level,
}).collect())
}

pub async fn collect_payment(
&self,
reader_id: &str,
payment_intent_id: &str,
) -> Result<PaymentResult> {
// Process payment on reader
let result = stripe::PaymentIntent::process_payment(
payment_intent_id,
&stripe::ProcessPaymentParams {
reader: reader_id.to_string(),
..Default::default()
}
).await?;

Ok(PaymentResult {
status: map_payment_status(&result.status),
payment_intent_id: result.id.to_string(),
amount: result.amount,
card_brand: result.charges.data[0].payment_method_details
.card.as_ref().map(|c| c.brand.clone()),
last4: result.charges.data[0].payment_method_details
.card.as_ref().map(|c| c.last4.clone()),
})
}

pub async fn cancel_collect(&self, reader_id: &str) -> Result<()> {
stripe::Reader::cancel_action(reader_id).await?;
Ok(())
}

pub async fn set_display(
&self,
reader_id: &str,
cart: &Cart,
) -> Result<()> {
stripe::Reader::set_reader_display(reader_id, &stripe::SetReaderDisplayParams {
type_: "cart",
cart: Some(stripe::Cart {
line_items: cart.items.iter().map(|i| stripe::CartLineItem {
description: i.name.clone(),
amount: (i.total * 100.0) as i64,
quantity: i.quantity as u32,
}).collect(),
tax: Some((cart.tax * 100.0) as i64),
total: (cart.total * 100.0) as i64,
currency: "usd".to_string(),
}),
}).await?;

Ok(())
}
}

Square Terminal

// src/hardware/card_readers/square.rs
pub struct SquareTerminalService {
access_token: String,
location_id: String,
}

impl SquareTerminalService {
pub async fn create_checkout(
&self,
device_id: &str,
amount: Money,
order_id: &str,
) -> Result<TerminalCheckout> {
let client = reqwest::Client::new();

let response = client
.post("https://connect.squareup.com/v2/terminals/checkouts")
.header("Authorization", format!("Bearer {}", self.access_token))
.json(&json!({
"idempotency_key": Uuid::new_v4().to_string(),
"checkout": {
"amount_money": {
"amount": amount.cents,
"currency": amount.currency
},
"device_options": {
"device_id": device_id
},
"reference_id": order_id
}
}))
.send()
.await?;

response.json().await
}

pub async fn get_checkout_status(
&self,
checkout_id: &str,
) -> Result<CheckoutStatus> {
let client = reqwest::Client::new();

let response = client
.get(&format!(
"https://connect.squareup.com/v2/terminals/checkouts/{}",
checkout_id
))
.header("Authorization", format!("Bearer {}", self.access_token))
.send()
.await?;

let checkout: TerminalCheckout = response.json().await?;
Ok(checkout.status)
}
}