ui: checkout pays seller by seller only (drop unified checkout), per-seller auto-convert

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 11:25:06 +08:00
1 parent 2d40df81bd
commit 00182c4ce6
4 files changed
+48 -94

No files matched your search

+44 -74
View File
@@ -1459,13 +1459,13 @@ namespace Decentralized
private sealed class CartLine { public int Seller; public BmGood Good; public int Qty; }
private static readonly List<CartLine> _cart = new List<CartLine>();
private static bool _checkout, _ckEach;
private static string _ckCoin;
private static bool _checkout;
private static readonly Dictionary<int, string> _ckSwap = new Dictionary<int, string>();
private static readonly Dictionary<int, PayMode> _ckMode = new Dictionary<int, PayMode>();
private static readonly Dictionary<int, string> _ckGroupCoin = new Dictionary<int, string>();
/// <summary>读档或换局时清空购物车(货架对象已经不是同一批了)。</summary>
public static void ClearCart() { _cart.Clear(); _checkout = false; _ckMode.Clear(); _ckGroupCoin.Clear(); }
public static void ClearCart() { _cart.Clear(); _checkout = false; _ckMode.Clear(); _ckGroupCoin.Clear(); _ckSwap.Clear(); }
private static int StockOf(BmGood g) => g is BmOffer o ? o.Qty : ((BmListing)g).Qty;
@@ -1551,7 +1551,7 @@ namespace Decentralized
b.AddSeparator();
}
/// <summary>结算页:左栏是购物车详情(按商家分组,可增减数量、删除),右栏是付款选项(统一结算,或逐家生成账单)。</summary>
/// <summary>结算页:左栏是购物车详情(按商家分组,可增减数量、删除),右栏是付款:每家商家收款地址不同,所以逐家付款。</summary>
private static void CheckoutPage(CustomUIBuilder b, SaveData d)
{
CartValidate(d);
@@ -1594,42 +1594,13 @@ namespace Decentralized
{
r.AddHeader(L.T("bpay.h"));
if (_cart.Count == 0) { r.AddButton(L.T("cart.back"), () => { _checkout = false; Open(); }); return; }
r.BeginRow(4f);
PledgerSkin.Pick(r, L.T("cart.all"), !_ckEach, () => { _ckEach = false; Open(); }, _picked);
PledgerSkin.Pick(r, L.T("cart.each"), _ckEach, () => { _ckEach = true; Open(); }, _picked);
r.End();
if (!_ckEach) UnifiedPay(r, d, w, groups.Select(g => g.Key).ToList());
else foreach (var g in groups) GroupPay(r, d, w, g.Key);
foreach (var g in groups) GroupPay(r, d, w, g.Key);
r.AddSpace(4f);
r.AddButton(L.T("cart.back"), () => { _checkout = false; _message = null; Open(); });
});
}
/// <summary>统一结算:选一种币,一次合约签名付清全部商家;商家不收这种币的部分按 8% 滑点自动换。</summary>
private static void UnifiedPay(CustomUIBuilder r, SaveData d, Wallet w, List<int> sellers)
{
var coins = new[] { Assets.PSC, Assets.CRT, Assets.CRA, Assets.CRC, Assets.SPARK, Assets.Ponero }.Where(a => w.Avail(a) > 1e-9 && Session.Price(a) > 0).ToList();
if (coins.Count == 0) { r.AddLabel(L.T("cart.nocoin"), "cart_nocoin_dim"); return; }
if (_ckCoin == null || !coins.Contains(_ckCoin)) _ckCoin = coins[0];
r.AddLabel(L.T("bpay.coin2"), "bpay_coin_dim");
r.BeginRow(4f);
foreach (var a in coins) { string asset = a; PledgerSkin.Pick(r, asset, asset == _ckCoin, () => { _ckCoin = asset; Open(); }, _picked); }
r.End();
double credits = 0, swapped = 0;
foreach (int s in sellers)
{
double c = GroupCredits(d, s);
bool direct = AcceptsOf(d, s).Contains(_ckCoin);
credits += direct ? c : c * (1 + Tuning.BmAutoSwapSlip);
if (!direct) swapped += c;
}
double need = WalletWindow.RoundUp(credits / Session.Price(_ckCoin));
r.AddLabel(L.T("bpay.need", WalletWindow.Amt(need), _ckCoin, Fmt.Num(w.Avail(_ckCoin))), "bpay_need_dim");
if (swapped > 0) r.AddLabel(L.T("cart.swapped", Fmt.Credits(swapped), (Tuning.BmAutoSwapSlip * 100).ToString("0", CultureInfo.InvariantCulture)), "cart_swapped_dim");
r.AddButton(L.T("cart.pay"), () => CheckoutAll(_ckCoin, need));
}
/// <summary>逐家付款:每家商家单独选它收的币,生成账单,在钱包里转账。</summary>
/// <summary>逐家付款:这家商家收的币里选一种,生成账单在钱包里转账;也可以用钱包里别的币自动换币(多付滑点)一次签名付清。</summary>
private static void GroupPay(CustomUIBuilder r, SaveData d, Wallet w, int seller)
{
var acc = AcceptsOf(d, seller);
@@ -1649,6 +1620,44 @@ namespace Decentralized
_checkout = false;
BeginPay(AddressOf(d, seller), summary, coin, credits, pw => { var r2 = ExecGroup(seller, lines, mode, coin, pw); Finish(CartMessage(r2.items, r2.failed)); });
});
double slipped = credits * (1 + Tuning.BmAutoSwapSlip);
var sources = w.Balances.Keys.Where(a => !acc.Contains(a) && a != Assets.Ponero && Session.Price(a) > 0 && w.Avail(a) * Session.Price(a) >= slipped).ToList();
if (sources.Count == 0) return;
if (!_ckSwap.TryGetValue(seller, out var src) || !sources.Contains(src)) src = _ckSwap[seller] = sources[0];
r.AddLabel(L.T("bpay.swap.h", coin), "bpay_swap_dim");
r.BeginRow(4f);
foreach (var s in sources) { string sc = s; PledgerSkin.Pick(r, sc, sc == src, () => { _ckSwap[seller] = sc; Open(); }, _picked); }
r.End();
double needSrc = WalletWindow.RoundUp(slipped / Session.Price(src));
r.AddLabel(L.T("bpay.swap.need", WalletWindow.Amt(needSrc), src, (Tuning.BmAutoSwapSlip * 100).ToString("0", CultureInfo.InvariantCulture)), "bpay_swap_need_dim");
r.AddButton(L.T("bpay.swap.btn"), () => SwapGroup(seller, coin, src, credits, needSrc));
}
/// <summary>自动换币并结算一家商家:一次合约调用,先把别的币按滑点换成这家收的币,再下单。</summary>
private static void SwapGroup(int seller, string coin, string src, double credits, double needSrc)
{
var d = Session.Data;
var lines = _cart.Where(l => l.Seller == seller).ToList();
var mode = ModeOf(d, seller);
string summary = ShopName(d, seller) + " ×" + lines.Sum(l => l.Qty);
_checkout = false;
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
Contract = true, Asset = src, Amount = needSrc, Spend = needSrc,
Summary = L.T("bpay.swap.sum", summary, src, WalletWindow.Amt(needSrc), coin),
Execute = pw =>
{
if (pw.Avail(src) + 1e-9 < needSrc) { Finish(L.T("msg.balance")); return; }
double got = credits / Session.Price(coin) * 1.002;
pw.Add(src, -needSrc); pw.Add(coin, got); pw.RecordBuy(coin, got, credits);
CandleChart.Trade(src, credits); CandleChart.Trade(coin, credits);
Log.Info($"自动换币:{needSrc:0.####} {src} → {got:0.####} {coin}({credits:0} 信用点等值)");
var r2 = ExecGroup(seller, lines, mode, coin, pw);
Finish(CartMessage(r2.items, r2.failed));
},
});
}
private static void Finish(string message)
@@ -1683,45 +1692,6 @@ namespace Decentralized
private static string CartMessage(int items, int failed) => failed == 0 ? L.T("cart.done", items) : L.T("cart.partial", items, failed);
/// <summary>统一结算:一次合约签名;每家商家不收所选币时,先按滑点换成它收的币再付。</summary>
private static void CheckoutAll(string coin, double need)
{
var d = Session.Data;
var lines = _cart.ToList();
_checkout = false;
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
Contract = true, Asset = coin, Amount = need, Spend = need,
Summary = L.T("cart.sum", lines.Sum(l => l.Qty), coin, WalletWindow.Amt(need)),
Execute = pw =>
{
var msgs = new List<string>(); int doneItems = 0, doneFailed = 0;
foreach (var g in lines.GroupBy(l => l.Seller))
{
int seller = g.Key;
var acc = AcceptsOf(d, seller);
double credits = g.Sum(l => LineCredits(d, l));
string asset = acc.Contains(coin) ? coin : acc[0];
if (asset != coin)
{
double src = credits * (1 + Tuning.BmAutoSwapSlip) / Session.Price(coin);
if (pw.Avail(coin) + 1e-9 < src) { msgs.Add(L.T("cart.short", ShopName(d, seller))); doneFailed += g.Sum(l => l.Qty); continue; }
double got = credits / Session.Price(asset) * 1.002;
pw.Add(coin, -src); pw.Add(asset, got); pw.RecordBuy(asset, got, credits);
CandleChart.Trade(coin, credits); CandleChart.Trade(asset, credits);
Log.Info($"自动换币:{src:0.####} {coin} → {got:0.####} {asset}({credits:0} 信用点等值)");
}
var r3 = ExecGroup(seller, g.ToList(), ModeOf(d, seller), asset, pw);
doneItems += r3.items; doneFailed += r3.failed;
}
msgs.Insert(0, CartMessage(doneItems, doneFailed));
Finish(string.Join(" ", msgs));
},
});
}
private static string MerchantLabel(BmSeller s, bool shortName = false) => L.T("merchant." + s.Type) + " " + (shortName ? ChainAddress.Short(s.Name) : s.Name);
private static int _merchant = -1;