Skip to content

Commit 5430ed1

Browse files
committed
feat: add merchant sessions history endpoint
GET /api/merchants/me/sessions returns all sessions for the authenticated merchant with balance, refund info, and status.
1 parent 6e1e3a7 commit 5430ed1

3 files changed

Lines changed: 83 additions & 0 deletions

File tree

src/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
4646
.route("/me/delete", web::post().to(delete_account))
4747
.route("/me/webhooks", web::get().to(auth::my_webhooks))
4848
.route("/me/x402/history", web::get().to(x402::history))
49+
.route("/me/sessions", web::get().to(sessions::history))
4950
)
5051
.service(
5152
web::scope("/auth")

src/api/sessions.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,61 @@ pub async fn close(
187187
}
188188
}
189189

190+
pub async fn history(
191+
req: HttpRequest,
192+
pool: web::Data<SqlitePool>,
193+
) -> HttpResponse {
194+
let merchant = match crate::api::auth::resolve_session(&req, &pool).await {
195+
Some(m) => m,
196+
None => {
197+
return HttpResponse::Unauthorized().json(serde_json::json!({
198+
"error": "Not authenticated"
199+
}));
200+
}
201+
};
202+
203+
match crate::sessions::list_for_merchant(pool.get_ref(), &merchant.id).await {
204+
Ok(sessions) => {
205+
let items: Vec<_> = sessions.iter().map(|s| {
206+
let balance_used = s.balance_zatoshis - s.balance_remaining;
207+
let mut obj = serde_json::json!({
208+
"id": s.id,
209+
"deposit_txid": s.deposit_txid,
210+
"balance_zatoshis": s.balance_zatoshis,
211+
"balance_remaining": s.balance_remaining,
212+
"cost_per_request": s.cost_per_request,
213+
"requests_made": s.requests_made,
214+
"balance_used": balance_used,
215+
"status": s.status,
216+
"expires_at": s.expires_at,
217+
"created_at": s.created_at,
218+
"closed_at": s.closed_at,
219+
});
220+
221+
if let Some(ref addr) = s.refund_address {
222+
if s.balance_remaining > 0 && (s.status == "closed" || s.status == "depleted" || s.status == "expired") {
223+
let refund_zec = s.balance_remaining as f64 / 100_000_000.0;
224+
obj.as_object_mut().unwrap().insert("refund".to_string(), serde_json::json!({
225+
"address": addr,
226+
"amount_zatoshis": s.balance_remaining,
227+
"amount_zec": refund_zec,
228+
}));
229+
}
230+
}
231+
obj
232+
}).collect();
233+
234+
HttpResponse::Ok().json(serde_json::json!({ "sessions": items }))
235+
}
236+
Err(e) => {
237+
tracing::error!(error = %e, "Failed to list sessions");
238+
HttpResponse::InternalServerError().json(serde_json::json!({
239+
"error": "Internal error"
240+
}))
241+
}
242+
}
243+
}
244+
190245
pub async fn validate(
191246
req: HttpRequest,
192247
pool: web::Data<SqlitePool>,

src/sessions/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,33 @@ pub async fn get_summary(pool: &SqlitePool, session_id: &str) -> Result<Option<S
204204
}))
205205
}
206206

207+
/// List sessions for a merchant (dashboard view)
208+
pub async fn list_for_merchant(pool: &SqlitePool, merchant_id: &str) -> Result<Vec<Session>> {
209+
let rows = sqlx::query_as::<_, (String, String, String, String, i64, i64, i64, i64, Option<String>, String, String, String, Option<String>)>(
210+
"SELECT id, merchant_id, deposit_txid, bearer_token, balance_zatoshis, balance_remaining, cost_per_request, requests_made, refund_address, status, expires_at, created_at, closed_at
211+
FROM sessions WHERE merchant_id = ? ORDER BY created_at DESC LIMIT 100"
212+
)
213+
.bind(merchant_id)
214+
.fetch_all(pool)
215+
.await?;
216+
217+
Ok(rows.into_iter().map(|r| Session {
218+
id: r.0,
219+
merchant_id: r.1,
220+
deposit_txid: r.2,
221+
bearer_token: r.3,
222+
balance_zatoshis: r.4,
223+
balance_remaining: r.5,
224+
cost_per_request: r.6,
225+
requests_made: r.7,
226+
refund_address: r.8,
227+
status: r.9,
228+
expires_at: r.10,
229+
created_at: r.11,
230+
closed_at: r.12,
231+
}).collect())
232+
}
233+
207234
/// Check if a deposit txid has already been used for a session
208235
pub async fn txid_already_used(pool: &SqlitePool, txid: &str) -> bool {
209236
sqlx::query_scalar::<_, i64>(

0 commit comments

Comments
 (0)