summaryrefslogtreecommitdiff
path: root/merchant-spec
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-08-08 18:26:39 +0200
committerFlorian Dold <florian@dold.me>2021-08-08 18:26:39 +0200
commitcf7e099367137923b2764f38fd32aa0fd1e7c2ab (patch)
treeb0c6c7c46f0602a272b3d83dde446aac78710c97 /merchant-spec
parente429ce9d917935b354e23f7b24e5fdeb702a795b (diff)
downloaddocs-cf7e099367137923b2764f38fd32aa0fd1e7c2ab.tar.gz
docs-cf7e099367137923b2764f38fd32aa0fd1e7c2ab.tar.bz2
docs-cf7e099367137923b2764f38fd32aa0fd1e7c2ab.zip
public-orders-get.ts
Diffstat (limited to 'merchant-spec')
-rw-r--r--merchant-spec/public-orders-get.ts145
1 files changed, 145 insertions, 0 deletions
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;
+}