backbone_accounting/presentation/http/
emv_qr_handler.rs1use std::sync::Arc;
21
22use axum::{
23 extract::{Query, State},
24 http::StatusCode,
25 response::IntoResponse,
26 routing::{get, put},
27 Json, Router,
28};
29use rust_decimal::Decimal;
30use serde::{Deserialize, Serialize};
31use uuid::Uuid;
32
33use crate::application::service::emv_qr_service::{
34 EmvPayloadAck, EmvQrConfigAck, EmvQrConfigInput, EmvQrService, EmvQrServiceError,
35};
36
37#[derive(Debug, Deserialize)]
38pub struct EmvQrConfigBody {
39 #[serde(default)]
40 pub bank_account_id: Option<Uuid>,
41 pub merchant_name: String,
42 pub merchant_city: String,
43 #[serde(default = "default_country")]
44 pub country: String,
45 pub mcc: String,
46 #[serde(default = "default_gui")]
47 pub gui: String,
48 pub merchant_identifier: String,
49 #[serde(default = "default_currency")]
50 pub currency: String,
51 #[serde(default = "default_initiation")]
52 pub initiation: String,
53}
54
55fn default_country() -> String {
56 "ID".into()
57}
58fn default_gui() -> String {
59 "ID.CO.QRIS.WWW".into()
60}
61fn default_currency() -> String {
62 "IDR".into()
63}
64fn default_initiation() -> String {
65 "11".into()
66}
67
68#[derive(Debug, Deserialize)]
69pub struct EmvQrPayloadQuery {
70 #[serde(default)]
71 pub bank_account_id: Option<Uuid>,
72 pub amount: Option<Decimal>,
74 pub currency: Option<String>,
76 pub reference: Option<String>,
78}
79
80#[derive(Debug, Serialize)]
81struct ErrorBody {
82 error: String,
83 message: String,
84}
85
86fn error_response(e: &EmvQrServiceError) -> axum::response::Response {
87 let status = StatusCode::from_u16(e.http_status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
88 (
89 status,
90 Json(ErrorBody {
91 error: e.code().to_string(),
92 message: e.to_string(),
93 }),
94 )
95 .into_response()
96}
97
98async fn upsert_config(
108 State(service): State<Arc<EmvQrService>>,
109 Json(body): Json<EmvQrConfigBody>,
110) -> impl IntoResponse {
111 let input = EmvQrConfigInput {
112 bank_account_id: body.bank_account_id,
113 merchant_name: body.merchant_name,
114 merchant_city: body.merchant_city,
115 country: body.country,
116 mcc: body.mcc,
117 gui: body.gui,
118 merchant_identifier: body.merchant_identifier,
119 currency: body.currency,
120 initiation: body.initiation,
121 };
122 match service.upsert_config(input).await {
123 Ok(ack) => (
124 StatusCode::OK,
125 Json(EmvQrConfigAck {
126 config_id: ack.config_id,
127 bank_account_id: ack.bank_account_id,
128 }),
129 )
130 .into_response(),
131 Err(e) => error_response(&e),
132 }
133}
134
135async fn payload(
136 State(service): State<Arc<EmvQrService>>,
137 Query(q): Query<EmvQrPayloadQuery>,
138) -> impl IntoResponse {
139 match service
140 .invoice_payload(
141 q.bank_account_id,
142 q.amount,
143 q.currency,
144 q.reference,
145 )
146 .await
147 {
148 Ok(ack) => (StatusCode::OK, Json(EmvPayloadAck {
149 payload: ack.payload,
150 currency: ack.currency,
151 initiation_method: ack.initiation_method,
152 }))
153 .into_response(),
154 Err(e) => error_response(&e),
155 }
156}
157
158pub fn create_emv_qr_routes(service: Arc<EmvQrService>) -> Router {
162 Router::new()
163 .route("/accounting/emv-qr/config", put(upsert_config))
164 .route("/accounting/emv-qr/payload", get(payload))
165 .with_state(service)
166}