From c4b06eb6c4f578f5f29810ef24d7210c4226eb11 Mon Sep 17 00:00:00 2001 From: bronku Date: Mon, 9 Nov 2026 18:58:23 +0100 Subject: [PATCH] added google sheets --- .gitignore | 1 + dev.sh | 28 ++++---- go.mod | 34 ++++++++-- go.sum | 60 +++++++++++++++++ orders/order.go | 6 +- orders/sheets.go | 166 +++++++++++++++++++++++++++++++++++++++++++++++ orders/stripe.go | 37 +++++++---- 7 files changed, 304 insertions(+), 28 deletions(-) create mode 100644 orders/sheets.go diff --git a/.gitignore b/.gitignore index f4fa39b..ae87a67 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ www *_templ.go piekarnikgosi .env +key.json diff --git a/dev.sh b/dev.sh index d408417..451e522 100755 --- a/dev.sh +++ b/dev.sh @@ -1,29 +1,35 @@ #!/bin/bash -exec 3< <(stripe listen --events checkout.session.completed,checkout.session.async_payment_succeeded --forward-to localhost:8000/webhook 2>&1) -CHILD_PID=$! -trap "kill -TERM $CHILD_PID 2>/dev/null; exec 3<&-" EXIT +LOG_FILE=$(mktemp) +trap "rm -f $LOG_FILE; kill $(jobs -p) 2>/dev/null" EXIT if [ -f .env ]; then export $(cat .env | xargs) fi export DEV_ENV="TRUE" -export WEBHOOK_SECRET="" -while IFS= read -r line <&3; do - matched_value=$(echo "$line" | grep -oP 'whsec_[a-zA-Z0-9]+') - if [ -n "$matched_value" ]; then - WEBHOOK_SECRET=$matched_value +echo "Starting Stripe CLI listener..." + +stripe listen --events checkout.session.completed,checkout.session.async_payment_succeeded --forward-to localhost:8000/webhook > "$LOG_FILE" 2>&1 & + +WEBHOOK_SECRET="" +for i in {1..10}; do + if grep -q "whsec_" "$LOG_FILE"; then + # Extract the exact whsec_ token + WEBHOOK_SECRET=$(grep -oE 'whsec_[a-zA-Z0-9]+' "$LOG_FILE" | head -n 1) break fi + sleep 0.5 done -exec 3<&- - if [ -z "$WEBHOOK_SECRET" ]; then - echo "Error: can't get the webhook secret" + echo "Error: can't get the webhook secret from Stripe CLI" + cat "$LOG_FILE" exit 1 fi +echo "Successfully captured webhook secret: $WEBHOOK_SECRET" +export WEBHOOK_SECRET + go tool templ generate go run . diff --git a/go.mod b/go.mod index 5bc2b20..9728d6c 100644 --- a/go.mod +++ b/go.mod @@ -10,21 +10,45 @@ require ( ) require ( + cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a // indirect + cloud.google.com/go/auth/oauth2adapt v0.3.1 // indirect + cloud.google.com/go/compute/metadata v0.10.0 // indirect github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cli/browser v1.3.0 // indirect github.com/fatih/color v1.16.0 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/s2a-go v0.1.11 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.24 // indirect + github.com/googleapis/gax-go/v2 v2.27.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/natefinch/atomic v1.0.1 // indirect github.com/tdewolff/parse/v2 v2.8.12 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.51.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/tools v0.35.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.47.0 // indirect + go.opentelemetry.io/otel/log v1.47.0 // indirect + go.opentelemetry.io/otel/metric v1.47.0 // indirect + go.opentelemetry.io/otel/trace v1.47.0 // indirect + golang.org/x/crypto v0.58.0 // indirect + golang.org/x/mod v0.42.0 // indirect + golang.org/x/net v0.62.0 // indirect + golang.org/x/oauth2 v0.37.0 // indirect + golang.org/x/sync v0.24.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/text v0.43.0 // indirect + golang.org/x/tools v0.51.0 // indirect + google.golang.org/api v0.311.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e // indirect + google.golang.org/grpc v1.85.0 // indirect + google.golang.org/protobuf v1.36.12 // indirect ) tool github.com/a-h/templ/cmd/templ diff --git a/go.sum b/go.sum index 0bec5b4..fc496f3 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a h1:jW1ZLFy9oa5ai8LOI40z6s+DL1cO/L1KSm9hVGnibn8= +cloud.google.com/go/auth v0.24.1-0.20261001053825-dbc26066f70a/go.mod h1:1dmBXa9xfBvBr39ZnDUDkaaA91ccq9CcqENDgJA4d7c= +cloud.google.com/go/auth/oauth2adapt v0.3.1 h1:BKLDdbWS7pafd820dsyFDF6fPEzIGqr7ny6hbTzCCgE= +cloud.google.com/go/auth/oauth2adapt v0.3.1/go.mod h1:B1nM4FJDxoDqAt3PXFG4IflUoQ5kvWAOOM3R+5wAszg= +cloud.google.com/go/compute/metadata v0.10.0 h1:pyKMUQSwchgkIBBJGdILqQbs/BNJXqwSA7Ej6LAvvtY= +cloud.google.com/go/compute/metadata v0.10.0/go.mod h1:rGFHRrIif570kSibjFTMbt6/4/tzgJWFGI/HVol4GIk= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= @@ -6,16 +12,34 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/s2a-go v0.1.11 h1:7v1V1TraNGP63WptsAkNqr2w8jgm4vM6A6qx6Lsw9iM= +github.com/google/s2a-go v0.1.11/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.24 h1:G9iCJIY+AYVmUskKdjX8FVy7sG+nbzjOQ41cvlmhcvg= +github.com/googleapis/enterprise-certificate-proxy v0.3.24/go.mod h1:hEjCmnoakQzv5ImLUp6alkY7XgLlNoAxwTXmhHHNIsI= +github.com/googleapis/gax-go/v2 v2.27.0 h1:qe2iQOUzxb3fd6x6ewpIEixPQhJ+0Ihc/RRZhKA/2g0= +github.com/googleapis/gax-go/v2 v2.27.0/go.mod h1:RofiqDw3ehcG4L0H1zO3D2Itpwd3/uS/7wdmbNDF1DE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -36,21 +60,57 @@ github.com/tdewolff/parse/v2 v2.8.12/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLB github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM= +go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU= +go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= +go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw= +go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY= +go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU= +go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM= +go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0= +golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= +golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg= +golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY= +golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= +golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= +golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI= +golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA= +google.golang.org/api v0.311.0 h1:t9xGmC/+3kC75YmJldWrlVW2EZqS+PhxL0QMTdytlAY= +google.golang.org/api v0.311.0/go.mod h1:x9+Bc6l3cH1HItpSGqV2mZnbM1bk1VmpLJMwKEtsmgg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e h1:w3fy/G8OrJHYexn+u5y7hFyc8YRRIr7DCevtIOwAcGY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20261026165753-2ffab32bce7e/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw= +google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/orders/order.go b/orders/order.go index fb5ee9b..e461759 100644 --- a/orders/order.go +++ b/orders/order.go @@ -63,7 +63,6 @@ func parseOrder(r *http.Request) (order, error) { if total < 500 { errorList = append(errorList, fmt.Errorf("Minimalna wartość zamówienia wynosi 50zł")) } - o.id = newOrderID() return o, errors.Join(errorList...) } @@ -79,5 +78,10 @@ func OrderHandler(w http.ResponseWriter, r *http.Request) { writeErrors(w, r, http.StatusUnprocessableEntity, errorMessages(err)...) return } + o.id, err = insertPending(o) + if err != nil { + log.Println("ORDER_SHEETS_ERROR", err) + o.id = newOrderID() + } startCheckout(w, r, o) } diff --git a/orders/sheets.go b/orders/sheets.go new file mode 100644 index 0000000..1ef2446 --- /dev/null +++ b/orders/sheets.go @@ -0,0 +1,166 @@ +package orders + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "strings" + "sync" + "time" + + "google.golang.org/api/option" + "google.golang.org/api/sheets/v4" +) + +const ( + tab = "Zamówienia" + statusPending = "oczekuje" + statusConfirmed = "potwierdzone" +) + +var ( + sheetsSvc *sheets.Service + SheetID string + rowMu sync.Mutex +) + +func init() { + SheetID = os.Getenv("SHEET_ID") + credsFile := os.Getenv("SHEETS_CREDS") + var err error + sheetsSvc, err = sheets.NewService(context.Background(), option.WithCredentialsFile(credsFile)) + if err != nil { + log.Fatal(err) + } + log.Println("init google sheets") +} + +func sctx() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 10*time.Second) +} + +func cell(r []any, i int) string { + if i < len(r) && r[i] != nil { + return strings.TrimSpace(fmt.Sprint(r[i])) + } + return "" +} + +func get(ctx context.Context, a1 string) ([][]any, error) { + res, err := sheetsSvc.Spreadsheets.Values.Get(SheetID, a1).Context(ctx).Do() + if err != nil { + return nil, err + } + return res.Values, nil +} + +func clearRow(ctx context.Context, row int) error { + _, err := sheetsSvc.Spreadsheets.Values.BatchClear(SheetID, &sheets.BatchClearValuesRequest{ + Ranges: []string{ + fmt.Sprintf("%s!B%d:D%d", tab, row, row), + fmt.Sprintf("%s!G%d", tab, row), + fmt.Sprintf("%s!I%d:T%d", tab, row, row), + }, + }).Context(ctx).Do() + return err +} + +func insertPending(o order) (string, error) { + ctx, cancel := sctx() + defer cancel() + rowMu.Lock() + defer rowMu.Unlock() + + vals, err := get(ctx, tab+"!B:C") + if err != nil { + return "", err + } + row := max(len(vals)+1, 4) + for i := 3; i < len(vals); i++ { + if cell(vals[i], 0) == "" && cell(vals[i], 1) == "" { + row = i + 1 + break + } + } + + data := []*sheets.ValueRange{{ + Range: fmt.Sprintf("%s!B%d:D%d", tab, row, row), + Values: [][]any{{o.phone, o.name, statusPending}}, + }} + for _, it := range o.items { + if it.count > 0 { + data = append(data, &sheets.ValueRange{ + Range: fmt.Sprintf("%s!%s%d", tab, it.id, row), + Values: [][]any{{float64(it.count) / 10}}, + }) + } + } + _, err = sheetsSvc.Spreadsheets.Values.BatchUpdate(SheetID, &sheets.BatchUpdateValuesRequest{ + ValueInputOption: "RAW", + Data: data, + }).Context(ctx).Do() + if err != nil { + for _, datum := range data { + log.Println(datum) + } + return "", err + } + + idVals, err := get(ctx, fmt.Sprintf("%s!A%d", tab, row)) + if err != nil || len(idVals) == 0 || cell(idVals[0], 0) == "" { + clearRow(ctx, row) + return "", errors.New("row has no id") + } + return cell(idVals[0], 0), nil +} + +func findPendingRow(ctx context.Context, id string) (int, error) { + ids, err := get(ctx, tab+"!A:A") + if err != nil { + return 0, err + } + for i, r := range ids { + if i == 0 || cell(r, 0) != id { + continue + } + row := i + 1 + v, err := get(ctx, fmt.Sprintf("%s!D%d", tab, row)) + if err != nil { + return 0, err + } + if len(v) > 0 && cell(v[0], 0) == statusPending { + return row, nil + } + return 0, errors.New("row is not pending") + } + return 0, errors.New("no row with this id") +} + +func confirmRow(id string) error { + ctx, cancel := sctx() + defer cancel() + row, err := findPendingRow(ctx, id) + if err != nil { + return err + } + _, err = sheetsSvc.Spreadsheets.Values.BatchUpdate(SheetID, &sheets.BatchUpdateValuesRequest{ + ValueInputOption: "RAW", + Data: []*sheets.ValueRange{ + {Range: fmt.Sprintf("%s!D%d", tab, row), Values: [][]any{{statusConfirmed}}}, + {Range: fmt.Sprintf("%s!G%d", tab, row), Values: [][]any{{float64(retainer) / 100}}}, + }, + }).Context(ctx).Do() + return err +} + +func clearOrder(id string) error { + ctx, cancel := sctx() + defer cancel() + row, err := findPendingRow(ctx, id) + if err != nil { + return err + } + return clearRow(ctx, row) +} diff --git a/orders/stripe.go b/orders/stripe.go index 8ce7ac2..4cd3490 100644 --- a/orders/stripe.go +++ b/orders/stripe.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "time" "github.com/stripe/stripe-go/v82" "github.com/stripe/stripe-go/v82/checkout/session" @@ -24,6 +25,8 @@ func init() { log.Println("the url is: ", siteUrl) stripe.Key = os.Getenv("STRIPE_SECRET_KEY") webhookSecret = os.Getenv("WEBHOOK_SECRET") + log.Println(stripe.Key) + log.Println(webhookSecret) } func startCheckout(w http.ResponseWriter, r *http.Request, o order) { @@ -36,13 +39,14 @@ func startCheckout(w http.ResponseWriter, r *http.Request, o order) { Mode: stripe.String("payment"), ClientReferenceID: stripe.String(o.id), Locale: stripe.String("pl"), + ExpiresAt: stripe.Int64(time.Now().Add(time.Hour).Unix()), LineItems: []*stripe.CheckoutSessionLineItemParams{{ Quantity: stripe.Int64(1), PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{ Currency: stripe.String("pln"), UnitAmount: stripe.Int64(retainer), ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{ - Name: stripe.String("Zaliczka na zamówienie " + o.id), + Name: stripe.String("Zaliczka na zamówienie nr " + o.id), }, }, }}, @@ -75,7 +79,10 @@ func WebhookHandler(w http.ResponseWriter, r *http.Request) { return } - if event.Type != "checkout.session.completed" && event.Type != "checkout.session.async_payment_succeeded" { + switch event.Type { + case "checkout.session.completed", "checkout.session.async_payment_succeeded", + "checkout.session.expired", "checkout.session.async_payment_failed": + default: w.WriteHeader(http.StatusOK) return } @@ -86,14 +93,22 @@ func WebhookHandler(w http.ResponseWriter, r *http.Request) { return } - switch { - case s.PaymentStatus != stripe.CheckoutSessionPaymentStatusPaid: - log.Println("ORDER_NOT_YET_PAID", s.ClientReferenceID, s.ID, s.PaymentStatus) - case s.AmountTotal != retainer || s.Currency != stripe.CurrencyPLN: - log.Println("ORDER_PAYMENT_MISMATCH", s.ClientReferenceID, s.ID, s.AmountTotal, s.Currency) - default: - log.Println("ORDER_PAID", s.ClientReferenceID, s.ID) - } - w.WriteHeader(http.StatusOK) + id := s.ClientReferenceID + switch { + case event.Type == "checkout.session.expired" || event.Type == "checkout.session.async_payment_failed": + log.Println("ORDER_ABANDONED", id, event.Type) + if err := clearOrder(id); err != nil { + log.Println("ORDER_SHEETS_ERROR", id, err) + } + case s.PaymentStatus != stripe.CheckoutSessionPaymentStatusPaid: + log.Println("ORDER_NOT_YET_PAID", id, s.ID, s.PaymentStatus) + case s.AmountTotal != retainer || s.Currency != stripe.CurrencyPLN: + log.Println("ORDER_PAYMENT_MISMATCH", id, s.ID, s.AmountTotal, s.Currency) + default: + log.Println("ORDER_PAID", id, s.ID) + if err := confirmRow(id); err != nil { + log.Println("ORDER_SHEETS_ERROR", id, err) + } + } }