api.spaceplanner.app

Spaceplanner API
git clone git://jacobedwards.org/api.spaceplanner.app
Log | Files | Refs

stripe.go (3025B)


      1 package backend
      2 
      3 import (
      4 	"encoding/json"
      5 	"errors"
      6 	"log"
      7 	"github.com/stripe/stripe-go/v72"
      8 )
      9 
     10 /*
     11  * Don't need customer.subscription.paused without free trial like stuff
     12  * <https://docs.stripe.com/billing/subscriptions/pause-payment>
     13  */
     14 func (e *Env) StripeEventHandler(ev *stripe.Event) error {
     15 	// Not sure whether I need to store in persistent database,
     16 	// maybe I should
     17 	v, exists := e.stripeProcessedEvents[ev.ID]
     18 	if exists && v {
     19 		log.Printf("StripeEventHandler: %s event already processed, ignoring (and returning success)\n", ev.ID)
     20 		return nil
     21 	}
     22 	e.stripeProcessedEvents[ev.ID] = true
     23 
     24 	var err error
     25 	switch ev.Type {
     26 	case "customer.subscription.created":
     27 		err = stripeHandle(e.stripeCreateSub, ev)
     28 	case "customer.subscription.deleted":
     29 		err = stripeHandle(e.stripeDeleteSub, ev)
     30 	case "customer.subscription.updated":
     31 		err = stripeHandle(e.stripeUpdateSub, ev)
     32 	default:
     33 		err = errors.New(ev.Type + ": Event type is not being processed")
     34 	}
     35 
     36 	if err == nil {
     37 		log.Printf("StripeEventHandler: Handled %s\n", ev.ID)
     38 	} else {
     39 		log.Printf("StripeEventHandler: %s: error: %s\n", ev.ID, err.Error())
     40 	}
     41 	return err
     42 }
     43 
     44 func (e *Env) stripeCreateSub(s *stripe.Subscription) error {
     45 	return e.stripeUpdateSub(s)
     46 }
     47 
     48 func (e *Env) stripeUpdateSub(s *stripe.Subscription) error {
     49 	update, err := e.CacheStmt("create_sub",
     50 		`INSERT INTO spaceplanner.subscriptions (id, customer_id, price_id, product_id, active)
     51 		VALUES ($1, $2, $3, $4, $5)
     52 		ON CONFLICT (id) DO UPDATE SET (price_id, product_id, active) = ($3, $4, $5)`)
     53 	if err != nil {
     54 		return err
     55 	}
     56 
     57 	price, err := e.subPrice(s)
     58 	if err != nil {
     59 		return err
     60 	}
     61 
     62 	log.Printf("stripeHandleUpdateSub sub %s for %s (active: %v)\n", s.ID, s.Customer.ID, subIsActive(s))
     63 	_, err = update.Exec(s.ID, s.Customer.ID, price.ID, price.Product.ID, subIsActive(s))
     64 	return err
     65 }
     66 
     67 func (e *Env) stripeDeleteSub(s *stripe.Subscription) error {
     68 	delete, err := e.CacheStmt("delete_sub",
     69 		`DELETE FROM spaceplanner.subscriptions
     70 		WHERE id = $1`)
     71 	if err != nil {
     72 		return err
     73 	}
     74 
     75 	_, err = delete.Exec(s.ID)
     76 	return err
     77 		
     78 }
     79 
     80 // Fetch all subscriptions anew
     81 func (e *Env) StripeUpdateSubs() error {
     82 	log.Printf("Fetching subscriptions")
     83 
     84 	tx, err := e.DB.Begin()
     85 	if err != nil {
     86 		return err
     87 	}
     88 	defer tx.Rollback()
     89 
     90 	subs := e.Stripe.Subscriptions.List(nil)
     91 	for subs.Next() {
     92 		err := e.stripeUpdateSub(subs.Subscription())
     93 		if err != nil {
     94 			return err
     95 		}
     96 	}
     97         if err := subs.Err(); err != nil {
     98                 return err
     99         }
    100 
    101 	return tx.Commit()
    102 }
    103 
    104 func stripeHandle[T any](handler func(*T) error, ev *stripe.Event) error {
    105 	var d T
    106 	if err := json.Unmarshal(ev.Data.Raw, &d); err != nil {
    107 		return err
    108 	}
    109 
    110 	return handler(&d)
    111 }
    112 
    113 func subIsActive(s *stripe.Subscription) bool {
    114 	return s.Status == stripe.SubscriptionStatusActive
    115 }
    116 
    117 func (e *Env) subPrice(s *stripe.Subscription) (*stripe.Price, error) {
    118 	if len(s.Items.Data) != 1 {
    119 		return nil, errors.New("Expected exactly one subscription item")
    120 	}
    121 	return s.Items.Data[0].Price, nil
    122 }