From 1ff85e982ae28fd5e0fc0118375ec92a1b8ad25b Mon Sep 17 00:00:00 2001 From: tavo-wasd Date: Sun, 18 Aug 2024 01:16:13 -0600 Subject: [PATCH] better ui, paypal order ready, subscription still missing --- main.go | 59 +++++++++++++++ public/form.js | 122 ++++++++++++++++++++++-------- public/index.html | 39 ++++++---- public/static/css/style.css | 20 ++++- public/static/js/config.editor.js | 12 +-- public/static/svg/cart.svg | 5 ++ public/static/svg/x.svg | 12 +++ public/static/svg/xd.svg | 13 ++++ 8 files changed, 229 insertions(+), 53 deletions(-) create mode 100644 public/static/svg/cart.svg create mode 100644 public/static/svg/x.svg create mode 100644 public/static/svg/xd.svg diff --git a/main.go b/main.go index 30df483..5900b65 100644 --- a/main.go +++ b/main.go @@ -10,10 +10,24 @@ import ( "os/signal" "syscall" "bytes" + // "time" "github.com/joho/godotenv" ) +type OrderData struct { + ID string `json:"id"` + Status string `json:"status"` + PurchaseUnits []struct { + Payments struct { + Captures []struct { + ID string `json:"id"` + Status string `json:"status"` + } `json:"captures"` + } `json:"payments"` + } `json:"purchase_units"` +} + var ( baseURL = "https://api-m.sandbox.paypal.com" ) @@ -27,6 +41,7 @@ func init() { func main() { // Handlers http.HandleFunc("/api/orders", CreateOrder) + http.HandleFunc("/api/orders/", CaptureOrder) http.Handle("/", http.FileServer(http.Dir("./public"))) // Channel to listen for signals @@ -121,3 +136,47 @@ func CreateOrder(w http.ResponseWriter, r *http.Request) { return } } + +func CaptureOrder(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/api/orders/") + parts := strings.Split(path, "/") + orderID := parts[0] + + client := &http.Client{} + req, err := http.NewRequest("POST", baseURL+"/v2/checkout/orders/"+orderID+"/capture", nil) + if err != nil { + http.Error(w, "Failed to create request", http.StatusInternalServerError) + return + } + + token, err := Token() + if err != nil { + http.Error(w, "Failed to get access token", http.StatusInternalServerError) + return + } + + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + http.Error(w, "Failed to send request", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + // Create an instance of AutoGenerated + var result OrderData + + // Decode the response into the AutoGenerated struct + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + http.Error(w, "Failed to decode response", http.StatusInternalServerError) + return + } + + // Now, `result` contains the entire structured response + // You can send the whole `result` back to the client, or you can selectively send fields. + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(result); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + } +} diff --git a/public/form.js b/public/form.js index 9249033..cc87420 100644 --- a/public/form.js +++ b/public/form.js @@ -46,10 +46,10 @@ function togglePaymentMethod(selectedButtonId) { if (selectedButtonId === 'showOneTimeButton') { document.getElementById('paypal-button-container').classList.add('active'); - document.getElementById('paypalOneTimeButton').classList.add('active'); + document.getElementById('paypal-button-container-order').classList.add('active'); } else if (selectedButtonId === 'showSubButton') { document.getElementById('paypal-button-container').classList.add('active'); - document.getElementById('paypalSubButton').classList.add('active'); + document.getElementById('paypal-button-container-subscribe').classList.add('active'); } } @@ -57,37 +57,99 @@ function isFormValid(form) { return form.checkValidity(); } -paypal_onetime.Buttons({ +window.paypal_order.Buttons({ style: { shape: 'pill', color: 'black', layout: 'vertical', label: 'pay' }, - createOrder: function(data, actions) { - return actions.order.create({ - intent: 'CAPTURE', - purchase_units: [{ - amount: { - currency_code: 'USD', - value: '20.00' - } - }] - }); - }, - onApprove: function(data, actions) { - return actions.order.capture().then(function(details) { - alert('Transaction completed by ' + details.payer.name.given_name); - }); - } -}).render("#paypalOneTimeButton"); + async createOrder() { + try { + const response = await fetch("/api/orders", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + // use the "body" param to optionally pass additional order information + // like product ids and quantities + body: JSON.stringify({ + cart: [ + { + id: "YOUR_PRODUCT_ID", + quantity: "YOUR_PRODUCT_QUANTITY", + }, + ], + }), + }); -paypal_subscribe.Buttons({ - style: { shape: 'pill', color: 'black', layout: 'vertical', label: 'subscribe' }, - createSubscription: function(data, actions) { - return actions.subscription.create({ - plan_id: PlanID - }); + const orderData = await response.json(); + + if (orderData.id) { + return orderData.id; + } else { + const errorDetail = orderData?.details?.[0]; + const errorMessage = errorDetail + ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})` + : JSON.stringify(orderData); + + throw new Error(errorMessage); + } + } catch (error) { + console.error(error); + resultMessage(`Could not initiate PayPal Checkout...

${error}`); + } }, - onApprove: function(data, actions) { - alert(data.subscriptionID); // You can add optional success message for the subscriber here - } -}).render('#paypalSubButton'); + async onApprove(data, actions) { + try { + const response = await fetch(`/api/orders/${data.orderID}/capture`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + + const orderData = await response.json(); + // Three cases to handle: + // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart() + // (2) Other non-recoverable errors -> Show a failure message + // (3) Successful transaction -> Show confirmation or thank you message + + const errorDetail = orderData?.details?.[0]; + + if (errorDetail?.issue === "INSTRUMENT_DECLINED") { + // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart() + // recoverable state, per https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/ + return actions.restart(); + } else if (errorDetail) { + // (2) Other non-recoverable errors -> Show a failure message + throw new Error(`${errorDetail.description} (${orderData.debug_id})`); + } else if (!orderData.purchase_units) { + throw new Error(JSON.stringify(orderData)); + } else { + // (3) Successful transaction -> Show confirmation or thank you message + // Or go to another URL: actions.redirect('thank_you.html'); + const transaction = + orderData?.purchase_units?.[0]?.payments?.captures?.[0] || + orderData?.purchase_units?.[0]?.payments?.authorizations?.[0]; + resultMessage( + `Transaction ${transaction.status}: ${transaction.id}

See console for all available details`, + ); + console.log( + "Capture result", + orderData, + JSON.stringify(orderData, null, 2), + ); + } + } catch (error) { + console.error(error); + resultMessage( + `Sorry, your transaction could not be processed...

${error}`, + ); + } + }, +}).render("#paypal-button-container-order"); + +// Example function to show a result to the user. Your site's UI library can be used instead. +function resultMessage(message) { + const container = document.querySelector("#checkout"); + container.innerHTML = message; +} document.getElementById('showOneTimeButton').addEventListener('click', function() { if (isFormValid(form)) { diff --git a/public/index.html b/public/index.html index 24746a7..d0d48c0 100644 --- a/public/index.html +++ b/public/index.html @@ -26,20 +26,23 @@ - + + + +
@@ -52,7 +55,12 @@

Información de Contacto

- +

Utilizaremos esta información para contactarle acerca de la publicación del sitio.

@@ -66,14 +74,15 @@

Pago Automático: Requiere cuenta de PayPal para rebajo automático, si no tiene una le pedirá configurar rápidamente los datos.

Por favor digite los campos requeridos.

-
- - -
-
-
-
+
+
+ + +
+
+
+
diff --git a/public/static/css/style.css b/public/static/css/style.css index bfe2ab4..34fdadb 100644 --- a/public/static/css/style.css +++ b/public/static/css/style.css @@ -25,6 +25,9 @@ --hover-border: #505050; --hyper-color: #00b4db; } + #closeIcon { + content: url('/static/svg/xd.svg'); + } } @media (max-width: 900px) { @@ -208,6 +211,8 @@ button { } #openDialogButton { + display: flex; + align-items: center; position: fixed; bottom: 2em; right: 1em; @@ -273,6 +278,19 @@ button { font-size: 1.1em; } +#checkout { +} + +#openDialogButton img { + width: 1.2em; + height: 1.2em; + vertical-align: middle; +} + +#openDialogButton span { + margin-left: 0.6em; +} + /* Custom SimpleMDE styling */ .CodeMirror { border-radius: 10px; @@ -286,5 +304,3 @@ button { font-size: 1em; font-family: sans-serif; } - - diff --git a/public/static/js/config.editor.js b/public/static/js/config.editor.js index 56721de..63d50b6 100644 --- a/public/static/js/config.editor.js +++ b/public/static/js/config.editor.js @@ -71,9 +71,9 @@ var editor = new EditorJS({ type : 'list', data : { items : [ - 'It is a block-styled editor', - 'It returns clean data output in JSON', - 'Designed to be extendable and pluggable with a simple API', + 'Resolvemos una necesidad clave de mercado', + 'Inversión en crecimiento con presupuesto sostenible.', + 'Enfoque en satisfacción del cliente', ], style: 'unordered' } @@ -82,9 +82,9 @@ var editor = new EditorJS({ type: 'table', data: { content: [ - ['Header 1', 'Header 2', 'Header 3'], - ['Row 1, Cell 1', 'Row 1, Cell 2', 'Row 1, Cell 3'], - ['Row 2, Cell 1', 'Row 2, Cell 2', 'Row 2, Cell 3'] + ['Servicios', 'Descripción', 'Costo'], + ['Impresión', 'Breve descripción', '1000'], + ['laminado', 'Breve descripción', '2000'], ] } }, diff --git a/public/static/svg/cart.svg b/public/static/svg/cart.svg new file mode 100644 index 0000000..d401046 --- /dev/null +++ b/public/static/svg/cart.svg @@ -0,0 +1,5 @@ + + diff --git a/public/static/svg/x.svg b/public/static/svg/x.svg new file mode 100644 index 0000000..a96ee9c --- /dev/null +++ b/public/static/svg/x.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/public/static/svg/xd.svg b/public/static/svg/xd.svg new file mode 100644 index 0000000..d076f5d --- /dev/null +++ b/public/static/svg/xd.svg @@ -0,0 +1,13 @@ + + + + + + +