diff options
author | Florian Dold <florian@dold.me> | 2021-08-10 15:11:09 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2021-08-10 15:11:18 +0200 |
commit | a2cef959c0cf801e9f4136736dd8e8e8d98838af (patch) | |
tree | 1dd01c751d4f0b608b0c7bd41cb4b638e21abf82 | |
parent | 89304a023cab89cf73f5563ef7af403e2152cb9f (diff) | |
download | docs-a2cef959c0cf801e9f4136736dd8e8e8d98838af.tar.gz docs-a2cef959c0cf801e9f4136736dd8e8e8d98838af.tar.bz2 docs-a2cef959c0cf801e9f4136736dd8e8e8d98838af.zip |
complete public order spec, implement Christian's suggestions
-rw-r--r-- | merchant-spec/public-orders-get.ts | 183 |
1 files changed, 119 insertions, 64 deletions
diff --git a/merchant-spec/public-orders-get.ts b/merchant-spec/public-orders-get.ts index 916ba19a..03c4f7af 100644 --- a/merchant-spec/public-orders-get.ts +++ b/merchant-spec/public-orders-get.ts @@ -28,8 +28,11 @@ interface Req { // (Abstract) response to /orders/{id} interface Resp { httpStatus: string; + contentType: "json" | "html"; // Schema type of the response responseType: string; + // Redirect "Location: " if applicable to status code + redirectLocation?: string; // Additional details about response response?: any; } @@ -41,56 +44,40 @@ 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", - }; + return respNotFound(req); } if (!ord.claimed) { if (ord.requireClaimToken && ord.claimToken !== req.claimToken) { - return { - httpStatus: "403 Forbidden", - responseType: "TalerErrorResponse", - }; + return respForbidden(req); } - return { - httpStatus: "402 Payment Required", - responseType: "StatusUnpaidResponse", - response: { - fulfillmentUrl: ord.fulfillmentUrl, - // FIXME: do we include claim token here? - talerPayUri: "taler://pay/", - }, - }; + return respUnpaid(req, ord); } if (!ord.paid) { const hcOk = ord.contractHash === req.contractHash; - if (!hcOk && ord.requireClaimToken && ord.claimToken !== req.claimToken) { + const ctOk = ord.claimToken === req.claimToken; + if (req.contractHash && !hcOk) { + // Contract terms hash given but wrong + return respForbidden(req); + } + if (req.claimToken && ord.claimToken !== req.claimToken) { + // Claim token given but wrong + return respForbidden(req); + } + if (ord.requireClaimToken && !req.claimToken && !hcOk) { // Client is trying to get the order status of a claimed, // unpaid order. However, the client is not showing authentication. - // + // // This can happen when the fulfillment URL includes the order ID, // and the storefront redirects the user to the backend QR code // page, because the order is not paid under the current session. // This happens on bookmarking / link sharing. - return { - httpStatus: "202 Accepted", - responseType: "StatusGotoResponse", - response: { - public_reorder_url: ord.publicReorderUrl, - }, - }; + if (!ord.publicReorderUrl) { + return respForbidden(req); + } + return respGoto(req, ord.publicReorderUrl); } - return { - httpStatus: "402 Payment Required", - responseType: "StatusUnpaidResponse", - response: { - fulfillmentUrl: ord.fulfillmentUrl, - // FIXME: do we include claim token here? - talerPayUri: "taler://pay/", - }, - }; + return respUnpaid(req, ord); } // Here, we know that the order is paid for. @@ -106,41 +93,109 @@ function handlePublicOrdersGet(mos: MerchantOrderStore, req: Req): Resp { 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: { - public_reorder_url: ord.publicReorderUrl, - 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/", - }, - }; + if (!authOk) { + return respForbidden(req); + } + + if (!!req.sessionId && req.sessionId !== ord.lastPaidSessionId) { + const alreadyPaidOrd = findAlreadyPaid(mos, req.sessionId); + if (!!alreadyPaidOrd) { + return respAlreadyPaid(req, alreadyPaidOrd); } + return respUnpaid(req, ord); + } + return respPaid(req, ord); +} + +function respNotFound(req: Req): Resp { + return { + contentType: req.accept, + httpStatus: "404 Not Found", + responseType: "TalerError", + }; +} + +function respForbidden(req: Req): Resp { + return { + contentType: req.accept, + httpStatus: "403 Forbidden", + responseType: "TalerError", + }; +} + +function respAlreadyPaid(req: Req, alreadyPaidOrd: MerchantOrderInfo): Resp { + // This could be called with an empty fulfillment URL, but that doens't + // really make sense for the client's perspective. + if (req.accept === "html") { return { - httpStatus: "200 OK", - responseType: "StatusPaidResponse", - response: { - fulfillmentUrl: ord.fulfillmentUrl, - }, + httpStatus: "302 Found", + contentType: "html", + redirectLocation: alreadyPaidOrd.fulfillmentUrl, + responseType: "empty", }; } + return { + httpStatus: "202 Accepted", + contentType: "json", + responseType: "StatusGotoResponse", + response: { + fulfillment_url: alreadyPaidOrd.fulfillmentUrl, + }, + }; +} +function respGoto(req: Req, publicReorderUrl: string): Resp { + if (req.accept === "html") { + return { + httpStatus: "302 Found", + contentType: "html", + redirectLocation: publicReorderUrl, + responseType: "empty", + }; + } return { - httpStatus: "403 Forbidden", - responseType: "TalerErrorResponse", + httpStatus: "202 Accepted", + contentType: "json", + responseType: "StatusGotoResponse", + response: { + public_reorder_url: publicReorderUrl, + }, + }; +} + +function respUnpaid(req: Req, ord: MerchantOrderInfo): Resp { + if (req.accept === "html") { + return; + } + return { + httpStatus: "402 Payment Required", + contentType: "html", + responseType: "StatusUnpaidResponse", + response: { + // Required for repurchase detection + fulfillmentUrl: ord.fulfillmentUrl, + }, + }; +} + +function respPaid(req: Req, ord: MerchantOrderInfo): Resp { + if (req.accept === "html") { + if (req.accept === "html") { + return { + httpStatus: "302 Found", + contentType: "html", + redirectLocation: ord.fulfillmentUrl || "<backend pay success page>", + responseType: "empty", + }; + } + } + return { + httpStatus: "200 OK", + contentType: "json", + responseType: "StatusPaidResponse", + response: { + fulfillmentUrl: ord.fulfillmentUrl, + }, }; } |