From cf7e099367137923b2764f38fd32aa0fd1e7c2ab Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sun, 8 Aug 2021 18:26:39 +0200 Subject: public-orders-get.ts --- merchant-spec/public-orders-get.ts | 145 +++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 merchant-spec/public-orders-get.ts diff --git a/merchant-spec/public-orders-get.ts b/merchant-spec/public-orders-get.ts new file mode 100644 index 00000000..d46475f1 --- /dev/null +++ b/merchant-spec/public-orders-get.ts @@ -0,0 +1,145 @@ +// Merchant's DB state for one particular order. +// Invariants: +// paid => claimed +// claimed => !!contractHash +// requireClaimToken => !!claimToken +// !!lastPaidSessionId => paid +interface MerchantOrderInfo { + orderId: string; + requireClaimToken: boolean; + claimToken?: string; + contractHash?: string; + claimed: boolean; + paid: boolean; + fulfillmentUrl?: string; + publicReorderUrl?: string; + lastPaidSessionId?: string; +} + +interface Req { + orderId: string; + contractHash?: string; + claimToken?: string; + sessionId?: string; + accept: "json" | "html"; +} + +interface Resp { + httpStatus: string; + responseType: string; + // Additional details about response + response?: any; +} + +type MerchantOrderStore = { [orderId: string]: MerchantOrderInfo }; + +function handlePublicOrdersGet(mos: MerchantOrderStore, req: Req): Resp { + const ord = mos[req.orderId]; + if (!ord) { + return { + httpStatus: "404 Not Found", + responseType: "TalerErrorResponse", + }; + } + if (!ord.claimed) { + if (ord.requireClaimToken && ord.claimToken !== req.claimToken) { + return { + httpStatus: "403 Forbidden", + responseType: "TalerErrorResponse", + }; + } + return { + httpStatus: "402 Payment Required", + responseType: "StatusUnpaidResponse", + response: { + fulfillmentUrl: ord.fulfillmentUrl, + // FIXME: do we include claim token here? + talerPayUri: "taler://pay/", + }, + }; + } + + if (!ord.paid) { + if (ord.requireClaimToken && ord.claimToken !== req.claimToken) { + // This can happen when the fulfillment URL page detects + // the user has not paid under the current session. + return { + httpStatus: "202 Accepted", + responseType: "StatusGotoResponse", + response: { + public_reorder_url: ord.publicReorderUrl, + }, + }; + } + return { + httpStatus: "402 Payment Required", + responseType: "StatusUnpaidResponse", + response: { + fulfillmentUrl: ord.fulfillmentUrl, + // FIXME: do we include claim token here? + talerPayUri: "taler://pay/", + }, + }; + } + + // Here, we know that the order is paid for. + // But we still need the ord.claimToken, because + // the QR code page will poll until it gets a + // fulfillment URL, but we decided that the + // fulfillment URL should only be returned + // when the client is authenticated. + // (Otherwise, guessing the order ID might leak the + // fulfillment URL). + + const authOk = + ord.contractHash === req.contractHash || + (ord.requireClaimToken && ord.claimToken === req.claimToken); + + if (authOk) { + if (!!req.sessionId && req.sessionId !== ord.lastPaidSessionId) { + const alreadyPaidOrd = findAlreadyPaid(mos, req.sessionId); + if (alreadyPaidOrd) { + return { + httpStatus: "202 Accepted", + responseType: "StatusGotoResponse", + response: { + already_paid_order_id: alreadyPaidOrd.orderId, + } + } + } + return { + httpStatus: "402 Payment Required", + responseType: "StatusUnpaidResponse", + response: { + fulfillmentUrl: ord.fulfillmentUrl, + // TO DISCUSS: do we include claim token here? + talerPayUri: "taler://pay/", + }, + }; + } + return { + httpStatus: "200 OK", + responseType: "StatusPaidResponse", + response: { + fulfillmentUrl: ord.fulfillmentUrl, + }, + }; + } + + return { + httpStatus: "403 Forbidden", + responseType: "TalerErrorResponse", + }; +} + +function findAlreadyPaid( + mos: MerchantOrderStore, + sessionId: string +): MerchantOrderInfo | undefined { + for (const orderId of Object.keys(mos)) { + if (mos[orderId].lastPaidSessionId === sessionId) { + return mos[orderId]; + } + } + return undefined; +} -- cgit v1.2.3