diff --git a/checkout.css b/checkout.css index 093833c..fb1a169 100644 --- a/checkout.css +++ b/checkout.css @@ -5,105 +5,14 @@ body { display: flex; justify-content: center; align-items: center; - /* background-color: #F3F5F6; */ - /* height: 80vh; */ margin:0; + background-color: #F3F5F6; } form { width: 30vw; min-width: 500px; align-self: center; - /* padding: 10px; */ -} - -.hidden { - display: none; -} - -#payment-message { - color: rgb(105, 115, 134); - font-size: 16px; - line-height: 20px; - padding-top: 12px; - text-align: center; -} - -#payment-element { -} - -/* Buttons and links */ -button { - background: #5469d4; - font-family: Arial, sans-serif; - color: #ffffff; - border-radius: 4px; - border: 0; - padding: 12px 16px; - font-size: 16px; - font-weight: 600; - cursor: pointer; - display: block; - transition: all 0.2s ease; - box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); - width: 100%; -} -button:hover { - filter: contrast(115%); -} - -button:disabled { - opacity: 0.5; - cursor: default; -} - -/* spinner/processing state, errors */ -.spinner, -.spinner:before, -.spinner:after { - border-radius: 50%; -} -.spinner { - color: #ffffff; - font-size: 22px; - text-indent: -99999px; - margin: 0px auto; - position: relative; - width: 20px; - height: 20px; - box-shadow: inset 0 0 0 2px; - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); - transform: translateZ(0); -} -.spinner:before, -.spinner:after { - position: absolute; - content: ""; -} -.spinner:before { - width: 10.4px; - height: 20.4px; - background: #5469d4; - border-radius: 20.4px 0 0 20.4px; - top: -0.2px; - left: -0.2px; - -webkit-transform-origin: 10.4px 10.2px; - transform-origin: 10.4px 10.2px; - -webkit-animation: loading 2s infinite ease 1.5s; - animation: loading 2s infinite ease 1.5s; -} -.spinner:after { - width: 10.4px; - height: 10.2px; - background: #5469d4; - border-radius: 0 10.2px 10.2px 0; - top: -0.1px; - left: 10.2px; - -webkit-transform-origin: 0px 10.2px; - transform-origin: 0px 10.2px; - -webkit-animation: loading 2s infinite ease; - animation: loading 2s infinite ease; } pre { diff --git a/checkout.js b/checkout.js index 9baa77b..09d7f93 100644 --- a/checkout.js +++ b/checkout.js @@ -1,152 +1,11 @@ let elements; let stripe; -// initialize({ -// token: "", -// customerKey: "", -// locale: "en", -// amount: 2500, -// currency: "usd", -// }); - -// initStripe({ -// nativeAPI: true, -// showWebComponents: false, -// stripe: { -// publishableKey: "", -// options: { -// betas: ["elements_saved_payment_methods_beta_1"], -// locale: "en", -// }, -// elementOptions: { -// appearance: { -// theme: "stripe", -// variables: { -// fontSizeBase: "1rem", -// }, -// rules: { -// ".AccordionItem": { -// fontSize: "18px", -// }, -// ".Label": { -// fontSize: "16px", -// }, -// }, -// }, -// customerSessionClientSecret: "", -// amount: 2500, -// currency: "nzd", -// }, -// paymentElementsOptions: { -// savePaymentMethod: { -// maxVisiblePaymentMethods: 4, -// }, -// }, -// }, -// }); - -document - .querySelector("#payment-form") - .addEventListener("submit", handleSubmit); - -document.querySelector("#submit").classList.add("hidden"); -document.querySelector("#confirm").classList.add("hidden"); - -async function callAPI({ url, method, body }) { - const headers = { - "Content-Type": "application/json", - Authorization: window.authorization, - country: window.country, - }; - - return await fetch(`https://apim-na.dev.mypepsico.com/cgf/gpg/v1/${url}`, { - method, - headers, - body, - }) - .then((response) => response.json()) - .catch((error) => error); -} - -// Fetches a payment intent and captures the client secret -async function initialize({ - token, - country, - customerKey, - locale, - amount, - currency, -}) { - window.authorization = token; - window.country = country; - window.amount = amount; - - reactNativePostMessage({ - eventName: "initialize", - eventData: { token, customerKey, locale, amount, currency }, - }); - - const configResponse = await callAPI({ - url: "payments/config?application=test", - method: "GET", - }); - - reactNativePostMessage({ eventName: "config", eventData: configResponse }); - - const { publishableKey, error_msg } = configResponse; - - if (error_msg) { - alert(error_msg); - } - - const customerResponse = await callAPI({ - url: "payments/customer", - method: "POST", - body: JSON.stringify({ customerId: encodeURIComponent(customerKey) }), - }); - - reactNativePostMessage({ - eventName: "customer", - eventData: customerResponse, - }); - - const { clientSecret } = customerResponse; - - initStripe({ - nativeAPI: false, - stripe: { - publishableKey, - options: { - locale: locale, - }, - elementOptions: { - customerSessionClientSecret: clientSecret, - amount: amount, - currency: currency, - }, - paymentElementOptions: { - savePaymentMethod: { - maxVisiblePaymentMethods: 4, - }, - }, - }, - }); -} - -function initStripe({ - showWebComponents = true, - nativeAPI = true, - stripe: stripeObj = {}, -}) { +function initStripe({ stripe: stripeObj = {} }) { try { - window.nativeAPI = nativeAPI; - window.showWebComponents = showWebComponents; - reactNativePostMessage({ eventName: "initStripe", eventData: { - nativeAPI, - showWebComponents, stripe: stripeObj, }, }); @@ -169,7 +28,6 @@ function initStripe({ } if (missingOptions?.length > 0) { - setLoading(false); reactNativePostMessage({ eventName: "stripe.configuration.error", eventData: { @@ -222,18 +80,37 @@ function initStripe({ paymentElement.mount("#payment-element"); paymentElement.on("ready", function (_event) { reactNativePostMessage({ - eventName: "stripe.scrollHeight", + eventName: "stripe.event.ready", eventData: { - height: document.querySelector("#payment-element").scrollHeight, + scrollHeight: document.querySelector("#payment-element").scrollHeight, }, }); - if (showWebComponents) { - document.querySelector("#submit").classList.remove("hidden"); - } - setLoading(false); + }); + paymentElement.on("change", function (event) { + reactNativePostMessage({ + eventName: "stripe.event.change", + eventData: event, + }); + }); + paymentElement.on("click", function (event) { + reactNativePostMessage({ + eventName: "stripe.event.click", + eventData: event, + }); + }); + paymentElement.on("focus", function (event) { + reactNativePostMessage({ + eventName: "stripe.event.focus", + eventData: event, + }); + }); + paymentElement.on("blur", function (event) { + reactNativePostMessage({ + eventName: "stripe.event.blur", + eventData: event, + }); }); } catch (error) { - setLoading(false); reactNativePostMessage({ eventName: "stripe.configuration.error", eventData: { @@ -250,17 +127,16 @@ async function validateElements() { result: submitError?.message ? false : true, error: submitError?.message, }; + if (submitError) { reactNativePostMessage({ - eventName: "stripe.submit", + eventName: "stripe.validateElements", eventData: resultData, }); - showMessage(submitError); - setLoading(false); return resultData; } else { reactNativePostMessage({ - eventName: "stripe.submit", + eventName: "stripe.validateElements", eventData: resultData, }); return resultData; @@ -271,18 +147,22 @@ async function validateElements() { error: error?.message, }; reactNativePostMessage({ - eventName: "stripe.submit", + eventName: "stripe.validateElements", eventData: resultData, }); - showMessage(error); - setLoading(false); return resultData; } } async function getConfirmationToken() { + const commonPostMessage = (error) => { + reactNativePostMessage({ + eventName: "stripe.confirmationToken.error", + eventData: { error: error?.message }, + }); + }; + try { - setLoading(true); const { error, confirmationToken } = await stripe.createConfirmationToken({ elements, params: { @@ -297,12 +177,8 @@ async function getConfirmationToken() { }, }); - setLoading(false); if (error) { - reactNativePostMessage({ - eventName: "stripe.confirmationToken.error", - eventData: { error: error?.message }, - }); + commonPostMessage(error); } else { reactNativePostMessage({ eventName: "stripe.confirmationToken", @@ -310,169 +186,7 @@ async function getConfirmationToken() { }); } } catch (error) { - setLoading(false); - reactNativePostMessage({ - eventName: "stripe.confirmationToken.error", - eventData: { - error: error?.message, - }, - }); - } -} - -async function handleSubmit(e) { - e.preventDefault(); - setLoading(true); - - if (event.submitter.id == "confirm") { - return handleConfirm(); - } - - const isValid = await validateElements(); - - if (!isValid) { - return; - } - - const { error, confirmationToken } = await stripe.createConfirmationToken({ - elements, - params: { - payment_method_data: { - billing_details: { - address: { - country: null, - postal_code: null, - }, - }, - }, - }, - }); - - if (error) { - // This point is only reached if there's an immediate error when - // creating the ConfirmationToken. Show the error to your customer (for example, payment details incomplete) - reactNativePostMessage({ - eventName: "stripe.confirmationToken.error", - eventData: { error: error?.message }, - }); - setLoading(false); - return; - } - - window.confirmationToken = confirmationToken; - - setLoading(false); - document.querySelector("#payment-element").classList.add("hidden"); - document.querySelector("#submit").classList.add("hidden"); - document.querySelector("#confirm").classList.remove("hidden"); -} - -async function handleConfirm() { - let confirmationToken = window.confirmationToken; - - if (window.nativeAPI) { - reactNativePostMessage({ - eventName: "stripe.confirmationToken", - eventData: { confirmationToken: window.confirmationToken }, - }); - } else { - //TODO Call checkout to create paymentIntent and return client secret - const chargeObj = { - customerId: "cus_PwnJsViElBR9Ck", - totalAmount: window.amount, - paymentMethodType: "card", - currency: "usd", - captureFunds: true, - confirmationTokenId: confirmationToken.id, - }; - - const checkoutResponse = await callAPI({ - url: "payments/checkout", - method: "POST", - body: JSON.stringify(chargeObj), - }); - - handleConfirmResponse(checkoutResponse); - } -} - -function handleConfirmResponse(checkoutResponse) { - setLoading(false); - try { - const paymentResponse = - typeof checkoutResponse === "string" - ? JSON.parse(checkoutResponse) - : checkoutResponse; - checkStatus(paymentResponse); - } catch (_error) {} -} - -// Fetches the payment intent status after payment submission -async function checkStatus(paymentResponse) { - const clientSecret = paymentResponse?.clientSecret; - - if (!clientSecret) { - return; - } - - switch (paymentResponse?.status) { - case "requires_capture": - document.getElementById("confirm").classList.add("hidden"); - showMessage("Payment succeeded!"); - break; - case "succeeded": - showMessage("Payment succeeded!"); - break; - case "processing": - showMessage("Your payment is processing."); - break; - case "requires_payment_method": - showMessage("Your payment was not successful, please try again."); - break; - default: - showMessage("Something went wrong."); - break; - } -} - -// ------- UI helpers ------- - -function showMessage(messageText) { - if (window.showWebComponents) { - const messageContainer = document.querySelector("#payment-message"); - - messageContainer.classList.remove("hidden"); - messageContainer.textContent = messageText; - - setTimeout(function () { - messageContainer.classList.add("hidden"); - messageContainer.textContent = ""; - }, 10000); - } -} - -// Show a spinner on payment submission -function setLoading(isLoading) { - reactNativePostMessage({ - eventName: "stripe.isLoading", - eventData: isLoading, - }); - - if (isLoading) { - // Disable the button and show a spinner - document.querySelector("#submit").disabled = true; - document.querySelector("#confirm").disabled = true; - document.querySelector("#spinner").classList.remove("hidden"); - document.querySelector("#spinner-confirm").classList.remove("hidden"); - document.querySelector("#button-text").classList.add("hidden"); - document.querySelector("#button-confirm-text").classList.add("hidden"); - } else { - document.querySelector("#submit").disabled = false; - document.querySelector("#confirm").disabled = false; - document.querySelector("#spinner").classList.add("hidden"); - document.querySelector("#spinner-confirm").classList.add("hidden"); - document.querySelector("#button-text").classList.remove("hidden"); - document.querySelector("#button-confirm-text").classList.remove("hidden"); + commonPostMessage(error); } } diff --git a/index.html b/index.html index 6f74270..6c564c0 100644 --- a/index.html +++ b/index.html @@ -3,33 +3,17 @@ - - - Accept a payment - + -
- -
- - - -