summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-08-10 15:11:09 +0200
committerFlorian Dold <florian@dold.me>2021-08-10 15:11:18 +0200
commita2cef959c0cf801e9f4136736dd8e8e8d98838af (patch)
tree1dd01c751d4f0b608b0c7bd41cb4b638e21abf82
parent89304a023cab89cf73f5563ef7af403e2152cb9f (diff)
downloaddocs-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.ts183
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,
+ },
};
}