From bfacdc8e58d8cb2ad55a944fb8f26885457eebb2 Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@hebeler.club> Date: Wed, 21 Dec 2022 14:46:03 +0100 Subject: [PATCH] added refund form --- pass/public/images/money.svg | 167 +++++++++++++++++++ pass/public/styles/orders/order.css | 2 +- pass/public/styles/orders/order.less | 10 +- pass/public/styles/orders/refund.css | 1 + pass/public/styles/orders/refund.less | 23 +++ pass/routes/key.js | 2 +- pass/routes/{orders.js => orders/invoice.js} | 126 +------------- pass/routes/orders/orders.js | 123 ++++++++++++++ pass/routes/orders/refund.js | 24 +++ pass/views/orders/invoice.ejs | 15 +- pass/views/orders/order.ejs | 39 ++--- pass/views/orders/order_details.ejs | 14 ++ pass/views/orders/refund.ejs | 21 +++ 13 files changed, 405 insertions(+), 162 deletions(-) create mode 100644 pass/public/images/money.svg create mode 100644 pass/public/styles/orders/refund.css create mode 100644 pass/public/styles/orders/refund.less rename pass/routes/{orders.js => orders/invoice.js} (61%) create mode 100644 pass/routes/orders/orders.js create mode 100644 pass/routes/orders/refund.js create mode 100644 pass/views/orders/order_details.ejs create mode 100644 pass/views/orders/refund.ejs diff --git a/pass/public/images/money.svg b/pass/public/images/money.svg new file mode 100644 index 0000000..8c596ce --- /dev/null +++ b/pass/public/images/money.svg @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="900" + height="900" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="cash1.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="350" + inkscape:cy="520" + inkscape:document-units="px" + inkscape:current-layer="g5229" + showgrid="false" + inkscape:window-width="1366" + inkscape:window-height="715" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Capa 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-152.36218)"> + <g + transform="matrix(12.774324,0,0,13.452647,-604.34066,-4031.2319)" + id="g5229"> + <g + id="g5859" + transform="matrix(0.6263025,0,0,0.6263025,33.712087,192.29508)"> + <g + transform="matrix(-0.70666232,0,0,0.70666232,-6.0929638,-40.117016)" + id="g5231"> + <g + id="g5257" + transform="translate(-6.4285711,-84.642857)" /> + <g + transform="translate(-6.4285711,-92.64286)" + id="g5273"> + <g + id="g5275"> + <path + style="fill:#ffffff;stroke:none" + d="m -190.89286,425.75504 45,30.53571 0.53572,0.35715 8.92857,-3.39286 18.92857,-8.75 17.14286,-8.75 18.571426,-10 27.678571,-16.60714 7.857143,-4.82143 -43.571429,-21.96429 -36.785711,19.46429 -17.67857,8.39285 -24.28572,8.75 z" + id="path5277" + inkscape:connector-curvature="0" + transform="translate(6.4285711,84.642857)" /> + <path + style="fill:#005500;stroke:none" + d="m -83.203841,466.29464 c -55.310089,31.49436 -76.664149,35.62903 -103.590799,44.23291 l 47.49879,31.83852 c 35.8553,-14.36254 69.040859,-32.88026 101.072639,-53.21721 l -44.98063,-22.85422 z m -0.31477,1.63947 41.70702,21.18196 c -29.70502,18.85968 -63.729829,37.8978 -96.980629,51.21706 l -44.03632,-29.51047 c 24.97073,-7.97891 48.01751,-13.68191 99.309929,-42.88855 z" + id="path5279" + inkscape:connector-curvature="0" /> + </g> + </g> + <g + id="g5281" + transform="translate(-6.4285711,-96.64286)"> + <g + id="g5283"> + <path + transform="translate(6.4285711,84.642857)" + inkscape:connector-curvature="0" + id="path5285" + d="m -190.89286,425.75504 45,30.53571 0.53572,0.35715 8.92857,-3.39286 18.92857,-8.75 17.14286,-8.75 18.571426,-10 27.678571,-16.60714 7.857143,-4.82143 -43.571429,-21.96429 -36.785711,19.46429 -17.67857,8.39285 -24.28572,8.75 z" + style="fill:#ffffff;stroke:none" /> + <path + inkscape:connector-curvature="0" + id="path5287" + d="m -83.203841,466.29464 c -55.310089,31.49436 -76.664149,35.62903 -103.590799,44.23291 l 47.49879,31.83852 c 35.8553,-14.36254 69.040859,-32.88026 101.072639,-53.21721 l -44.98063,-22.85422 z m -0.31477,1.63947 41.70702,21.18196 c -29.70502,18.85968 -63.729829,37.8978 -96.980629,51.21706 l -44.03632,-29.51047 c 24.97073,-7.97891 48.01751,-13.68191 99.309929,-42.88855 z" + style="fill:#005500;stroke:none" /> + </g> + </g> + <g + transform="translate(-6.4285711,-100.64286)" + id="g5289"> + <g + id="g5291"> + <path + style="fill:#ffffff;stroke:none" + d="m -190.89286,425.75504 45,30.53571 0.53572,0.35715 8.92857,-3.39286 18.92857,-8.75 17.14286,-8.75 18.571426,-10 27.678571,-16.60714 7.857143,-4.82143 -43.571429,-21.96429 -36.785711,19.46429 -17.67857,8.39285 -24.28572,8.75 z" + id="path5293" + inkscape:connector-curvature="0" + transform="translate(6.4285711,84.642857)" /> + <path + style="fill:#005500;stroke:none" + d="m -83.203841,466.29464 c -55.310089,31.49436 -76.664149,35.62903 -103.590799,44.23291 l 47.49879,31.83852 c 35.8553,-14.36254 69.040859,-32.88026 101.072639,-53.21721 l -44.98063,-22.85422 z m -0.31477,1.63947 41.70702,21.18196 c -29.70502,18.85968 -63.729829,37.8978 -96.980629,51.21706 l -44.03632,-29.51047 c 24.97073,-7.97891 48.01751,-13.68191 99.309929,-42.88855 z" + id="path5295" + inkscape:connector-curvature="0" /> + </g> + </g> + <g + id="g5297" + transform="translate(0,-14)"> + <path + inkscape:connector-curvature="0" + id="path5299" + d="m -180.71429,422.00504 c 0,0 5.71429,3.92857 6.42858,7.14286 0.71428,3.21428 23.92857,16.78571 23.92857,16.78571 l 11.42857,0 7.85714,-1.78571 19.64286,-9.28572 15.714284,-6.78571 18.928572,-10.35715 17.857142,-10.71428 0,-11.42857 -25.714285,-12.14286 -12.857143,1.78571 -11.78571,7.14286 -28.92858,12.85714 -37.5,13.21429 z" + style="fill:#ffffff;stroke:none" /> + <g + id="g5301" + style="fill:#165016"> + <path + style="fill:#165016;stroke:none" + d="M -89.65625,378.78125 C -144.56747,408.797 -165.76753,412.73755 -192.5,420.9375 l 47.15625,30.34375 C -109.74702,437.59301 -76.800778,419.94466 -45,400.5625 L -89.65625,378.78125 z M -78.75,387.3125 -63.875,395 c -0.955399,1.91634 -1.5,4.03707 -1.5,6.28125 0,2.55026 0.692363,4.95727 1.90625,7.0625 -21.463826,13.90825 -45.38187,24.75337 -69.875,34.875 -2.7124,-2.49005 -6.6914,-4.0625 -11.125,-4.0625 -3.62367,0 -6.93009,1.06679 -9.5,2.8125 l -17.4375,-11.3125 c 1.12739,-1.77781 1.75,-3.80691 1.75,-5.96875 0,-2.267 -0.7069,-4.37955 -1.9375,-6.21875 19.45498,-5.13454 54.12354,-20.98019 72.03125,-29.9375 2.577326,1.27086 5.824082,2.03125 9.375,2.03125 4.567016,0 8.656963,-1.26067 11.4375,-3.25 z" + id="path5303" + inkscape:connector-curvature="0" /> + <path + style="color:#000000;fill:#165016;stroke:none;stroke-width:0.07914069;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m -146.36813,410.11743 c -1.58551,7.11414 1.20048,14.03422 8.40277,18.38878 7.20233,4.35457 17.17174,5.14646 26.66229,2.81172 z" + id="path5305" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cscc" /> + <path + sodipodi:nodetypes="cscc" + inkscape:connector-curvature="0" + id="path5307" + d="m -82.423075,417.96506 c 1.506079,-6.55188 -1.140337,-12.92505 -7.981804,-16.93546 -6.841505,-4.01042 -16.311461,-4.73972 -25.326551,-2.5895 z" + style="color:#000000;fill:#165016;stroke:none;stroke-width:0.07402207;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> + </g> + <path + style="fill:#005500;stroke:none" + d="m -145.17857,393.79076 40.71428,24.99999 0.14986,10.67087 20.57879,-10.40022 0.164211,-11.87779 -39.285711,-23.39286 z" + id="path5309" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + </g> + <path + sodipodi:nodetypes="cccccccccccccc" + inkscape:connector-curvature="0" + id="path5857" + d="m 127.8464,248.20324 -0.23853,1.7609 -1.53542,1.40864 2.01358,1.25366 -1.27645,1.61437 2.1288,1.2319 -25.32182,16.26715 12.76389,-10.1861 -8.72348,4.49246 11.73919,-9.55072 -8.71936,4.63275 10.98808,-8.90621 -8.96125,5.69782 z" + style="fill:#165016;stroke:none" /> + </g> + </g> + </g> +</svg> diff --git a/pass/public/styles/orders/order.css b/pass/public/styles/orders/order.css index 4961101..67d1c37 100644 --- a/pass/public/styles/orders/order.css +++ b/pass/public/styles/orders/order.css @@ -1 +1 @@ -#order{padding:1rem 0;grid-area:content}#order .breadcrumbs{display:flex;gap:.5rem;padding:0;list-style-type:disclosure-closed;font-size:clamp(.7rem, 4w, 1rem)}#order .breadcrumbs li{margin-left:1rem}#order .breadcrumbs li:first-child{margin-left:0;list-style-type:none}#order .breadcrumbs li a{color:#777}#order #order-details{display:grid;grid-template-columns:1fr 4rem 4rem;text-align:right;padding:2rem 0}#order #order-details>div.heading{text-align:left;border-bottom:1px solid #ff7f00;font-weight:bold;padding-bottom:.2rem}#order #order-details>div:not(.heading){padding:.5rem 0}#order #order-details>div.sum{font-weight:bold}#order>h3{text-align:center}#order #order-buttons{display:grid;grid-template-columns:1fr auto;justify-items:end;gap:.5rem}#order #order-buttons .button{font-size:.8rem;display:flex;align-items:center;justify-content:center;line-height:1;gap:.5rem;padding:.5rem}#order #order-buttons .button img{height:1rem;filter:brightness(0) invert(1)}@media (max-width:500px){#order #order-buttons{grid-template-columns:1fr}#order #order-buttons>a.button{width:100%;padding:1rem}}#order #invoice #download-receipt>a.button{display:flex;margin:0 auto;justify-content:center;align-items:center;gap:.5rem;padding:.5rem}#order #invoice #download-receipt>a.button>img{height:1.5em}#order #invoice #invoice-form #invoice-form-fields{display:grid;gap:1rem}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field{display:grid;gap:.5rem;font-weight:bold;color:#777;font-size:.7rem;place-content:center;text-align:center}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field #email,#order #invoice #invoice-form #invoice-form-fields .invoice-form-field #name{padding:.5rem;border-radius:5px;font-size:.8rem;width:13rem;text-align:center}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field #address{padding:.5rem;border-radius:5px;line-height:2;text-align:center;width:13rem}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field button{display:flex;align-items:center;gap:.5rem;padding:.5rem}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field button img{height:1.2rem}#order #invoice #invoice-form .storage-time{font-size:.8rem;text-align:center} \ No newline at end of file +#order{padding:1rem 0;grid-area:content}#order .breadcrumbs{display:flex;gap:.5rem;padding:0;list-style-type:disclosure-closed;font-size:clamp(.7rem, 4vw, 1rem)}#order .breadcrumbs li{margin-left:1rem}#order .breadcrumbs li:first-child{margin-left:0;list-style-type:none}#order .breadcrumbs li a{color:#777}#order #order-details{display:grid;grid-template-columns:1fr 4rem 4rem;text-align:right;padding:2rem 0}#order #order-details>div.heading{text-align:left;border-bottom:1px solid #ff7f00;font-weight:bold;padding-bottom:.2rem}#order #order-details>div:not(.heading){padding:.5rem 0}#order #order-details>div.sum{font-weight:bold}#order>h3{text-align:center}#order #order-buttons{display:grid;grid-template-columns:1fr auto auto;justify-items:end;gap:.5rem}#order #order-buttons .button{font-size:.8rem;display:flex;align-items:center;justify-content:center;line-height:1;gap:.5rem;padding:.5rem}#order #order-buttons .button img{height:1rem}#order #order-buttons .button img.order-receipt{filter:brightness(0) invert(1)}@media (max-width:842px){#order #order-buttons{grid-template-columns:1fr}#order #order-buttons>a.button{width:100%;padding:1rem}}#order #invoice #download-receipt>a.button{display:flex;margin:0 auto;justify-content:center;align-items:center;gap:.5rem;padding:.5rem}#order #invoice #download-receipt>a.button>img{height:1.5em}#order #invoice #invoice-form #invoice-form-fields{display:grid;gap:1rem}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field{display:grid;gap:.5rem;font-weight:bold;color:#777;font-size:.7rem;place-content:center;text-align:center}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field #email,#order #invoice #invoice-form #invoice-form-fields .invoice-form-field #name{padding:.5rem;border-radius:5px;font-size:.8rem;width:13rem;text-align:center}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field #address{padding:.5rem;border-radius:5px;line-height:2;text-align:center;width:13rem}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field button{display:flex;align-items:center;gap:.5rem;padding:.5rem}#order #invoice #invoice-form #invoice-form-fields .invoice-form-field button img{height:1.2rem}#order #invoice #invoice-form .storage-time{font-size:.8rem;text-align:center} \ No newline at end of file diff --git a/pass/public/styles/orders/order.less b/pass/public/styles/orders/order.less index 57589c8..3dafbb1 100644 --- a/pass/public/styles/orders/order.less +++ b/pass/public/styles/orders/order.less @@ -6,7 +6,7 @@ gap: 0.5rem; padding: 0; list-style-type: disclosure-closed; - font-size: clamp(0.7rem, 4w, 1rem); + font-size: clamp(0.7rem, 4vw, 1rem); li { margin-left: 1rem; &:first-child { @@ -45,7 +45,7 @@ #order-buttons { display: grid; - grid-template-columns: 1fr auto; + grid-template-columns: 1fr auto auto; justify-items: end; gap: 0.5rem; .button { @@ -58,10 +58,12 @@ padding: 0.5rem; img { height: 1rem; - filter: brightness(0) invert(1); + &.order-receipt { + filter: brightness(0) invert(1); + } } } - @media (max-width: 500px) { + @media (max-width: 842px) { grid-template-columns: 1fr; > a.button { width: 100%; diff --git a/pass/public/styles/orders/refund.css b/pass/public/styles/orders/refund.css new file mode 100644 index 0000000..fee50cd --- /dev/null +++ b/pass/public/styles/orders/refund.css @@ -0,0 +1 @@ +#refund{grid-area:content}#refund #refund-form textarea{width:100%;max-width:25rem}#refund #refund-form button{display:flex;text-align:center;width:100%;max-width:25rem;align-items:center;justify-content:center;gap:.5rem;padding:.5rem;font-size:.8rem}#refund #refund-form button>img{height:2em} \ No newline at end of file diff --git a/pass/public/styles/orders/refund.less b/pass/public/styles/orders/refund.less new file mode 100644 index 0000000..e58f62f --- /dev/null +++ b/pass/public/styles/orders/refund.less @@ -0,0 +1,23 @@ +#refund { + grid-area: content; + #refund-form { + textarea { + width: 100%; + max-width: 25rem; + } + button { + display: flex; + text-align: center; + width: 100%; + max-width: 25rem; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.5rem; + font-size: 0.8rem; + > img { + height: 2em; + } + } + } +} diff --git a/pass/routes/key.js b/pass/routes/key.js index 958cfb9..cc93c47 100644 --- a/pass/routes/key.js +++ b/pass/routes/key.js @@ -3,7 +3,7 @@ var router = express.Router(); const { param, validationResult } = require("express-validator"); const config = require("config"); -var orderRouter = require("./orders"); +var orderRouter = require("./orders/orders"); var checkout_router_paypal = require("./checkout/paypal"); var Key = require("../app/Key"); diff --git a/pass/routes/orders.js b/pass/routes/orders/invoice.js similarity index 61% rename from pass/routes/orders.js rename to pass/routes/orders/invoice.js index 71b732d..822cda7 100644 --- a/pass/routes/orders.js +++ b/pass/routes/orders/invoice.js @@ -1,121 +1,11 @@ var express = require("express"); var router = express.Router({ mergeParams: true }); -const { param, body, validationResult } = require("express-validator"); -const Order = require("../app/Order"); -const config = require("config"); - -const dayjs = require("dayjs"); -const OrderReceipt = require("../app/pdf/OrderReceipt"); - -router.use("/", (req, res, next) => { - if (typeof req.cookies.order !== "undefined") { - req.data.form = { - "order-id": req.cookies.order, - }; - } - console.log(req.cookies); - next(); -}); -router.get("/", (req, res, next) => { - req.data.page = "order"; - req.data.css.push("/styles/orders/orders.css"); - res.render("key", req.data); -}); - -router.post("/", (req, res, next) => { - console.log(req.body["order-id"]); - let matches = req.body["order-id"].match(/^(INV_)?(\d{14})$/); - if (!matches) { - req.data.page = "order"; - req.data.css.push("/styles/orders/orders.css"); - req.data.error = 400; - req.data.form = { - "order-id": req.body["order-id"], - }; - res.render("key", req.data); - } else { - let order_id = matches[2]; - Order.LOAD_ORDER_FROM_ID(order_id) - .then(/** @param {Order} order */ (order) => order.getKeyFromOrderLink()) - .then((key) => { - if (key !== req.data.key.key) { - throw "Order is not connected to this key"; - } - res.redirect(`/key/${req.data.key.key}/orders/${order_id}`); - }) - .catch((reason) => { - req.data.page = "order"; - req.data.css.push("/styles/orders/orders.css"); - req.data.error = 404; - req.data.form = { - "order-id": req.body["order-id"], - }; - res.render("key", req.data); - }); - } -}); - -router.use("/:order_id", param("order_id").isInt(), (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(404).render("error", { - message: "Order not found", - error: { - status: 404, - }, - }); - } - Order.LOAD_ORDER_FROM_ID(req.params.order_id) - .then((order) => { - res.cookie("order", order.getOrderID()); - req.data.page = "order"; - req.data.css.push("/styles/orders/order.css"); - req.data.order = { order: order }; - req.data.links.order_url = `/key/${req.data.key.key}/orders/${req.params.order_id}#order`; - req.data.links.receipt_url = `/key/${req.data.key.key}/orders/${req.params.order_id}/pdf`; - req.data.links.invoice_url = `/key/${req.data.key.key}/orders/${req.params.order_id}/invoice#invoice-form`; - if (req.data.order.order.isReceiptCreated()) { - req.data.order.download_invoice_url = `/key/${ - req.data.key.key - }/orders/${req.data.order.order.getOrderID()}/invoice/download`; - } - next("route"); - }) - .catch((reason) => { - res.status(404).render("error", { - message: "Order not found", - error: { - status: 404, - }, - }); - }); -}); - -router.get("/:order_id", (req, res) => { - res.render("key", req.data); -}); -router.get("/:order_id/pdf", (req, res) => { - OrderReceipt.CREATE_ORDER_RECEIPT(req.data.order.order) - .then((data) => { - res - .status(200) - .header({ - "Content-Type": "application/pdf", - }) - .send(Buffer.concat(data)); - }) - .catch((reason) => { - res.locals.error = { - status: 500, - stack: JSON.stringify(reason, null, 4), - }; - res.locals.message = "Error generating Order Receipt"; - res.status(500).render("error"); - }); -}); +const config = require("config"); +const { body, validationResult } = require("express-validator"); +const OrderReceipt = require("../../app/pdf/OrderReceipt"); -router.get("/:order_id/invoice", (req, res) => { +router.get("/", (req, res) => { req.data.order.invoice = { params: { name: req.query.name || "", @@ -132,7 +22,7 @@ router.get("/:order_id/invoice", (req, res) => { }); router.post( - "/:order_id/invoice*", + "/*", body("email").isEmail({ domain_specific_validation: true }), body("address").isLength({ max: 1000 }), body("name").isLength({ max: 500 }), @@ -159,7 +49,7 @@ router.post( ); router.post( - "/:order_id/invoice", + "/", (req, res, next) => { // Check if admin parameter is set and if so if the user is authenticated if (req.body.admin) { @@ -235,7 +125,7 @@ router.post( ); router.post( - "/:order_id/invoice/create", + "/create", (req, res, next) => { // Check if admin parameter is set and if so if the user is authenticated if (req.data.admin) { @@ -280,7 +170,7 @@ router.post( } ); -router.get("/:order_id/invoice/download", (req, res) => { +router.get("/download", (req, res) => { if (req.data.order.order.isReceiptCreated()) { req.data.order.order .getReceipt() diff --git a/pass/routes/orders/orders.js b/pass/routes/orders/orders.js new file mode 100644 index 0000000..60b2372 --- /dev/null +++ b/pass/routes/orders/orders.js @@ -0,0 +1,123 @@ +var express = require("express"); +var router = express.Router({ mergeParams: true }); + +const { param, validationResult } = require("express-validator"); +const Order = require("../../app/Order"); +const OrderReceipt = require("../../app/pdf/OrderReceipt"); + +router.use("/", (req, res, next) => { + if (typeof req.cookies.order !== "undefined") { + req.data.form = { + "order-id": req.cookies.order, + }; + } + console.log(req.cookies); + next(); +}); +router.get("/", (req, res, next) => { + req.data.page = "order"; + req.data.css.push("/styles/orders/orders.css"); + res.render("key", req.data); +}); + +router.post("/", (req, res, next) => { + console.log(req.body["order-id"]); + let matches = req.body["order-id"].match(/^(INV_)?(\d{14})$/); + if (!matches) { + req.data.page = "order"; + req.data.css.push("/styles/orders/orders.css"); + req.data.error = 400; + req.data.form = { + "order-id": req.body["order-id"], + }; + res.render("key", req.data); + } else { + let order_id = matches[2]; + Order.LOAD_ORDER_FROM_ID(order_id) + .then(/** @param {Order} order */ (order) => order.getKeyFromOrderLink()) + .then((key) => { + if (key !== req.data.key.key) { + throw "Order is not connected to this key"; + } + res.redirect(`/key/${req.data.key.key}/orders/${order_id}`); + }) + .catch((reason) => { + req.data.page = "order"; + req.data.css.push("/styles/orders/orders.css"); + req.data.error = 404; + req.data.form = { + "order-id": req.body["order-id"], + }; + res.render("key", req.data); + }); + } +}); + +router.use("/:order_id", param("order_id").isInt(), (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(404).render("error", { + message: "Order not found", + error: { + status: 404, + }, + }); + } + Order.LOAD_ORDER_FROM_ID(req.params.order_id) + .then((order) => { + res.cookie("order", order.getOrderID()); + req.data.page = "order"; + req.data.css.push("/styles/orders/order.css"); + req.data.order = { order: order }; + req.data.links.order_url = `/key/${req.data.key.key}/orders/${req.params.order_id}#order`; + req.data.links.receipt_url = `/key/${req.data.key.key}/orders/${req.params.order_id}/pdf`; + req.data.links.invoice_url = `/key/${req.data.key.key}/orders/${req.params.order_id}/invoice#invoice-form`; + req.data.links.refund_url = `/key/${req.data.key.key}/orders/${req.params.order_id}/refund#refund-form`; + if (req.data.order.order.isReceiptCreated()) { + req.data.order.download_invoice_url = `/key/${ + req.data.key.key + }/orders/${req.data.order.order.getOrderID()}/invoice/download`; + } + next("route"); + }) + .catch((reason) => { + res.status(404).render("error", { + message: "Order not found", + error: { + status: 404, + }, + }); + }); +}); + +router.get("/:order_id", (req, res) => { + res.render("key", req.data); +}); + +router.get("/:order_id/pdf", (req, res) => { + OrderReceipt.CREATE_ORDER_RECEIPT(req.data.order.order) + .then((data) => { + res + .status(200) + .header({ + "Content-Type": "application/pdf", + }) + .send(Buffer.concat(data)); + }) + .catch((reason) => { + res.locals.error = { + status: 500, + stack: JSON.stringify(reason, null, 4), + }; + res.locals.message = "Error generating Order Receipt"; + res.status(500).render("error"); + }); +}); + +let invoiceRouter = require("./invoice"); +router.use("/:order_id/invoice", invoiceRouter); + +let refundRouter = require("./refund"); +router.use("/:order_id/refund", refundRouter); + +module.exports = router; diff --git a/pass/routes/orders/refund.js b/pass/routes/orders/refund.js new file mode 100644 index 0000000..13b7a44 --- /dev/null +++ b/pass/routes/orders/refund.js @@ -0,0 +1,24 @@ +var express = require("express"); +var router = express.Router({ mergeParams: true }); + +// Base URLÖ: /key/:key/orders/:order/refund +router.use("/", (req, res, next) => { + let refund_count = Math.min( + req.data.key.charge, + req.data.order.order.getAmount(), + 408 + ); + req.data.order.refund = { + count: refund_count, + amount: + (req.data.order.order.getPrice() / req.data.order.order.getAmount()) * + refund_count, + }; + req.data.css.push("/styles/orders/refund.css"); + next(); +}); +router.get("/", (req, res) => { + res.render("key", req.data); +}); + +module.exports = router; diff --git a/pass/views/orders/invoice.ejs b/pass/views/orders/invoice.ejs index 79343f4..2c584e0 100644 --- a/pass/views/orders/invoice.ejs +++ b/pass/views/orders/invoice.ejs @@ -5,20 +5,7 @@ <li><a href="<%= links.order_url %>"><%= order.order.getOrderID() %></a></li> <li>Rechnung</li> </ul> - <div id="order-details"> - <div class="heading item">Bestelldetails</div> - <div class="heading count">Anzahl</div> - <div class="heading price">Preis</div> - <div class="item">MetaGer Schlüssel: Suchanfragen (300x)</div> - <div class="count"><%= order.order.getAmount() / 300 %></div> - <div class="price"><%= order.order.getNettoPrice().toFixed(2) %> €</div> - <div class="item">MwSt. (<%= order.order.getVat() %> %)</div> - <div class="count"></div> - <div class="price"><%= order.order.getVatAmount().toFixed(2) %> €</div> - <div class="item sum">Gesamtbetrag</div> - <div class="count"></div> - <div class="price"><%= order.order.getPrice().toFixed(2) %> €</div> - </div> + <%- include("order_details") %> <form <% if(admin) { %>action="<%= order.invoice.create_invoice_url %>"<%_ } _%> method="POST" id="invoice-form"> <%_ if(!order.invoice.success) { _%> diff --git a/pass/views/orders/order.ejs b/pass/views/orders/order.ejs index 48d0fc0..b8e0e6b 100644 --- a/pass/views/orders/order.ejs +++ b/pass/views/orders/order.ejs @@ -1,45 +1,36 @@ <div id="order"> - <%_ if(!order.invoice) { _%> + <%_ if(!order.invoice && !order.refund) { _%> <h2>Ihre Bestellung Nr. <%= order.order.getOrderID() %></h2> <ul class="breadcrumbs"> <li><a href="<%= links.orders_url %>">Bestellungen</a></li> <li><%= order.order.getOrderID() %></li> </ul> - <div id="order-details"> - <div class="heading item">Bestelldetails</div> - <div class="heading count">Anzahl</div> - <div class="heading price">Preis</div> - <div class="item">MetaGer Schlüssel: Suchanfragen (300x)</div> - <div class="count"><%= order.order.getAmount() / 300 %></div> - <div class="price"><%= order.order.getNettoPrice().toFixed(2) %> €</div> - <div class="item">MwSt. (<%= order.order.getVat() %> %)</div> - <div class="count"></div> - <div class="price"><%= order.order.getVatAmount().toFixed(2) %> €</div> - <div class="item sum">Gesamtbetrag</div> - <div class="count"></div> - <div class="price"><%= order.order.getPrice().toFixed(2) %> €</div> - </div> + <%- include("order_details") %> <h3>Vielen Dank für Ihren Einkauf!</h3> <div id="order-buttons"> <a href="<%= links.receipt_url %>" target="_blank" class="button"> - <img src="/images/download.svg" alt="" /> + <img src="/images/download.svg" alt="" class="order-receipt" /> <span>Auftragsbestätigung herunterladen</span> </a> <%_ if(!order.order.isReceiptCreated()) { _%> <a href="<%= links.invoice_url %>" class="button"> <img src="/images/invoice.svg" alt="" /> - <span>Rechnung beantragen</span> + <span>Rechnung anfragen</span> </a> <%_ } else { _%> - <div id="download-receipt"> - <a href="<%= links.download_invoice_url %>" target="_blank" class="button"> - <img src="/images/invoice.svg" alt="" /> - <span>Rechnung herunterladen</span> - </a> - </div> + <a href="<%= links.download_invoice_url %>" target="_blank" class="button"> + <img src="/images/invoice.svg" alt="" /> + <span>Rechnung herunterladen</span> + </a> <%_ } _%> + <a href="<%= links.refund_url %>" class="button"> + <img src="/images/money.svg" alt="" /> + <span>Erstattung anfragen</span> + </a> </div> - <%_ } else { _%> + <%_ } else if(order.invoice) { _%> <%- include('./invoice', {}); %> + <%_ } else if(typeof order.refund !== "undefined") { _%> + <%- include('./refund') %> <%_ } _%> </div> \ No newline at end of file diff --git a/pass/views/orders/order_details.ejs b/pass/views/orders/order_details.ejs new file mode 100644 index 0000000..24a1cce --- /dev/null +++ b/pass/views/orders/order_details.ejs @@ -0,0 +1,14 @@ +<div id="order-details"> + <div class="heading item">Bestelldetails</div> + <div class="heading count">Anzahl</div> + <div class="heading price">Preis</div> + <div class="item">MetaGer Schlüssel: Suchanfragen (300x)</div> + <div class="count"><%= order.order.getAmount() / 300 %></div> + <div class="price"><%= order.order.getNettoPrice().toFixed(2) %> €</div> + <div class="item">MwSt. (<%= order.order.getVat() %> %)</div> + <div class="count"></div> + <div class="price"><%= order.order.getVatAmount().toFixed(2) %> €</div> + <div class="item sum">Gesamtbetrag</div> + <div class="count"></div> + <div class="price"><%= order.order.getPrice().toFixed(2) %> €</div> +</div> \ No newline at end of file diff --git a/pass/views/orders/refund.ejs b/pass/views/orders/refund.ejs new file mode 100644 index 0000000..7d370ac --- /dev/null +++ b/pass/views/orders/refund.ejs @@ -0,0 +1,21 @@ +<div id="refund"> + <h2>Erstattung</h2> + <ul class="breadcrumbs"> + <li><a href="<%= links.orders_url %>">Bestellungen</a></li> + <li><a href="<%= links.order_url %>"><%= order.order.getOrderID() %></a></li> + <li>Erstattung</li> + </ul> + <%- include("order_details") %> + <form id="refund-form"> + <p>Sind Sie unzufrieden mit Ihrem Schlüssel? Das bedauern wir sehr! Selbstverständlich erstatten wir Ihnen in diesem Fall den Rechnungsbetrag. Gerne nehmen wir auch Ihre Kritik entgegen.</p> + <%_ if(order.refund.count < order.order.getAmount()) { _%> + <p>Hinweis: Ein Teil Ihres gekauften Guthabens wurde bereits verbraucht. Wir können Ihnen deshalb lediglich <span class="bold"><%= order.refund.count %>/<%= order.order.getAmount() %></span> Suchanfragen erstatten.</p> + <%_ } _%> + <h3>Ihre Erstattung</h3> + <textarea name="message" id="message" cols="1" rows="10" placeholder="Ihre Nachricht (optional)" size="1" autofocus></textarea> + <button class="button"> + <img src="/images/money.svg" alt="" aria-hidden="true"> + <span><%= order.refund.amount.toFixed(2) %>€ Erstattung anfragen</span> + </button> + </form> +</div> \ No newline at end of file -- GitLab