use serde::{Deserialize, Serialize};
use crate::models::common::Money;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RefundRequest {
#[serde(rename = "paymentId")]
pub payment_id: String,
#[serde(rename = "trxId")]
pub trx_id: String,
#[serde(rename = "refundAmount")]
pub refund_amount: Money,
pub sku: String,
pub reason: String,
}
impl RefundRequest {
#[must_use]
pub fn new(
payment_id: impl Into<String>,
trx_id: impl Into<String>,
refund_amount: Money,
sku: impl Into<String>,
reason: impl Into<String>,
) -> Self {
Self {
payment_id: payment_id.into(),
trx_id: trx_id.into(),
refund_amount,
sku: sku.into(),
reason: reason.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RefundResponse {
#[serde(rename = "paymentID", default)]
pub payment_id: String,
#[serde(rename = "trxID", default)]
pub trx_id: String,
#[serde(rename = "refundTrxID", default)]
pub refund_trx_id: String,
#[serde(rename = "refundAmount", default)]
pub refund_amount: Money,
#[serde(default)]
pub sku: String,
#[serde(default)]
pub reason: String,
#[serde(rename = "maxRefundableAmount", default)]
pub max_refundable_amount: Money,
#[serde(rename = "organizationShortCode", default)]
pub organization_short_code: String,
#[serde(rename = "transactionStatus", default)]
pub transaction_status: String,
#[serde(rename = "initiationTime", default)]
pub initiation_time: String,
#[serde(rename = "completedTime", default)]
pub completed_time: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RefundStatusRequest {
#[serde(rename = "paymentID")]
pub payment_id: String,
#[serde(rename = "trxID")]
pub trx_id: String,
}
impl RefundStatusRequest {
#[must_use]
pub fn new(payment_id: impl Into<String>, trx_id: impl Into<String>) -> Self {
Self {
payment_id: payment_id.into(),
trx_id: trx_id.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RefundStatusResponse {
#[serde(rename = "paymentID", default)]
pub payment_id: String,
#[serde(rename = "trxID", default)]
pub trx_id: String,
#[serde(rename = "refundTrxID", default)]
pub refund_trx_id: String,
#[serde(rename = "refundAmount", default)]
pub refund_amount: Money,
#[serde(default)]
pub sku: String,
#[serde(default)]
pub reason: String,
#[serde(rename = "maxRefundableAmount", default)]
pub max_refundable_amount: Money,
#[serde(rename = "transactionStatus", default)]
pub transaction_status: String,
#[serde(rename = "initiationTime", default)]
pub initiation_time: String,
#[serde(rename = "completedTime", default)]
pub completed_time: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn refund_request_serialises() {
let req = RefundRequest::new(
"TR0001",
"8A00ABCD",
Money::bdt("25.00"),
"sku-1",
"customer-return",
);
let json: serde_json::Value = serde_json::to_value(&req).unwrap();
assert_eq!(json["paymentId"], "TR0001");
assert_eq!(json["trxId"], "8A00ABCD");
assert_eq!(json["refundAmount"], "25.00");
assert_eq!(json["sku"], "sku-1");
assert_eq!(json["reason"], "customer-return");
}
#[test]
fn refund_response_round_trip() {
let body = r#"{
"paymentID": "TR0001",
"trxID": "8A00ABCD",
"refundTrxID": "8B00EFGH",
"refundAmount": "25.00",
"sku": "sku-1",
"reason": "test",
"maxRefundableAmount": "75.00",
"transactionStatus": "Completed",
"initiationTime": "2026-06-22T10:00:00:000 GMT+06:00",
"completedTime": "2026-06-22T10:01:00:000 GMT+06:00"
}"#;
let resp: RefundResponse = serde_json::from_str(body).unwrap();
assert_eq!(resp.refund_trx_id, "8B00EFGH");
assert_eq!(resp.refund_amount.as_str(), "25.00");
assert_eq!(resp.max_refundable_amount.as_str(), "75.00");
let json = serde_json::to_string(&resp).unwrap();
let back: RefundResponse = serde_json::from_str(&json).unwrap();
assert_eq!(back.refund_trx_id, resp.refund_trx_id);
assert_eq!(back.refund_amount.as_str(), resp.refund_amount.as_str());
}
#[test]
fn refund_status_request_serialises() {
let req = RefundStatusRequest::new("TR0001", "8A00ABCD");
let json: serde_json::Value = serde_json::to_value(&req).unwrap();
assert_eq!(json["paymentID"], "TR0001");
assert_eq!(json["trxID"], "8A00ABCD");
}
#[test]
fn refund_status_response_parses_minimal() {
let body = r#"{
"paymentID": "TR0001",
"trxID": "8A00ABCD",
"refundTrxID": "8B00EFGH",
"refundAmount": "25.00",
"transactionStatus": "Completed"
}"#;
let resp: RefundStatusResponse = serde_json::from_str(body).unwrap();
assert_eq!(resp.refund_trx_id, "8B00EFGH");
assert_eq!(resp.transaction_status, "Completed");
}
use proptest::prelude::*;
proptest! {
#[test]
fn refund_request_roundtrip(
payment_id in ".*",
trx_id in ".*",
amount in ".*",
sku in ".*",
reason in ".*",
) {
let req = RefundRequest::new(
payment_id,
trx_id,
Money::new(amount),
sku,
reason,
);
let json = serde_json::to_string(&req).unwrap();
let back: RefundRequest = serde_json::from_str(&json).unwrap();
let json2 = serde_json::to_string(&back).unwrap();
prop_assert_eq!(json, json2);
}
}
}