This commit is contained in:
parent
5d3a3adb86
commit
1a38cfe5d7
7 changed files with 169 additions and 17 deletions
|
|
@ -115,6 +115,7 @@ struct AdvanceResult {
|
|||
};
|
||||
std::optional<AdvanceResult> PollAndAdvance(const OrderRecord& order);
|
||||
bool ArrivalPollAllowed(std::string_view token, std::chrono::seconds interval);
|
||||
bool MoneySeenRecently(std::string_view token);
|
||||
std::string NowIso8601();
|
||||
|
||||
// Common headers on every HTML response.
|
||||
|
|
@ -317,6 +318,7 @@ HTTPResponse RenderPage(std::string_view target) {
|
|||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
pay.minutesLeft = (instr->deadlineUnix - now) / 60;
|
||||
pay.seen = MoneySeenRecently(order->token);
|
||||
for (auto& c : instr->chains) {
|
||||
pay.chains.push_back({ std::move(c.name), std::move(c.contract),
|
||||
std::move(c.link), std::move(c.note) });
|
||||
|
|
@ -894,6 +896,34 @@ HTTPResponse HandleCheckout(const HTTPRequest& req, const Route& route) {
|
|||
std::mutex gArrivalPollMutex;
|
||||
std::unordered_map<std::string, std::chrono::steady_clock::time_point> gLastArrivalPoll;
|
||||
|
||||
// Orders whose payment the rail has SEEN in flight (visible on the network,
|
||||
// finality pending), stamped by whichever poll noticed — the reconciler or an
|
||||
// arrival check. Read at render time so the page can acknowledge the money
|
||||
// without the render path ever dialing a provider itself: the page shows the
|
||||
// most recent poll's knowledge, which is exactly as fresh as "paid" would be.
|
||||
// Entries only ever accumulate truth ("seen" is never un-seen by one flaky
|
||||
// probe) and age out; a settled or lapsed order stops rendering the section
|
||||
// that reads this, so stale entries are harmless.
|
||||
std::unordered_map<std::string, std::chrono::steady_clock::time_point> gMoneySeen;
|
||||
|
||||
void RememberMoneySeen(std::string_view token) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
std::lock_guard lock(gArrivalPollMutex);
|
||||
std::erase_if(gMoneySeen, [&](const auto& entry) {
|
||||
return now - entry.second > std::chrono::hours(1);
|
||||
});
|
||||
gMoneySeen[std::string(token)] = now;
|
||||
}
|
||||
|
||||
bool MoneySeenRecently(std::string_view token) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
std::lock_guard lock(gArrivalPollMutex);
|
||||
const auto it = gMoneySeen.find(std::string(token));
|
||||
// Ten minutes covers several finality epochs; a payment seen longer ago
|
||||
// that still has not settled is a claim this page should stop making.
|
||||
return it != gMoneySeen.end() && now - it->second < std::chrono::minutes(10);
|
||||
}
|
||||
|
||||
bool ArrivalPollAllowed(std::string_view token, std::chrono::seconds interval) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
std::lock_guard lock(gArrivalPollMutex);
|
||||
|
|
@ -924,6 +954,9 @@ std::optional<AdvanceResult> PollAndAdvance(const OrderRecord& order) {
|
|||
if (!rail) return std::nullopt;
|
||||
const std::optional<PaidStatus> paid = rail->CheckPaid(order.payId, order.totalMinor);
|
||||
if (!paid.has_value()) return std::nullopt;
|
||||
if (paid->state == PayState::Pending && paid->seen) {
|
||||
RememberMoneySeen(order.token);
|
||||
}
|
||||
if (paid->state == PayState::Paid) {
|
||||
if (AppendOrderStatus(order.token, "paid", NowIso8601(), paid->method)) {
|
||||
// The invoice number exists from the moment the money does —
|
||||
|
|
|
|||
Loading…
Reference in a new issue