From 6af8519a7dbc1c363eff1126e0a42b207765f8cf Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@suma-ev.de> Date: Wed, 7 Dec 2022 14:50:07 +0100 Subject: [PATCH] added paypal payment buttons --- pass/public/styles/key/checkout-amount.less | 82 ++++++ pass/public/styles/key/checkout-payment.less | 46 ++++ pass/public/styles/key/key.css | 1 + pass/public/styles/{ => key}/key.less | 81 +----- pass/resources/js/checkout.js | 267 ++----------------- pass/routes/key.js | 64 ++++- pass/views/key.ejs | 47 ++-- 7 files changed, 251 insertions(+), 337 deletions(-) create mode 100644 pass/public/styles/key/checkout-amount.less create mode 100644 pass/public/styles/key/checkout-payment.less create mode 100644 pass/public/styles/key/key.css rename pass/public/styles/{ => key}/key.less (51%) diff --git a/pass/public/styles/key/checkout-amount.less b/pass/public/styles/key/checkout-amount.less new file mode 100644 index 0000000..990b70b --- /dev/null +++ b/pass/public/styles/key/checkout-amount.less @@ -0,0 +1,82 @@ +#checkout { + > h2 { + margin: 0 0 1rem; + text-align: center; + border-bottom: 1px solid @color-main; + } + #checkout-amount { + @checkout-amount-breakpoint-1: 800px; + @checkout-amount-breakpoint-2: 470px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + &.single { + grid-template-columns: 1fr; + } + gap: 1rem; + justify-items: center; + align-items: center; + > a { + text-decoration: none; + color: inherit; + display: grid; + width: max-content; + grid-template-columns: 7em 3.5em; + grid-template-rows: 2.5em 2em; + height: max-content; + > .checkout-amount { + font-size: 2rem; + font-weight: bold; + line-height: 1; + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + border-top-left-radius: 10px; + border: 1px solid rgb(255, 127, 0); + border-bottom: 0; + border-right: 0; + } + > .checkout-duration { + width: 100%; + height: 100%; + border-bottom-left-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid rgb(255, 127, 0); + border-top: 0; + border-right: 0; + } + > .checkout-cost { + grid-row: span 2; + justify-content: center; + background-color: rgb(255, 127, 0); + color: white; + font-size: 1.7rem; + align-items: center; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + border: 1px solid rgb(255, 127, 0); + border-left: 0; + display: flex; + width: 100%; + height: 100%; + align-self: center; + } + } + @media (max-width: @checkout-amount-breakpoint-1) { + grid-template-columns: 1fr 1fr; + &.single { + grid-template-columns: 1fr; + } + } + @media (max-width: @checkout-amount-breakpoint-2) { + grid-template-columns: 1fr; + > a { + width: 100%; + grid-template-columns: 1fr 3em; + } + } + } +} diff --git a/pass/public/styles/key/checkout-payment.less b/pass/public/styles/key/checkout-payment.less new file mode 100644 index 0000000..fc18c38 --- /dev/null +++ b/pass/public/styles/key/checkout-payment.less @@ -0,0 +1,46 @@ +#payment { + margin-bottom: 1rem; + @payment-group-breakpoint-1: 1050px; + @payment-group-breakpoint-2: 580px; + > h2 { + margin: 0 0 1rem; + text-align: center; + border-bottom: 1px solid @color-main; + } + + > label[for="payment-group-paypal"] { + border: 1px solid #777; + display: block; + padding: 0.5rem; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background-color: #f0f0f0; + border-bottom-color: rgb(255, 127, 0); + border-bottom-width: 2px; + } + > #payment-group-paypal { + display: none; + } + > .payment-group { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + align-items: center; + justify-items: center; + gap: 1rem; + border: 1px solid #777; + padding: 1rem; + border-top: 0; + > div { + width: 14em; + } + @media (max-width: @payment-group-breakpoint-1) { + grid-template-columns: 1fr 1fr; + } + @media (max-width: @payment-group-breakpoint-2) { + grid-template-columns: 1fr; + > div { + width: 100%; + } + } + } +} diff --git a/pass/public/styles/key/key.css b/pass/public/styles/key/key.css new file mode 100644 index 0000000..847b32a --- /dev/null +++ b/pass/public/styles/key/key.css @@ -0,0 +1 @@ +#checkout>h2{margin:0 0 1rem;text-align:center;border-bottom:1px solid #ff7f00}#checkout #checkout-amount{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;justify-items:center;align-items:center}#checkout #checkout-amount.single{grid-template-columns:1fr}#checkout #checkout-amount>a{text-decoration:none;color:inherit;display:grid;width:max-content;grid-template-columns:7em 3.5em;grid-template-rows:2.5em 2em;height:max-content}#checkout #checkout-amount>a>.checkout-amount{font-size:2rem;font-weight:bold;line-height:1;display:flex;width:100%;height:100%;align-items:center;justify-content:center;border-top-left-radius:10px;border:1px solid #ff7f00;border-bottom:0;border-right:0}#checkout #checkout-amount>a>.checkout-duration{width:100%;height:100%;border-bottom-left-radius:10px;display:flex;align-items:center;justify-content:center;border:1px solid #ff7f00;border-top:0;border-right:0}#checkout #checkout-amount>a>.checkout-cost{grid-row:span 2;justify-content:center;background-color:#ff7f00;color:white;font-size:1.7rem;align-items:center;border-top-right-radius:10px;border-bottom-right-radius:10px;border:1px solid #ff7f00;border-left:0;display:flex;width:100%;height:100%;align-self:center}@media (max-width:800px){#checkout #checkout-amount{grid-template-columns:1fr 1fr}#checkout #checkout-amount.single{grid-template-columns:1fr}}@media (max-width:470px){#checkout #checkout-amount{grid-template-columns:1fr}#checkout #checkout-amount>a{width:100%;grid-template-columns:1fr 3em}}#payment{margin-bottom:1rem}#payment>h2{margin:0 0 1rem;text-align:center;border-bottom:1px solid #ff7f00}#payment>label[for="payment-group-paypal"]{border:1px solid #777;display:block;padding:.5rem;border-top-left-radius:5px;border-top-right-radius:5px;background-color:#f0f0f0;border-bottom-color:#ff7f00;border-bottom-width:2px}#payment>#payment-group-paypal{display:none}#payment>.payment-group{display:grid;grid-template-columns:1fr 1fr 1fr;align-items:center;justify-items:center;gap:1rem;border:1px solid #777;padding:1rem;border-top:0}#payment>.payment-group>div{width:14em}@media (max-width:1050px){#payment>.payment-group{grid-template-columns:1fr 1fr}}@media (max-width:580px){#payment>.payment-group{grid-template-columns:1fr}#payment>.payment-group>div{width:100%}}main{max-width:980px;margin:0 auto;display:grid;row-gap:1rem;padding-right:1rem;grid-template-columns:auto 1fr;grid-template-rows:auto 1fr auto auto;grid-template-areas:"qr key " "qr setting-url" "qr buttons" "amount charge"}main>#qr{grid-area:qr;justify-self:center}main #key{grid-area:key;font-size:clamp(.9rem, 4.8vw, 1.5rem);font-weight:bold;width:max-content;margin-top:11px;margin-right:1rem}main #setting-url{grid-area:setting-url;align-self:start;display:flex;padding:.5rem 1rem;border-radius:5px}main #setting-url>input{line-height:1.5;padding:.1rem .5rem;border-radius:5px;flex-grow:1}main>#buttons{grid-area:buttons;display:flex;gap:1rem;margin-bottom:17px;justify-content:flex-end}@media (max-width:435px){main>#buttons{display:grid;grid-template-columns:1fr}main>#buttons .button{width:auto}main>#buttons>*{display:grid;justify-items:center;text-align:center}}main>#amount{grid-area:amount;max-width:200px;justify-self:center;display:flex;flex-direction:column;align-items:center}main>#amount>h3{font-size:1.5rem;margin:0}main>#amount>div.amount{font-size:3rem}main>#charge{grid-area:charge}main>#charge>#store>p{line-height:1.5}@media (max-width:770px){main{padding:0 1rem;grid-template-rows:auto auto auto auto auto;grid-template-columns:auto auto;grid-template-areas:"amount qr " "key key" "setting-url setting-url" "buttons buttons " "charge charge "}main>#key{margin:0;justify-self:center}main>#setting-url{text-align:center}main>#buttons{justify-content:center;margin-bottom:0}main>#amount{align-self:center}main>#charge>#store{display:flex;flex-direction:column;align-items:center}}@media (max-width:430px){main{grid-template-rows:auto auto auto auto auto auto;grid-template-columns:auto;grid-template-areas:"qr" "key" "setting-url" "buttons" "amount" "charge"}} \ No newline at end of file diff --git a/pass/public/styles/key.less b/pass/public/styles/key/key.less similarity index 51% rename from pass/public/styles/key.less rename to pass/public/styles/key/key.less index 6bff593..907fd71 100644 --- a/pass/public/styles/key.less +++ b/pass/public/styles/key/key.less @@ -1,4 +1,6 @@ -@import "./misc/vars.less"; +@import "../misc/vars.less"; +@import "./checkout-amount.less"; +@import "./checkout-payment.less"; @max-width: 980px; @key-breakpoint-1: 770px; @@ -92,83 +94,6 @@ main { line-height: 1.5; } } - - > #checkout { - > h2 { - margin: 0 0 1rem; - text-align: center; - border-bottom: 1px solid @color-main; - } - #checkout-amount { - @checkout-amount-breakpoint-1: 800px; - @checkout-amount-breakpoint-2: 470px; - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: 1rem; - justify-items: center; - align-items: center; - > a { - text-decoration: none; - color: inherit; - display: grid; - width: max-content; - grid-template-columns: 7em 3.5em; - grid-template-rows: 2.5em 2em; - height: max-content; - > .checkout-amount { - font-size: 2rem; - font-weight: bold; - line-height: 1; - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - border-top-left-radius: 10px; - border: 1px solid rgb(255, 127, 0); - border-bottom: 0; - border-right: 0; - } - > .checkout-duration { - width: 100%; - height: 100%; - border-bottom-left-radius: 10px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid rgb(255, 127, 0); - border-top: 0; - border-right: 0; - } - > .checkout-cost { - grid-row: span 2; - justify-content: center; - background-color: rgb(255, 127, 0); - color: white; - font-size: 1.7rem; - align-items: center; - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; - border: 1px solid rgb(255, 127, 0); - border-left: 0; - display: flex; - width: 100%; - height: 100%; - align-self: center; - } - } - @media (max-width: @checkout-amount-breakpoint-1) { - grid-template-columns: 1fr 1fr; - } - @media (max-width: @checkout-amount-breakpoint-2) { - grid-template-columns: 1fr; - > a { - width: 100%; - grid-template-columns: 1fr 3em; - } - } - } - } } @media (max-width: @key-breakpoint-1) { diff --git a/pass/resources/js/checkout.js b/pass/resources/js/checkout.js index 111b137..fad6fc3 100644 --- a/pass/resources/js/checkout.js +++ b/pass/resources/js/checkout.js @@ -1,242 +1,29 @@ -const uuid_generator = require("uuid"); -const BlindSignature = require("blind-signatures"); - -var metager_pass_order_id = null; -var metager_pass_expires_at = null; -var metager_pass_sales_receipts = []; -var metager_pass_encrypted_sales_receipts = []; // Stores the encrypted sales receipts that get signed by our server after successful purchase -var metager_pass_encrypted_sales_receipts_r = []; // Those are the secrets for decrypting the signed sales_receipts; Should never leave the clients computer -var metager_pass_signatures = []; - -one_generate_encrypted_sales_receipt(); - -function one_generate_encrypted_sales_receipt() { - let current_step_container = document.getElementById( - "generate-sales-receipt" - ); - let next_step_container = document.getElementById("execute-payment"); - - let amount = parseInt(document.querySelector("input[name=amount]").value); - let unit_size = parseInt( - document.querySelector("input[name=unit_size]").value - ); - let N = document.querySelector("input[name=public_key_n]").value; - let E = document.querySelector("input[name=public_key_e]").value; - - let ticket_count = (amount * unit_size) / 50; - if (ticket_count % 1 !== 0) { - console.error( - "This should not happen. Ticket count should only produce integer values" - ); - return; - } - - for (let i = 0; i < ticket_count; i++) { - let uuid = uuid_generator.v4(); - metager_pass_sales_receipts.push(uuid); - let { blinded, r } = BlindSignature.blind({ - message: uuid, - N: N, - E: E, - }); - metager_pass_encrypted_sales_receipts.push(blinded.toString()); - metager_pass_encrypted_sales_receipts_r.push(r.toString()); - } - - current_step_container.classList.remove("current"); - current_step_container.classList.add("finished"); - next_step_container.classList.add("current"); - - two_execute_payment(); -} - -function two_execute_payment() { - let current_step_container = document.getElementById("execute-payment"); - let development_handler_button = document.getElementById( - "payment_method_development" - ); - if (development_handler_button) { - let development_handler = require("./checkout_development"); - development_handler_button.addEventListener("click", () => { - development_handler(metager_pass_encrypted_sales_receipts); - }); - } - - let paypal_handler = require("./checkout_paypal"); // Add handler for PayPal Checkout - document - .getElementById("payment_method_paypal") - .addEventListener("click", () => { - paypal_handler(metager_pass_encrypted_sales_receipts); - }); - - current_step_container.addEventListener("payment-complete", (e) => { - current_step_container.classList.remove("current"); - current_step_container.classList.add("finished"); - three_verify_and_decrypt(e.detail); - }); -} - -function three_verify_and_decrypt(signatures) { - let current_step_container = document.getElementById("verify-and-decrypt"); - let next_step_container = document.getElementById("finish-purchase"); - - current_step_container.classList.add("current"); - metager_pass_order_id = signatures.order_id; - let dayjs = require("dayjs"); - metager_pass_expires_at = dayjs(signatures.expires_at).format("YYYY-MM"); - - let N = document.querySelector("input[name=public_key_n]").value; - let E = document.querySelector("input[name=public_key_e]").value; - // Decrypting/Unblinding Signatures - for (let i = 0; i < signatures.signatures.length; i++) { - metager_pass_signatures.push( - BlindSignature.unblind({ - signed: signatures.signatures[i], - N: N, - r: metager_pass_encrypted_sales_receipts_r[i], - }).toString() - ); - } - - // Verify Signatures - // ToDo do something with invalid signatures - for (let i = 0; i < metager_pass_sales_receipts.length; i++) { - let verification = BlindSignature.verify({ - unblinded: metager_pass_signatures[i], - N: N, - E: E, - message: metager_pass_sales_receipts[i], - }); - console.log(metager_pass_sales_receipts[i] + " => " + verification); - } - - current_step_container.classList.remove("current"); - current_step_container.classList.add("finished"); - four_finish_purchase(); -} - -function four_finish_purchase() { - let current_step_container = document.getElementById("finish-purchase"); - current_step_container.classList.add("current"); - - // Prepare Voucher download - let voucher_link = document.getElementById("voucher-link"); - let voucher_data = { - expiration_month: metager_pass_expires_at, - receipts: [], - }; - for (let i = 0; i < metager_pass_sales_receipts.length; i++) { - voucher_data.receipts.push({ - receipt: metager_pass_sales_receipts[i], - signature: metager_pass_signatures[i], - }); - } - voucher_data = - "data:application/mgpass;charset=utf-8;base64," + - btoa(JSON.stringify(voucher_data, null, 4)); - voucher_link.href = voucher_data; - voucher_link.download = "mgpass_coupon.json"; - - // Create redeem data - let order_month = require("dayjs") - .unix(metager_pass_order_id.substr(0, 10)) - .format("YYYY-MM"); - let redeem_data = { - expiration_month: metager_pass_expires_at, - generation_month: order_month, - metager_pass_codes: [], - }; - for (let i = 0; i < metager_pass_sales_receipts.length; i++) { - redeem_data.metager_pass_codes.push({ - code: metager_pass_sales_receipts[i], - signature: metager_pass_signatures[i], - }); - } - - // Make Create Key button work - let create_key_button = document.getElementById("create-key-button"); - create_key_button.addEventListener("pointerdown", e => { - if (e.target.disabled) { - return; - } else { - e.target.disabled = true; - } - - fetch("/redeem/create", { - method: "POST", - headers: { - "Content-Type": "application/json;charset=utf-8", - }, - body: JSON.stringify(redeem_data), - }).then(response => { - if (!response.ok) { - return response.json().then(json_response => { - console.error(json_response); - for (let i = 0; i < json_response.errors.length; i++) { - let error_message = document.createElement("li"); - error_message.textContent = json_response.errors[i].msg - document.querySelector("#create-key .errors ul").appendChild(error_message); - } - e.target.disabled = false; - return Promise.reject("Error while creating a new MetaGer-Pass Key"); - }); - } else { - return response.json() - } - }) - .then(response => { - let key = response.metager_pass_key; - // Prefill the recharge existing key button - let metager_pass_key_input = document.getElementById("metager-pass-key"); - metager_pass_key_input.value = key.key; - document.getElementById("metager-pass-key").addEventListener("change", e => { - // When the user changes the generated key unlock the generate button again - create_key_button.disabled = false; - }); - // Select The recharge existing key tab and show the correct text - document.getElementById("recharge-existing-key").classList.add("hidden"); - document.getElementById("recharge-new-key").classList.remove("hidden"); - document.getElementById("recharge-key-radio").checked = true; - }); - }); - - // Make Redeem Button Work - let redeem_button = document.getElementById("recharge-key-button"); - redeem_button.addEventListener("pointerdown", e => { - if (e.target.disabled) { - return; - } else { - e.target.disabled = true; - document.getElementById("metager-pass-key").disabled = true; - } - let post_data = { ...redeem_data }; - post_data.metager_pass_key = document.getElementById("metager-pass-key").value; - fetch("/redeem", { - method: "POST", - headers: { - "Content-Type": "application/json;charset=utf-8", - }, - body: JSON.stringify(post_data), - }).then(response => response.json()) - .then(response => { - // Show Summary and fill data for it - let dayjs = require('dayjs'); - document.querySelector("#redeem-successful .key").textContent = response.metager_pass_key.key; - document.querySelector("#redeem-successful .searches").textContent = response.metager_pass_key.searches; - document.querySelector("#redeem-successful .valid-until").textContent = dayjs(response.metager_pass_key.expire_at).format("DD.MM.YYYY HH:mm:ss"); - current_step_container.classList.remove("current"); - current_step_container.classList.add("finished"); - five_summary(); - console.log(response); - }).catch(reason => { - e.target.disabled = false; - document.getElementById("metager-pass-key").disabled = false; - console.error(reason); +const paypal_client = require("@paypal/paypal-js"); +console.log(paypal_client); + +paypal_client + .loadScript({ + "client-id": document.querySelector("input[name=paypal-client-id]").value, + components: ["buttons", "funding-eligibility"], + }) + .then((paypal) => { + paypal.getFundingSources().forEach((fundingSource) => { + let button = paypal.Buttons({ + style: { + color: "white", + height: 50, + }, + fundingSource: fundingSource, }); + if (button.isEligible()) { + console.log("eligible"); + let funding_source_element = document.createElement("div"); + funding_source_element.classList.add("funding_source"); + funding_source_element.id = fundingSource; + document + .getElementById("paypal-payments") + .appendChild(funding_source_element); + button.render("#" + fundingSource); + } + }); }); -} - -function five_summary() { - let current_step_container = document.getElementById("summary"); - current_step_container.classList.add("current"); -} diff --git a/pass/routes/key.js b/pass/routes/key.js index 43382dc..7f3938b 100644 --- a/pass/routes/key.js +++ b/pass/routes/key.js @@ -1,5 +1,7 @@ var express = require("express"); var router = express.Router(); +const { param, validationResult } = require("express-validator"); +const config = require("config"); var Key = require("../app/Key"); @@ -9,8 +11,65 @@ router.get("/create", function (req, res, next) { }); }); -router.get("/:key/:amount?", async (req, res) => { +router.use("/:key", param("key").isUUID(4), (req, res, next) => { + // Input Validation + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + next("route"); +}); + +// Basic account page +router.get("/:key", async (req, res) => { let key = req.params.key; + let metager_url = + "https://metager.de/meta/settings/load-settings?key=" + + encodeURIComponent(key); + let QRCode = require("qrcode"); + + let qr_data_uri = await QRCode.toDataURL(metager_url); + res.render("key", { + created_new: req.query.new === "true" ? true : false, + key: { + key: key, + settings_url: metager_url, + qr: qr_data_uri, + }, + js: [], + }); +}); + +/** + * Validate Amount field for checkout process + */ +router.use( + "/:key/checkout/:amount?", + param("amount") + .optional({ checkFalsy: true }) + .isInt() + .toInt() + .isIn([300, 600, 900, 1200, 1800, 3600]), + (req, res, next) => { + // Input Validation + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + next("route"); + } +); + +router.get("/:key/checkout/:amount?", async (req, res) => { + let key = req.params.key; + let checkout_data = { + amount: req.params.amount === 0 ? undefined : req.params.amount, + paypal: { + client_id: config.get( + `payments.paypal.${process.env.NODE_ENV}.client_id` + ), + }, + }; let metager_url = "https://metager.de/meta/settings/load-settings?key=" + @@ -18,7 +77,6 @@ router.get("/:key/:amount?", async (req, res) => { let QRCode = require("qrcode"); let qr_data_uri = await QRCode.toDataURL(metager_url); - console.log(req.query.new === "true"); res.render("key", { created_new: req.query.new === "true" ? true : false, key: { @@ -26,6 +84,8 @@ router.get("/:key/:amount?", async (req, res) => { settings_url: metager_url, qr: qr_data_uri, }, + js: checkout_data.amount !== undefined ? ["/js/checkout.js"] : [], + checkout: checkout_data, }); }); diff --git a/pass/views/key.ejs b/pass/views/key.ejs index f5daf02..9b23df9 100644 --- a/pass/views/key.ejs +++ b/pass/views/key.ejs @@ -1,4 +1,4 @@ -<%- include('templates/page_header', {css: ["/styles/key.css"], js: []}); %> +<%- include('templates/page_header', {css: ["/styles/key/key.css"], js: js}); %> <img id="qr" src="<%= key.qr %> "></img> <div id="key"> @@ -24,9 +24,8 @@ <div class="amount">0</div> <div>Suchanfragen</div> </div> - <div id="charge"> - <% if (created_new) { %> + <%_ if (created_new) { _%> <div id="store"> <h2>So gehts weiter:</h2> <p> @@ -37,51 +36,65 @@ </p> <a class="button" href="/key/<%= key.key %>">Schlüssel jetzt aufladen</a> </div> - <% }else { %> + <%_ }else { _%> <div id="checkout"> <h2>Suchanfragen auffüllen</h2> <div id="charge-steps"> <div id="charge-steps-amount"></div> </div> - <% if (typeof checkout === "undefined" || !checkout.amount) { %> - <div id="checkout-amount"> - <a href="/key/<%= key.key %>/300"> + <div id="checkout-amount" <%_ if (typeof checkout !== "undefined" && typeof checkout.amount !== "undefined") { _%>class="single"<%_ } _%>> + <%_ if (typeof checkout !== "undefined" && typeof checkout.amount !== "undefined") { _%> + <a href="#checkout-amount"> + <span class="checkout-amount"><%= checkout.amount %></span> + <span class="checkout-cost"><%= checkout.amount / 300 * 5%>€</span> + <span class="checkout-duration">~<%= checkout.amount / 300 %> <% if(checkout.amount > 300) { %> Monate<% }else { %>Monat<% } %>*</span> + </a> + <%_ }else { _%> + <a href="/key/<%= key.key %>/checkout/300#payment"> <span class="checkout-amount">300</span> <span class="checkout-cost">5€</span> <span class="checkout-duration">~1 Monat*</span> </a> - <a href="/key/<%= key.key %>/600"> + <a href="/key/<%= key.key %>/checkout/600#payment"> <span class="checkout-amount">600</span> <span class="checkout-cost">10€</span> <span class="checkout-duration">~2 Monate*</span> </a> - <a href="/key/<%= key.key %>/900"> + <a href="/key/<%= key.key %>/checkout/900#payment"> <span class="checkout-amount">900</span> <span class="checkout-cost">15€</span> <span class="checkout-duration">~3 Monate*</span> </a> - <a href="/key/<%= key.key %>/1200"> + <a href="/key/<%= key.key %>/checkout/1200#payment"> <span class="checkout-amount">1200</span> <span class="checkout-cost">20€</span> <span class="checkout-duration">~4 Monate*</span> </a> - <a href="/key/<%= key.key %>/1800"> + <a href="/key/<%= key.key %>/checkout/1800#payment"> <span class="checkout-amount">1800</span> <span class="checkout-cost">30€</span> <span class="checkout-duration">~6 Monate*</span> </a> - <a href="/key/<%= key.key %>/3600"> + <a href="/key/<%= key.key %>/checkout/3600#payment"> <span class="checkout-amount">3600</span> <span class="checkout-cost">60€</span> <span class="checkout-duration">~1 Jahr*</span> </a> + <%_ } _%> </div> <p>* Die angegebenen Zeiträume sind Schätzungen, die auf unseren Erfahrungswerten basieren und sollen einen Anhaltspunkt geben, wie viele Suchanfragen benötigt werden.</p> - <% } %> </div> - <% } %> + <%_ if (typeof checkout !== "undefined" && typeof checkout.amount !== "undefined") { _%> + <div id="payment"> + <h2>Zahlung durchführen</h2> + <input type="hidden" name="paypal-client-id" value="<%= checkout.paypal.client_id %>"> + <label for="payment-group-paypal"> + <div class="info">*PayPal, Kreditkarte, Girokarte, ...</div> + </label> + <input type="radio" name="payment-group" id="payment-group-paypal" selected> + <div id="paypal-payments" class="payment-group"></div> + </div> + <%_ } _%> + <%_ } _%> </div> - - - <%- include('templates/page_footer'); -%> \ No newline at end of file -- GitLab