store: name invoices answer 503 with names off

This commit is contained in:
shum committed 2026-10-22 02:30:45 +00:00
1 parent fe124363c9
commit 17943bbf0f
2 files changed
+24 -3

No files matched your search

@@ -248,12 +248,15 @@ invoiceViewOf :: WebEnv -> InvoiceRow -> IO (Either Response J.Value)
invoiceViewOf env@WebEnv {weConfig, weStore} row@InvoiceRow {irInvoiceId = InvoiceId invId, irProduct} = case irProduct of
IPBadge badgeType months -> pure $ Right $ badgeInvoiceView (confirmationsFor weConfig) badgeType months row
IPName rid -> case weOrders env of
Nothing -> Left internalError <$ logError ("invoice " <> invId <> " is a name's, and this service sells no names")
Nothing -> Left providerUnavailable <$ namesOff invId
Just orders ->
withTransaction weStore (`getRegistration` rid) >>= \case
Nothing -> pure $ Left notFound
Just r -> Right <$> nameInvoiceView orders (confirmationsFor weConfig) row r
namesOff :: Text -> IO ()
namesOff invId = logError ("invoice " <> invId <> " is a name's, and this service sells no names")
-- | Monero reports a payment as confirming while its figures are zero, so the verdict counts as much as the figure.
paymentMark :: InvoiceRow -> (Text, Bool)
paymentMark InvoiceRow {irPayment} =
@@ -261,11 +264,13 @@ paymentMark InvoiceRow {irPayment} =
-- | The provider is cancelled first, because cancelling our record first would leave an address the buyer can still pay into with nothing watching it.
cancelInvoiceHandler :: WebEnv -> InvoiceId -> Respond -> IO ResponseReceived
cancelInvoiceHandler env@WebEnv {weStore, weProviders} invId respond =
cancelInvoiceHandler env@WebEnv {weStore, weProviders} invId@(InvoiceId invText) respond =
getInvoice weStore invId >>= \case
Nothing -> respond notFound
Just InvoiceRow {irStatus} | irStatus /= ISOpen -> respond (jsonError status409 "not_open")
Just row | funded row -> respond (jsonError status409 "funded")
-- refused before the provider's cancel, since the answer could not show the order
Just InvoiceRow {irProduct = IPName _} | Nothing <- weOrders env -> namesOff invText >> respond providerUnavailable
Just InvoiceRow {irProvider, irProviderRef} -> case providerNamed weProviders irProvider of
Nothing -> respond providerUnavailable
Just Provider {pCancelInvoice} ->
@@ -279,7 +284,7 @@ cancelInvoiceHandler env@WebEnv {weStore, weProviders} invId respond =
finish = do
now <- getCurrentTime
E.try (cancelOpenInvoice weStore (weWaiters env) (oePhases <$> weOrders env) invId now) >>= \case
Left (RegistrationChanged rid) -> maybe (pure internalError) (`registrationChangedResponse` rid) (weOrders env) >>= respond
Left (RegistrationChanged rid) -> maybe (pure providerUnavailable) (`registrationChangedResponse` rid) (weOrders env) >>= respond
Right _ -> getInvoice weStore invId >>= maybe (respond notFound) (\row' -> respondInvoice env row' respond)
paidParam :: Request -> Maybe (Text, Bool)
@@ -92,6 +92,7 @@ namesOrdersTests = describe "StoreService names: orders" $ do
it "queues an order settled while open and publishes its version" testSettlementQueues
it "closes the order of an invoice the provider closed or the sweep expired" testExpiryCloses
it "cleans up unpaid orders after 30 days, reading the provider first" testCleanup
it "answers 503 for a name invoice while names are off, cancelling nothing" testNamesOff
describe "invoice side" $ do
it "finds an invoice by provider ref in either table, refusing a ref both hold" testProviderRefs
it "reads and sweeps name invoices with the badge ones" testPollerQueries
@@ -606,6 +607,21 @@ testCancelAnswers = withOrders $ \ctx -> do
where
cancelAt c (InvoiceId t) = post c ("/api/invoice/" <> T.unpack t <> "/cancel") ""
testNamesOff :: IO ()
testNamesOff = withOrders $ \ctx -> do
(invId@(InvoiceId t), _) <- placed ctx aliceOrder
mgr <- newManager defaultManagerSettings
Warp.testWithApplication (pure $ webApp (cWeb ctx) {weOrders = Nothing}) $ \port -> do
let off = ctx {cClient = (mgr, "http://127.0.0.1:" <> show port)}
logs <- capturingStderr $ do
r <- webGet (cClient off) ("/api/invoice/" <> T.unpack t)
(statusOf r, J.decode (responseBody r)) `shouldBe` refusedWith 503 "provider_unavailable"
post off ("/api/invoice/" <> T.unpack t <> "/cancel") "" `shouldReturn` refusedWith 503 "provider_unavailable"
logs `shouldSatisfy` T.isInfixOf ("invoice " <> t <> " is a name's, and this service sells no names")
calls <- stubCalls (cStub ctx)
[c | c@StubCancel {} <- calls] `shouldBe` []
(irStatus <$>) <$> getInvoice (cStore ctx) invId `shouldReturn` Just ISOpen
-- | The provider's cancel settles the invoice with the signal before it answers.
withOrdersCancelling :: PaymentSignal -> (Ctx -> IO a) -> IO a
withOrdersCancelling signal action = withOrders $ \ctx -> do