added google sheets
This commit is contained in:
parent
37ca207b92
commit
c4b06eb6c4
7 changed files with 304 additions and 28 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
166
orders/sheets.go
Normal file
166
orders/sheets.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue