120 lines
3.1 KiB
Rust
120 lines
3.1 KiB
Rust
//! Midtrans payment provider — Core API (v2/charge).
|
|||
|
|
//!
|
||
|
|
//! Gateway-agnostic design: the payments service calls into this module when
|
||
|
|
//! Midtrans credentials are configured (`MIDTRANS_*` env). It returns the
|
||
|
|
//! provider-specific reference that gets persisted into `app_payments.external_ref`:
|
||
|
|
//! a VA number for `va`, or the QR string payload for `qris`.
|
||
|
|
|
||
|
|
use imphnen_utils::AppError;
|
||
|
|
use serde_json::json;
|
||
|
|
|
||
|
|
/// Sandbox vs production endpoint. Sandbox is the default and safe for demo.
|
||
|
|
fn charge_url() -> &'static str {
|
||
|
|
if std::env::var("RUST_ENV").as_deref() == Ok("production") {
|
||
|
|
"https://api.midtrans.com/v2/charge"
|
||
|
|
} else {
|
||
|
|
"https://api.sandbox.midtrans.com/v2/charge"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a bank-transfer (Virtual Account) charge via Midtrans Core API.
|
||
|
|
///
|
||
|
|
/// Returns the VA number to display to the mentee.
|
||
|
|
pub async fn create_va_charge(
|
||
|
|
order_id: &str,
|
||
|
|
gross_amount: i64,
|
||
|
|
bank: &str,
|
||
|
|
server_key: &str,
|
||
|
|
) -> Result<String, AppError> {
|
||
|
|
let client = reqwest::Client::new();
|
||
|
|
let body = json!({
|
||
|
|
"payment_type": "bank_transfer",
|
||
|
|
"transaction_details": {
|
||
|
|
"order_id": order_id,
|
||
|
|
"gross_amount": gross_amount,
|
||
|
|
},
|
||
|
|
"bank_transfer": {
|
||
|
|
"bank": bank,
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
let resp = client
|
||
|
|
.post(charge_url())
|
||
|
|
.basic_auth(server_key, Some(""))
|
||
|
|
.json(&body)
|
||
|
|
.send()
|
||
|
|
.await
|
||
|
|
.map_err(|e| AppError::InternalServerError(format!("Midtrans request failed: {e}")))?;
|
||
|
|
|
||
|
|
let status = resp.status();
|
||
|
|
let payload: serde_json::Value = resp
|
||
|
|
.json()
|
||
|
|
.await
|
||
|
|
.map_err(|e| AppError::InternalServerError(format!("Midtrans response parse failed: {e}")))?;
|
||
|
|
|
||
|
|
if !status.is_success() {
|
||
|
|
return Err(AppError::InternalServerError(format!(
|
||
|
|
"Midtrans charge error ({}): {}",
|
||
|
|
status,
|
||
|
|
payload["status_message"]
|
||
|
|
.as_str()
|
||
|
|
.unwrap_or("unknown error")
|
||
|
|
)));
|
||
|
|
}
|
||
|
|
|
||
|
|
payload["va_numbers"][0]["va_number"]
|
||
|
|
.as_str()
|
||
|
|
.map(|s| s.to_string())
|
||
|
|
.ok_or_else(|| AppError::InternalServerError("Midtrans response missing va_number".into()))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Create a QRIS charge via Midtrans Core API.
|
||
|
|
///
|
||
|
|
/// Returns the QR string payload (renderable as a QR code).
|
||
|
|
pub async fn create_qris_charge(
|
||
|
|
order_id: &str,
|
||
|
|
gross_amount: i64,
|
||
|
|
server_key: &str,
|
||
|
|
) -> Result<String, AppError> {
|
||
|
|
let client = reqwest::Client::new();
|
||
|
|
let body = json!({
|
||
|
|
"payment_type": "qris",
|
||
|
|
"transaction_details": {
|
||
|
|
"order_id": order_id,
|
||
|
|
"gross_amount": gross_amount,
|
||
|
|
},
|
||
|
|
"qris": {
|
||
|
|
"acquirer": "gopay",
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
let resp = client
|
||
|
|
.post(charge_url())
|
||
|
|
.basic_auth(server_key, Some(""))
|
||
|
|
.json(&body)
|
||
|
|
.send()
|
||
|
|
.await
|
||
|
|
.map_err(|e| AppError::InternalServerError(format!("Midtrans request failed: {e}")))?;
|
||
|
|
|
||
|
|
let status = resp.status();
|
||
|
|
let payload: serde_json::Value = resp
|
||
|
|
.json()
|
||
|
|
.await
|
||
|
|
.map_err(|e| AppError::InternalServerError(format!("Midtrans response parse failed: {e}")))?;
|
||
|
|
|
||
|
|
if !status.is_success() {
|
||
|
|
return Err(AppError::InternalServerError(format!(
|
||
|
|
"Midtrans charge error ({}): {}",
|
||
|
|
status,
|
||
|
|
payload["status_message"]
|
||
|
|
.as_str()
|
||
|
|
.unwrap_or("unknown error")
|
||
|
|
)));
|
||
|
|
}
|
||
|
|
|
||
|
|
payload["qr_string"]
|
||
|
|
.as_str()
|
||
|
|
.map(|s| s.to_string())
|
||
|
|
.ok_or_else(|| AppError::InternalServerError("Midtrans response missing qr_string".into()))
|
||
|
|
}
|