feat(content): seller-picked payment methods + music always in the bottom bar + video PiP

- Paid sharing: AccessControl::Paid gains an accepted-methods list
  (lightning/onchain/ecash/fedimint; empty = all, back-compat). Sellers pick
  methods in ShareModal, gated on what the node can actually receive (LND
  running/channel open, ecash wallet, fedimint joined) with an ⓘ that
  explains exactly how to enable a missing rail. Enforced server-side (the
  invoice/onchain mints refuse non-accepted methods; the serve gate only
  honors tokens/hashes for accepted rails) and the buyer's pay modal only
  offers what the seller accepts.
- Purchased music: the two remaining lightbox paths now use the bottom-bar
  player — the immediate post-ecash-purchase viewer and the Paid Files
  tab's window.open.
- Picture-in-picture buttons on the peer video player and the cloud media
  lightbox (utils/pip.ts; Chromium/Safari, no-op elsewhere).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-23 16:15:11 -04:00
co-authored by Claude Fable 5
parent d5fc3d01a4
commit f72d4b92ac
8 changed files with 299 additions and 23 deletions
+20 -2
View File
@@ -188,7 +188,7 @@ impl ApiHandler {
}
};
let price_sats = match &item.access {
content_server::AccessControl::Paid { price_sats } => *price_sats,
content_server::AccessControl::Paid { price_sats, .. } => *price_sats,
_ => {
// Not a paid item — no invoice to issue.
return Ok(build_response(
@@ -198,6 +198,13 @@ impl ApiHandler {
));
}
};
if !content_server::method_accepted(&item.access, "lightning") {
return Ok(build_response(
StatusCode::BAD_REQUEST,
"application/json",
hyper::Body::from(r#"{"error":"The seller does not accept Lightning for this item"}"#),
));
}
let memo = format!("Archipelago peer file {content_id}");
match self
@@ -315,7 +322,18 @@ impl ApiHandler {
.unwrap_or_default();
let price_sats = match catalog.items.iter().find(|i| i.id == content_id) {
Some(i) => match &i.access {
content_server::AccessControl::Paid { price_sats } => *price_sats,
content_server::AccessControl::Paid { price_sats, .. } => {
if !content_server::method_accepted(&i.access, "onchain") {
return Ok(build_response(
StatusCode::BAD_REQUEST,
"application/json",
hyper::Body::from(
r#"{"error":"The seller does not accept on-chain payment for this item"}"#,
),
));
}
*price_sats
}
_ => {
return Ok(build_response(
StatusCode::BAD_REQUEST,
+18 -1
View File
@@ -179,7 +179,24 @@ impl RpcHandler {
if price == 0 {
return Err(anyhow::anyhow!("Paid content requires price_sats > 0"));
}
AccessControl::Paid { price_sats: price }
// Optional list of payment methods the sharer accepts.
// Absent/empty = all methods (backward compatible).
const KNOWN_METHODS: [&str; 4] = ["lightning", "onchain", "ecash", "fedimint"];
let accepted: Vec<String> = params
.get("accepted_methods")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|m| m.as_str())
.filter(|m| KNOWN_METHODS.contains(m))
.map(str::to_string)
.collect()
})
.unwrap_or_default();
AccessControl::Paid {
price_sats: price,
accepted,
}
}
_ => return Err(anyhow::anyhow!("Invalid access type: {}", access_type)),
};