PayjoinUriSessionService.ToExpirationSeconds clamps the remaining monitoring time to a floor of one second:
var remainingSeconds = (monitoringExpiresAt - DateTimeOffset.UtcNow).TotalSeconds;
if (remainingSeconds < 1d)
{
return 1UL;
}
So when monitoringExpiresAt is already in the past, BuildAsync still builds the receiver session, registers it with the directory, and that session expires one second later.
The path is hard to reach. The availability checks run before session creation, and an expired invoice normally fails one of them first. The problem is that the clamp hides the condition instead of reporting it. The result is a session that dies at birth, a confusing log trail, and a directory registration that serves no purpose.
InvoiceNotPayable now exists in PayjoinAvailabilityStatus, so refusing with a reason is straightforward. A test with a past monitoringExpiresAt would pin it.
PayjoinUriSessionService.ToExpirationSecondsclamps the remaining monitoring time to a floor of one second:So when
monitoringExpiresAtis already in the past,BuildAsyncstill builds the receiver session, registers it with the directory, and that session expires one second later.The path is hard to reach. The availability checks run before session creation, and an expired invoice normally fails one of them first. The problem is that the clamp hides the condition instead of reporting it. The result is a session that dies at birth, a confusing log trail, and a directory registration that serves no purpose.
InvoiceNotPayablenow exists inPayjoinAvailabilityStatus, so refusing with a reason is straightforward. A test with a pastmonitoringExpiresAtwould pin it.