1// ===== cart.go =====
2//go:build js && wasm
3
4// Package main complete.go
5package main
6
7import (
8 "encoding/json"
9 "fmt"
10 "log"
11 "strconv"
12 "strings"
13 "syscall/js"
14)
15
16func updateCartDisplayWrapper(this js.Value, args []js.Value) interface{} {
17 updateCartDisplay()
18 return nil
19}
20
21func saveCart() {
22 cartJSON, err := json.Marshal(cart)
23 if err != nil {
24 log.Println(wasmName+":", "Error saving cart:", err)
25 return
26 }
27 js.Global().Get("localStorage").Call("setItem", "cartItems", string(cartJSON))
28 updateCartDisplay()
29}
30
31// addToCart is called from Go rather than registered with js.FuncOf, so it
32// keeps the callback shape its callers build but returns nothing.
33//
34// The guard reads three arguments, not two: qty is args[2]. Checking for two
35// and then indexing the third is an out-of-range panic for any caller that
36// passes exactly two, which is what the check was there to prevent.
37func addToCart(_ js.Value, args []js.Value) {
38 if len(args) < 3 {
39 log.Println(wasmName+":", "addToCart: missing arguments")
40 return
41 }
42 var cartItem item
43 index := -1
44 id := args[0].String()
45 qty := args[2].Int()
46 if qty == 0 {
47 qty = 1
48 }
49 amount := int(args[1].Float()) * qty
50 for i := range cart {
51 if strings.Split(cart[i].ID, "|")[0] == strings.Split(id, "|")[0] {
52 index = i
53 }
54 }
55 if index > -1 {
56 // update shipping
57 if strings.Split(cart[index].ID, "|")[0] == "shipping-to" {
58 cart[index].ID = id
59 cart[index].Qty = 1
60 cart[index].Amount = amount
61 } else {
62 cart[index].Qty = cart[index].Qty + qty
63 cart[index].Amount = cart[index].Amount + amount
64 }
65 } else {
66 cartItem = item{
67 ID: id,
68 Amount: amount,
69 Qty: qty,
70 }
71 cart = append(cart, cartItem)
72 }
73 saveCart()
74}
75
76func addUnToCart(this js.Value, args []js.Value) interface{} {
77 if len(args) < 2 {
78 return "Error: Missing arguments"
79 }
80 id := args[0].String()
81 price := args[1].Float()
82 quantityInput := doc.Call("getElementById", fmt.Sprintf("qty-%s", id))
83 if !quantityInput.Truthy() {
84 log.Println(wasmName+":", "Error: Quantity input not found for item", id)
85 return nil
86 }
87 quantity, err := strconv.Atoi(quantityInput.Get("value").String())
88 if err != nil || quantity < 1 {
89 quantity = 1
90 }
91
92 addToCart(js.Value{}, []js.Value{
93 js.ValueOf(id),
94 js.ValueOf(int(price * 100)),
95 js.ValueOf(quantity),
96 })
97 return nil
98}
99
100func removeFromCart(this js.Value, inputs []js.Value) interface{} {
101 id := inputs[0].String()
102 newCart := []item{}
103 for _, m := range cart {
104 if m.ID != id {
105 newCart = append(newCart, m)
106 }
107 }
108 cart = newCart
109 saveCart()
110 return nil
111}
112
113func loadCart() {
114 storedCart := js.Global().Get("localStorage").Call("getItem", "cartItems")
115 if !storedCart.IsUndefined() && !storedCart.IsNull() {
116 err := json.Unmarshal([]byte(storedCart.String()), &cart)
117 if err != nil {
118 log.Println(`can't unmarshal cart from local storage`)
119 cart = []item{}
120 }
121 }
122}
123
124func emptyCart(this js.Value, inputs []js.Value) interface{} {
125 js.Global().Get("localStorage").Call("removeItem", "cartItems")
126 cart = []item{}
127 updateCartDisplay()
128 return nil
129}
130
131func clearAll(this js.Value, inputs []js.Value) interface{} {
132 js.Global().Get("localStorage").Call("clear")
133 cart = []item{}
134 updateCartDisplay()
135 return nil
136}
137
138func updateCartDisplay() {
139 cartContainer := doc.Call("getElementById", "cart-items")
140 totalPriceElement := doc.Call("getElementById", "total-price")
141 table := cartContainer.Call("querySelector", "table")
142 if table.IsNull() {
143 table = doc.Call("createElement", "table")
144 thead := doc.Call("createElement", "thead")
145 thead.Set("innerHTML", `<tr><th>Item</th><th>Price</th><th>Quantity</th><th>Actions</th></tr>`)
146 table.Call("appendChild", thead)
147 tbody := doc.Call("createElement", "tbody")
148 tbody.Set("id", "cart-tbody")
149 table.Call("appendChild", tbody)
150 cartContainer.Call("appendChild", table)
151 }
152 tbody := doc.Call("getElementById", "cart-tbody")
153 tbody.Set("innerHTML", "")
154
155 total := 0
156 hasShipping := false
157 for _, m := range cart {
158 total += m.Amount
159 row := doc.Call("createElement", "tr")
160
161 row.Set("innerHTML", fmt.Sprintf(`<td>%s</td><td>$%.2f</td><td>%s</td><td><button onclick='removeFromCart("%s")'>Remove</button></td>`,
162 func() string {
163 parts := strings.Split(m.ID, "|")
164 if len(parts) < 8 {
165 return m.ID
166 }
167 hasShipping = true
168 return fmt.Sprintf("%s:<br>%s<br>%s<br>%s, %s %s<br>%s<br>%s", parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7])
169 }(),
170 float64(m.Amount)/100,
171 func() string {
172 if len(strings.Split(m.ID, "|")) == 8 {
173 return ""
174 }
175 return fmt.Sprintf(`<input type='number' value='%d' min='1' onchange='updateItemQuantity("%s", this.value)'>`, m.Qty, m.ID)
176 }(),
177 m.ID,
178 ))
179 tbody.Call("appendChild", row)
180 }
181 totalPriceElement.Set("textContent", fmt.Sprintf("Total: $%.2f", float64(total)/100))
182
183 checkoutbutton := doc.Call("getElementById", "checkout-button")
184 if !checkoutbutton.Truthy() {
185 return
186 }
187
188 if len(cart) > 1 && hasShipping {
189 checkoutbutton.Call("removeAttribute", "disabled")
190 } else {
191 checkoutbutton.Call("setAttribute", "disabled", "true")
192 }
193}
194
195func updateItemQuantity(this js.Value, args []js.Value) interface{} {
196 id := args[0].String()
197 qty, err := strconv.Atoi(args[1].String())
198 if err != nil {
199 log.Println(err)
200 }
201 for i := range cart {
202 if cart[i].ID == id {
203 unitPrice := cart[i].Amount / cart[i].Qty
204 cart[i].Qty = qty
205 cart[i].Amount = unitPrice * qty
206 break
207 }
208 }
209 saveCart()
210 return nil
211}
212
213
214// ===== checkout.go =====
215//go:build js && wasm
216
217package main
218
219import (
220 "encoding/json"
221 "fmt"
222 "log"
223 "strconv"
224 "syscall/js"
225)
226
227func addShippingInfo(this js.Value, args []js.Value) interface{} {
228 event := args[0]
229 form := args[1]
230 event.Call("preventDefault")
231 getFormValue := func(name string) string {
232 return form.Call("querySelector", fmt.Sprintf("[name='%s']", name)).Get("value").String()
233 }
234 shippingInfo := fmt.Sprintf("shipping-to|%s|%s|%s|%s|%s|%s|%s",
235 getFormValue("shipping-name"),
236 getFormValue("shipping-address"),
237 getFormValue("shipping-city"),
238 getFormValue("shipping-state"),
239 getFormValue("shipping-zip"),
240 getFormValue("shipping-country"),
241 getFormValue("shipping-phone"),
242 )
243 priceStr := getFormValue("shipping-price")
244 price, err := strconv.ParseFloat(priceStr, 64)
245 if err != nil {
246 log.Println(wasmName+":", "Error: Failed to parse shipping price")
247 price = 0.0
248 }
249
250 addToCart(js.Value{}, []js.Value{
251 js.ValueOf(shippingInfo),
252 js.ValueOf(int(price * 100)),
253 js.ValueOf(1),
254 })
255 return false
256}
257
258var (
259 elements js.Value
260 stripeValue js.Value
261 stripe js.Value
262 checkoutStripe = doc.Call("getElementById", "stripecheckout")
263)
264
265func goToCheckout(this js.Value, args []js.Value) any {
266 if stripeValue.IsUndefined() {
267 log.Println(`js.Global().Get("Stripe")`)
268 stripeValue = js.Global().Get("Stripe")
269 if stripeValue.IsUndefined() {
270 log.Println(`Stripe is undefined, attempting to load Stripe.js`)
271
272 doc := js.Global().Get("document")
273 head := doc.Call("querySelector", "head")
274 script := doc.Call("createElement", "script")
275 script.Set("src", "https://js.stripe.com/v3/")
276 script.Set("defer", true)
277
278 done := make(chan bool)
279 script.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
280 log.Println(wasmName+":", "Stripe.js script has been loaded")
281 done <- true
282 return nil
283 }))
284
285 head.Call("appendChild", script)
286
287 <-done
288
289 stripeValue = js.Global().Get("Stripe")
290 if stripeValue.IsUndefined() {
291 log.Println(wasmName+":", "Failed to load Stripe.js")
292 return nil
293 }
294 }
295 }
296
297 log.Println(wasmName+":", "Stripe.js loaded successfully")
298
299 if stripe.IsUndefined() {
300 log.Println(wasmName+":", "Invoking Stripe")
301 stripe = stripeValue.Invoke(stripePK)
302 if stripe.IsUndefined() {
303 log.Println(wasmName+":", "Failed to invoke Stripe")
304 return nil
305 }
306 }
307
308 log.Println(wasmName+":", "Stripe initialized")
309 checkoutStripe = doc.Call("getElementById", "stripecheckout")
310 if checkoutStripe.IsUndefined() {
311 log.Println(wasmName+":", "element with ID stripecheckout not found")
312 }
313
314 checkoutStripe.Call("showModal")
315 log.Println(wasmName+":", "initializePayment()")
316 initializePayment()
317
318 return nil
319}
320
321func cancelCheckout(this js.Value, args []js.Value) any {
322 log.Println(wasmName+":", "Canceling checkout ; closing dialog")
323 checkoutStripe.Call("close")
324 updateCartDisplay()
325 return nil
326}
327
328func initializePayment() {
329 type cItem struct {
330 ID string `json:"id"`
331 Amount int `json:"amount"`
332 }
333 type checkout struct {
334 Items []cItem `json:"items"`
335 }
336 payload := checkout{
337 Items: func() []cItem {
338 var items []cItem
339 for _, it := range cart {
340 items = append(items, cItem{ID: it.ID + " X " + strconv.Itoa(it.Qty), Amount: it.Amount})
341 }
342 return items
343 }(),
344 }
345 payloadJSON, err := json.Marshal(payload)
346 if err != nil {
347 log.Println(wasmName+":", "Error marshaling JSON:", err)
348 return
349 }
350 fetchInit := map[string]interface{}{
351 "method": "POST",
352 "headers": map[string]interface{}{
353 "Content-Type": "application/json",
354 },
355 "body": string(payloadJSON),
356 }
357
358 log.Println(wasmName+":", "fetch /create-payment-intent")
359 js.Global().Call("fetch", "/create-payment-intent", js.ValueOf(fetchInit)).
360 Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
361 response := args[0]
362 log.Println(wasmName+":", "got response from fetch /create-payment-intent")
363 if !response.Get("ok").Bool() {
364 log.Println(wasmName+":", "Fetch request failed with status:", response.Get("status").Int())
365 showMessage("Failed to create payment intent: " + response.Get("status").String())
366 return nil
367 }
368 response.Call("json").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
369 clientSecret := args[0].Get("clientSecret").String()
370 log.Println(wasmName+":", "Client secret received:", clientSecret)
371 setupStripeElements(clientSecret)
372 return nil
373 })).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
374 log.Println(wasmName+":", "Error parsing JSON response:", args[0])
375 showMessage("Failed to parse payment intent response.")
376 return nil
377 }))
378 return nil
379 })).
380 Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
381 log.Println(wasmName+":", "Error in fetch request:", args[0])
382 showMessage("Failed to communicate with the server.")
383 return nil
384 }))
385}
386
387func setupStripeElements(clientSecret string) {
388 elements = stripe.Call("elements", map[string]interface{}{
389 "clientSecret": clientSecret,
390 })
391 if elements.IsUndefined() {
392 log.Println(wasmName+":", "Failed to initialize Stripe Elements")
393 showMessage("Failed to initialize payment elements.")
394 return
395 }
396 paymentElement := elements.Call("create", "payment", map[string]interface{}{
397 "layout": "tabs",
398 })
399 if paymentElement.IsUndefined() {
400 log.Println(wasmName+":", "Failed to create payment element")
401 showMessage("Failed to create payment element.")
402 return
403 }
404 paymentElement.Call("mount", "#payment-element")
405 submitButton := doc.Call("getElementById", "submit")
406 submitButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
407 args[0].Call("preventDefault")
408 showSpinner(true)
409 confirmPayment(clientSecret)
410 return nil
411 }))
412}
413
414func confirmPayment(clientSecret string) {
415
416 windowLocation := js.Global().Get("window").Get("location")
417 protocol := windowLocation.Get("protocol").String()
418 hostname := windowLocation.Get("hostname").String()
419 port := windowLocation.Get("port").String()
420
421 baseURL := protocol + "//" + hostname
422 if port != "" {
423 baseURL += ":" + port
424 }
425 // path := windowLocation.Get("pathname").String()
426 // baseURL += strings.Split(path, "?")[0]
427 // log.Println(wasmName+":","return url ", baseURL)
428
429 returnURL := baseURL + "/complete"
430 returnURL += "?payment_intent=" + clientSecret // + "#complete"
431 log.Println(wasmName+":", "Return URL for payment:", returnURL)
432
433 stripe.Call("confirmPayment", map[string]interface{}{
434 "elements": elements,
435 "confirmParams": map[string]interface{}{
436 "return_url": returnURL,
437 },
438 }).Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
439 result := args[0]
440 if result.Get("error").IsUndefined() {
441 log.Println(wasmName+":", "Payment successful:", result)
442 showMessage("Payment successful! Thank you for your order.")
443 } else {
444 log.Println(wasmName+":", "Payment error:", result.Get("error").Get("message").String())
445 showMessage("Payment failed: " + result.Get("error").Get("message").String())
446 }
447
448 showSpinner(false)
449 return nil
450 }))
451}
452
453func showMessage(message string) {
454 messageElement := doc.Call("getElementById", "payment-message")
455 messageElement.Set("innerText", message)
456 messageElement.Set("className", "")
457}
458
459func showSpinner(isLoading bool) {
460 spinner := doc.Call("getElementById", "spinner")
461 buttonText := doc.Call("getElementById", "button-text")
462
463 if isLoading {
464 spinner.Set("className", "")
465 buttonText.Set("className", "hidden")
466 } else {
467 spinner.Set("className", "hidden")
468 buttonText.Set("className", "")
469 }
470}
471
472
473// ===== complete.go =====
474//go:build js && wasm
475
476// Package main complete.go
477package main
478
479import (
480 "encoding/json"
481 "log"
482 "syscall/js"
483)
484
485func completeLogic() {
486 initializeStripe()
487}
488
489func initializeStripe() {
490 if stripeValue.IsUndefined() {
491 log.Println(`js.Global().Get("Stripe")`)
492 stripeValue = js.Global().Get("Stripe")
493 if stripeValue.IsUndefined() {
494 log.Println(`Stripe is undefined, attempting to load Stripe.js`)
495
496 doc := js.Global().Get("document")
497 head := doc.Call("querySelector", "head")
498 script := doc.Call("createElement", "script")
499 script.Set("src", "https://js.stripe.com/v3/")
500 script.Set("defer", true)
501
502 done := make(chan bool)
503 script.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
504 log.Println(wasmName+":", "Stripe.js script has been loaded")
505 done <- true
506 return nil
507 }))
508
509 head.Call("appendChild", script)
510
511 <-done
512
513 stripeValue = js.Global().Get("Stripe")
514 if stripeValue.IsUndefined() {
515 log.Println(wasmName+":", "Failed to load Stripe.js")
516 return
517 }
518 }
519 }
520
521 log.Println(wasmName+":", "Stripe.js loaded successfully")
522
523 if stripe.IsUndefined() {
524 log.Println(wasmName+":", "Invoking Stripe")
525 stripe = stripeValue.Invoke(stripePK)
526 if stripe.IsUndefined() {
527 log.Println(wasmName+":", "Failed to invoke Stripe")
528 return
529 }
530 }
531
532 log.Println(wasmName+":", "Stripe initialized")
533 checkStatus()
534}
535
536var (
537 successIcon = `<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
538 <path fill-rule="evenodd" clip-rule="evenodd" d="M15.4695 0.232963C15.8241 0.561287 15.8454 1.1149 15.5171 1.46949L6.14206 11.5945C5.97228 11.7778 5.73221 11.8799 5.48237 11.8748C5.23253 11.8698 4.99677 11.7582 4.83452 11.5681L0.459523 6.44311C0.145767 6.07557 0.18937 5.52327 0.556912 5.20951C0.924454 4.89575 1.47676 4.93936 1.79051 5.3069L5.52658 9.68343L14.233 0.280522C14.5613 -0.0740672 15.1149 -0.0953599 15.4695 0.232963Z" fill="white"/>
539 </svg>`
540
541 errorIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
542 <path fill-rule="evenodd" clip-rule="evenodd" d="M1.25628 1.25628C1.59799 0.914573 2.15201 0.914573 2.49372 1.25628L8 6.76256L13.5063 1.25628C13.848 0.914573 14.402 0.914573 14.7437 1.25628C15.0854 1.59799 15.0854 2.15201 14.7437 2.49372L9.23744 8L14.7437 13.5063C15.0854 13.848 15.0854 14.402 14.7437 14.7437C14.402 15.0854 13.848 15.0854 13.5063 14.7437L8 9.23744L2.49372 14.7437C2.15201 15.0854 1.59799 15.0854 1.25628 14.7437C0.914573 14.402 0.914573 13.848 1.25628 13.5063L6.76256 8L1.25628 2.49372C0.914573 2.15201 0.914573 1.59799 1.25628 1.25628Z" fill="white"/>
543 </svg>`
544
545 infoIcon = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
546 <path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.5H4C2.61929 1.5 1.5 2.61929 1.5 4V10C1.5 11.3807 2.61929 12.5 4 12.5H10C11.3807 12.5 12.5 11.3807 12.5 10V4C12.5 2.61929 11.3807 1.5 10 1.5ZM4 0C1.79086 0 0 1.79086 0 4V10C0 12.2091 1.79086 14 4 14H10C12.2091 14 14 12.2091 14 10V4C14 1.79086 12.2091 0 10 0H4Z" fill="white"/>
547 <path fill-rule="evenodd" clip-rule="evenodd" d="M5.25 7C5.25 6.58579 5.58579 6.25 6 6.25H7.25C7.66421 6.25 8 6.58579 8 7V10.5C8 10.9142 7.66421 11.25 7.25 11.25C6.83579 11.25 6.5 10.9142 6.5 10.5V7.75H6C5.58579 7.75 5.25 7.41421 5.25 7Z" fill="white"/>
548 <path d="M5.75 4C5.75 3.31075 6.31075 2.75 7 2.75C7.68925 2.75 8.25 3.31075 8.25 4C8.25 4.68925 7.68925 5.25 7 5.25C6.31075 5.25 5.75 4.68925 5.75 4Z" fill="white"/>
549 </svg>`
550)
551
552func setErrorState() {
553 js.Global().Get("document").Call("querySelector", "#status-icon").Set("style", map[string]interface{}{"backgroundColor": "#DF1B41"})
554 js.Global().Get("document").Call("querySelector", "#status-icon").Set("innerHTML", errorIcon)
555 js.Global().Get("document").Call("querySelector", "#status-text").Set("textContent", "Something went wrong, please try again.")
556 js.Global().Get("document").Call("querySelector", "#details-table").Call("classList").Call("add", "hidden")
557 js.Global().Get("document").Call("querySelector", "#view-details").Call("classList").Call("add", "hidden")
558}
559
560func checkStatus() {
561 clientSecret := js.Global().Get("URLSearchParams").New(js.Global().Get("window").Get("location").Get("search")).Call("get", "payment_intent_client_secret").String()
562
563 if clientSecret == "" {
564 setErrorState()
565 return
566 }
567
568 if stripe.IsUndefined() {
569 log.Println(wasmName+":", "Stripe is not initialized")
570 setErrorState()
571 return
572 }
573
574 stripe.Call("retrievePaymentIntent", clientSecret).Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
575 paymentIntent := p[0].Get("paymentIntent")
576 setPaymentDetails(paymentIntent)
577 return nil
578 })).Call("catch", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
579 setErrorState()
580 return nil
581 }))
582}
583
584func getAllLocalStorageData() map[string]interface{} {
585 localStorage := js.Global().Get("localStorage")
586 keys := js.Global().Get("Object").Call("keys", localStorage)
587 data := make(map[string]interface{})
588
589 for i := 0; i < keys.Length(); i++ {
590 key := keys.Index(i).String()
591 value := localStorage.Call("getItem", key).String()
592 var parsedValue interface{}
593 err := json.Unmarshal([]byte(value), &parsedValue)
594 if err != nil {
595 parsedValue = value // If not JSON, store raw value
596 }
597 data[key] = parsedValue
598 }
599 return data
600}
601
602func submitOrder(localStorageData map[string]interface{}, paymentIntentId string) {
603 orderData := map[string]interface{}{
604 "localStorageData": localStorageData,
605 "paymentIntentId": paymentIntentId,
606 }
607
608 body, err := json.Marshal(orderData)
609 if err != nil {
610 log.Println(wasmName+":", "Error marshaling order data:", err)
611 return
612 }
613
614 fetch := js.Global().Get("fetch")
615 if fetch.IsUndefined() {
616 log.Println(wasmName+":", "Fetch API is not available")
617 return
618 }
619
620 options := map[string]interface{}{
621 "method": "POST",
622 "headers": map[string]interface{}{
623 "Content-Type": "application/json",
624 },
625 "body": string(body),
626 }
627
628 fetch.Invoke("/submit-order", js.ValueOf(options)).Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
629 response := args[0]
630 if !response.Get("ok").Bool() {
631 response.Call("text").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
632 errText := args[0].String()
633 log.Println(wasmName+":", "Order submit failed:", errText)
634 js.Global().Call("alert", "Order submission failed:\n"+errText)
635 return nil
636 }))
637 return nil
638 }
639 response.Call("json").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
640 data := args[0]
641 log.Println(wasmName+":", "Order submitted successfully:", data)
642 return nil
643 }))
644 return nil
645 }))
646}
647
648func setPaymentDetails(intent js.Value) {
649 // Every path through the switch below sets this, including its default,
650 // so there is nothing to fall back to. iconColor and icon do fall back:
651 // the cases that only change the wording leave them red.
652 var statusText string
653 iconColor := "#DF1B41"
654 icon := errorIcon
655
656 if !intent.IsUndefined() {
657 intentStatus := intent.Get("status").String()
658 intentID := intent.Get("id").String()
659
660 allLocalStorageData := getAllLocalStorageData()
661
662 switch intentStatus {
663 case "succeeded":
664 statusText = "Payment succeeded"
665 iconColor = "#30B130"
666 icon = successIcon
667 if len(allLocalStorageData) > 0 {
668 submitOrder(allLocalStorageData, intentID)
669 } else {
670 log.Println(wasmName+":", "No data found in localStorage; order not submitted.")
671 }
672 case "processing":
673 statusText = "Your payment is processing."
674 iconColor = "#6D6E78"
675 icon = infoIcon
676 if len(allLocalStorageData) > 0 {
677 submitOrder(allLocalStorageData, intentID)
678 } else {
679 log.Println(wasmName+":", "No data found in localStorage; order not submitted.")
680 }
681 case "requires_payment_method":
682 statusText = "Your payment was not successful, please try again."
683 default:
684 statusText = "Unknown payment status."
685 }
686
687 // Update the status icon, text, and links
688 js.Global().Get("document").Call("querySelector", "#status-icon").Set("style", map[string]interface{}{"backgroundColor": iconColor})
689 js.Global().Get("document").Call("querySelector", "#status-icon").Set("innerHTML", icon)
690 js.Global().Get("document").Call("querySelector", "#status-text").Set("textContent", statusText)
691 js.Global().Get("document").Call("querySelector", "#intent-id").Set("textContent", intentID)
692 js.Global().Get("document").Call("querySelector", "#intent-status").Set("textContent", intentStatus)
693 js.Global().Get("document").Call("querySelector", "#view-details").Set("href", "https://dashboard.stripe.com/payments/"+intentID)
694
695 // Update the "Order Details" link with the paymentIntent ID
696 orderDetailsLink := js.Global().Get("document").Call("querySelector", "#order-details-link")
697 orderDetailsLink.Set("href", "/order/"+intentID)
698 orderDetailsLink.Set("onclick", nil) // Allow default behavior (navigation)
699
700 } else {
701 setErrorState()
702 }
703}
704
705
706// ===== main.go =====
707//go:build js && wasm
708
709package main
710
711import (
712 "log"
713 "syscall/js"
714)
715
716// set client pk on compile
717var stripePK string
718
719type item struct {
720 ID string `json:"id"`
721 Amount int `json:"amount"`
722 Qty int `json:"quantity"`
723}
724
725var (
726 wasmName string
727 doc = js.Global().Get("document")
728 cart []item
729)
730
731func main() {
732 ready := make(chan struct{})
733
734 document := js.Global().Get("document")
735 readyState := document.Get("readyState").String()
736 if readyState == "interactive" || readyState == "complete" {
737 log.Println(wasmName+":", "WASM: DOM already fully loaded")
738 close(ready)
739 } else {
740 cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
741 log.Println(wasmName+":", "WASM: DOM fully loaded and parsed")
742 close(ready)
743 return nil
744 })
745 defer cb.Release()
746
747 document.Call("addEventListener", "DOMContentLoaded", cb)
748 log.Println(wasmName+":", "WASM: waiting for DOM to load")
749 }
750
751 <-ready
752
753 c := make(chan struct{})
754 if stripePK == "" {
755 log.Fatal("Stripe PK not found!")
756 }
757 window := js.Global().Get("window")
758 location := window.Get("location")
759 pathname := location.Get("pathname").String()
760
761 switch pathname {
762 case "/complete":
763 completeLogic()
764 default:
765 defaultLogic()
766 }
767 <-c
768}
769
770func defaultLogic() {
771 js.Global().Set("addToCart", js.FuncOf(addUnToCart))
772 js.Global().Set("clearStorage", js.FuncOf(clearAll))
773 js.Global().Set("emptyCart", js.FuncOf(emptyCart))
774 js.Global().Set("updateItemQuantity", js.FuncOf(updateItemQuantity))
775 js.Global().Set("removeFromCart", js.FuncOf(removeFromCart))
776 js.Global().Set("addShippingInfo", js.FuncOf(addShippingInfo))
777 js.Global().Set("goToCheckout", js.FuncOf(goToCheckout))
778 js.Global().Set("cancelCheckout", js.FuncOf(cancelCheckout))
779 js.Global().Set("callUpdateCartDisplay", js.FuncOf(updateCartDisplayWrapper))
780 loadCart()
781 updateCartDisplay()
782}
783
784