added google sheets

This commit is contained in:
bronku 2026-11-09 18:58:23 +01:00
parent 37ca207b92
commit c4b06eb6c4
7 changed files with 304 additions and 28 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ www
*_templ.go *_templ.go
piekarnikgosi piekarnikgosi
.env .env
key.json

28
dev.sh
View file

@ -1,29 +1,35 @@
#!/bin/bash #!/bin/bash
exec 3< <(stripe listen --events checkout.session.completed,checkout.session.async_payment_succeeded --forward-to localhost:8000/webhook 2>&1) LOG_FILE=$(mktemp)
CHILD_PID=$! trap "rm -f $LOG_FILE; kill $(jobs -p) 2>/dev/null" EXIT
trap "kill -TERM $CHILD_PID 2>/dev/null; exec 3<&-" EXIT
if [ -f .env ]; then if [ -f .env ]; then
export $(cat .env | xargs) export $(cat .env | xargs)
fi fi
export DEV_ENV="TRUE" export DEV_ENV="TRUE"
export WEBHOOK_SECRET=""
while IFS= read -r line <&3; do echo "Starting Stripe CLI listener..."
matched_value=$(echo "$line" | grep -oP 'whsec_[a-zA-Z0-9]+')
if [ -n "$matched_value" ]; then stripe listen --events checkout.session.completed,checkout.session.async_payment_succeeded --forward-to localhost:8000/webhook > "$LOG_FILE" 2>&1 &
WEBHOOK_SECRET=$matched_value
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 break
fi fi
sleep 0.5
done done
exec 3<&-
if [ -z "$WEBHOOK_SECRET" ]; then 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 exit 1
fi fi
echo "Successfully captured webhook secret: $WEBHOOK_SECRET"
export WEBHOOK_SECRET
go tool templ generate go tool templ generate
go run . go run .

34
go.mod
View file

@ -10,21 +10,45 @@ require (
) )
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/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.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/cli/browser v1.3.0 // indirect
github.com/fatih/color v1.16.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/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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/natefinch/atomic v1.0.1 // indirect github.com/natefinch/atomic v1.0.1 // indirect
github.com/tdewolff/parse/v2 v2.8.12 // indirect github.com/tdewolff/parse/v2 v2.8.12 // indirect
golang.org/x/mod v0.26.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
golang.org/x/net v0.51.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
golang.org/x/sync v0.16.0 // indirect go.opentelemetry.io/otel v1.47.0 // indirect
golang.org/x/sys v0.43.0 // indirect go.opentelemetry.io/otel/log v1.47.0 // indirect
golang.org/x/tools v0.35.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 tool github.com/a-h/templ/cmd/templ

60
go.sum
View file

@ -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 h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= 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= 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/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 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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= 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.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= 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 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 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 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= 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 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= 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.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 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= 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 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= 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 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -63,7 +63,6 @@ func parseOrder(r *http.Request) (order, error) {
if total < 500 { if total < 500 {
errorList = append(errorList, fmt.Errorf("Minimalna wartość zamówienia wynosi 50zł")) errorList = append(errorList, fmt.Errorf("Minimalna wartość zamówienia wynosi 50zł"))
} }
o.id = newOrderID()
return o, errors.Join(errorList...) return o, errors.Join(errorList...)
} }
@ -79,5 +78,10 @@ func OrderHandler(w http.ResponseWriter, r *http.Request) {
writeErrors(w, r, http.StatusUnprocessableEntity, errorMessages(err)...) writeErrors(w, r, http.StatusUnprocessableEntity, errorMessages(err)...)
return return
} }
o.id, err = insertPending(o)
if err != nil {
log.Println("ORDER_SHEETS_ERROR", err)
o.id = newOrderID()
}
startCheckout(w, r, o) startCheckout(w, r, o)
} }

166
orders/sheets.go Normal file
View file

@ -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)
}

View file

@ -6,6 +6,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"time"
"github.com/stripe/stripe-go/v82" "github.com/stripe/stripe-go/v82"
"github.com/stripe/stripe-go/v82/checkout/session" "github.com/stripe/stripe-go/v82/checkout/session"
@ -24,6 +25,8 @@ func init() {
log.Println("the url is: ", siteUrl) log.Println("the url is: ", siteUrl)
stripe.Key = os.Getenv("STRIPE_SECRET_KEY") stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
webhookSecret = os.Getenv("WEBHOOK_SECRET") webhookSecret = os.Getenv("WEBHOOK_SECRET")
log.Println(stripe.Key)
log.Println(webhookSecret)
} }
func startCheckout(w http.ResponseWriter, r *http.Request, o order) { 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"), Mode: stripe.String("payment"),
ClientReferenceID: stripe.String(o.id), ClientReferenceID: stripe.String(o.id),
Locale: stripe.String("pl"), Locale: stripe.String("pl"),
ExpiresAt: stripe.Int64(time.Now().Add(time.Hour).Unix()),
LineItems: []*stripe.CheckoutSessionLineItemParams{{ LineItems: []*stripe.CheckoutSessionLineItemParams{{
Quantity: stripe.Int64(1), Quantity: stripe.Int64(1),
PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{ PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{
Currency: stripe.String("pln"), Currency: stripe.String("pln"),
UnitAmount: stripe.Int64(retainer), UnitAmount: stripe.Int64(retainer),
ProductData: &stripe.CheckoutSessionLineItemPriceDataProductDataParams{ 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 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) w.WriteHeader(http.StatusOK)
return return
} }
@ -86,14 +93,22 @@ func WebhookHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
switch { id := s.ClientReferenceID
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)
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)
}
}
} }