Skip to content

Commit

Permalink
lemon pay
Browse files Browse the repository at this point in the history
  • Loading branch information
lyricat committed Apr 17, 2023
1 parent 5184857 commit e76e1a2
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 9 deletions.
6 changes: 5 additions & 1 deletion cmd/httpd/httpd.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/pandodao/botastic/worker"
"github.com/pandodao/botastic/worker/ordersyncer"
"github.com/pandodao/botastic/worker/rotater"
"github.com/pandodao/lemon-checkout-go"
"github.com/pandodao/mixpay-go"
"github.com/rs/cors"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -72,6 +73,8 @@ func NewCmdHttpd() *cobra.Command {

mixpayClient := mixpay.New()

lemonClient := lemon.New(cfg.Lemon.Key)

apps := app.New(h)
convs := conv.New(h)
users := user.New(h)
Expand Down Expand Up @@ -111,7 +114,7 @@ func NewCmdHttpd() *cobra.Command {
CallbackUrl: cfg.Mixpay.CallbackUrl,
ReturnTo: cfg.Mixpay.ReturnTo,
FailedReturnTo: cfg.Mixpay.FailedReturnTo,
}, orders, userz, mixpayClient)
}, orders, userz, mixpayClient, lemonClient)
hub := chanhub.New()
// var userz core.UserService

Expand Down Expand Up @@ -159,6 +162,7 @@ func NewCmdHttpd() *cobra.Command {
svr := handler.New(handler.Config{
ClientID: client.ClientID,
TrustDomains: cfg.Auth.TrustDomains,
Lemon: cfg.Lemon,
}, s, apps, indexes, users, convs, models, appz, botz, indexService, userz, convz, orderz, hub)

// api v1
Expand Down
8 changes: 8 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ mixpay:
return_to: ""
failed_return_to: ""

lemonsqueezy:
key: ""
store_id: 12345
variants:
- id: 12345
name: "$9.99"
amount: 9.99 # 3.99

order_syncer:
interval: 1s
check_interval: 10s
Expand Down
13 changes: 13 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {
OpenAI OpenAIConfig `yaml:"openai"`
Auth Auth `yaml:"auth"`
Mixpay Mixpay `yaml:"mixpay"`
Lemon Lemonsqueezy `yaml:"lemonsqueezy"`
OrderSyncer OrderSyncerConfig `yaml:"order_syncer"`
}

Expand Down Expand Up @@ -62,6 +63,18 @@ type IndexStoreRedisConfig struct {
KeyPrefix string `yaml:"key_prefix"`
}

type Lemonsqueezy struct {
Key string `yaml:"key"`
StoreID int64 `yaml:"store_id"`
Variants []LemonsqueezyVariant `yaml:"variants"`
}

type LemonsqueezyVariant struct {
ID int64 `yaml:"id" json:"id"`
Name string `yaml:"name" json:"name"`
Amount float64 `yaml:"amount" json:"amount"`
}

type OpenAIConfig struct {
Keys []string `yaml:"keys"`
Timeout time.Duration `yaml:"timeout"`
Expand Down
3 changes: 3 additions & 0 deletions core/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
OrderStatusCanceled OrderStatus = "CANCELED"

OrderChannelMixpay OrderChannel = "Mixpay"
OrderChannelLemon OrderChannel = "Lemon"
)

type Order struct {
Expand Down Expand Up @@ -79,5 +80,7 @@ type OrderStore interface {

type OrderService interface {
CreateMixpayOrder(ctx context.Context, userId uint64, amount decimal.Decimal) (string, error)
CreateLemonOrder(ctx context.Context, userID uint64, storeID int64, variantID int64, amount decimal.Decimal, redirectURL string) (string, error)
HandleMixpayCallback(ctx context.Context, orderId string, traceId string, payeeId string) error
HandleLemonCallback(ctx context.Context, orderID string, userID uint64, lemonAmount decimal.Decimal, upstreamStatus string) error
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pandodao/lemon-checkout-go v0.0.0-20230328015639-cf8cd76dd06e
github.com/pandodao/passport-go v1.0.8
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgF
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pandodao/gen v0.0.0-20230215020331-916e2ec8363e h1:J+FaCS5gZaA0SPWFTdrLo88kx4HtAwFmDKMoRMRRqKk=
github.com/pandodao/gen v0.0.0-20230215020331-916e2ec8363e/go.mod h1:qy47gg8yYvXuT9aUxl3Sukr6Oo5QBuzhbc1zNsC16m4=
github.com/pandodao/lemon-checkout-go v0.0.0-20230328015639-cf8cd76dd06e h1:TczyyD9lyzoC6PM0l/7UZ0UA38rHYRyOAyoyZziwd+s=
github.com/pandodao/lemon-checkout-go v0.0.0-20230328015639-cf8cd76dd06e/go.mod h1:qLpfVeY6gIh1HrVhjCLkXy7xUME7RErVF6R+C0OqvCg=
github.com/pandodao/milvus-sdk-go/v2 v2.0.0-20230327110027-9f97cf0e4642 h1:OT9jm+irERTFrmn307B0fvVO1z/Z/8M7Jrrn6p1IMyg=
github.com/pandodao/milvus-sdk-go/v2 v2.0.0-20230327110027-9f97cf0e4642/go.mod h1:pLq/EZwHiqnVZFtnCxUMZtS4/qk8YcJsQq8uHZxLns8=
github.com/pandodao/mixpay-go v0.0.0-20230405103808-9f656bd75b03 h1:TOcnjRJI0OCiwD5Hiq49ZFUUKDzioPNvfcgFwo7rBzA=
Expand Down
107 changes: 100 additions & 7 deletions handler/order/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,39 @@ package order
import (
"errors"
"net/http"
"strconv"

"github.com/fox-one/pkg/httputil/param"
"github.com/pandodao/botastic/config"
"github.com/pandodao/botastic/core"
"github.com/pandodao/botastic/handler/render"
"github.com/pandodao/botastic/session"
"github.com/pandodao/lemon-checkout-go"
"github.com/shopspring/decimal"
)

type CreateOrderRequest struct {
Amount decimal.Decimal `json:"amount"`
Channel string `json:"channel"`
Amount decimal.Decimal `json:"amount"`
VariantID int64 `json:"variant_id"`
RedirectURL string `json:"redirect_url"`
}

func CreateOrder(orderz core.OrderService) http.HandlerFunc {
type (
LemonWebhookPayload struct {
lemon.WebhookPayload
Meta struct {
TestMode bool `json:"test_mode"`
EventName string `json:"event_name"`
CustomData struct {
UserID string `json:"user_id"`
OrderID string `json:"order_id"`
} `json:"custom_data"`
} `json:"meta"`
}
)

func CreateOrder(orderz core.OrderService, lemon config.Lemonsqueezy) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, _ := session.UserFrom(ctx)
Expand All @@ -31,13 +51,45 @@ func CreateOrder(orderz core.OrderService) http.HandlerFunc {
return
}

code, err := orderz.CreateMixpayOrder(ctx, user.ID, body.Amount)
if err != nil {
render.Error(w, http.StatusInternalServerError, err)
return
ret := make(map[string]any)
if body.Channel == "lemon" {
var chosenVariant config.LemonsqueezyVariant
for _, v := range lemon.Variants {
if body.VariantID == v.ID {
chosenVariant = v
break
}
}

if chosenVariant.ID == 0 {
render.Error(w, http.StatusBadRequest, nil)
return
}

paymentURL, err := orderz.CreateLemonOrder(ctx, user.ID, lemon.StoreID, chosenVariant.ID, decimal.NewFromFloat(chosenVariant.Amount), body.RedirectURL)
if err != nil {
render.Error(w, http.StatusInternalServerError, err)
return
}
ret["payment_url"] = paymentURL

} else if body.Channel == "mixpay" {
code, err := orderz.CreateMixpayOrder(ctx, user.ID, body.Amount)
if err != nil {
render.Error(w, http.StatusInternalServerError, err)
return
}
ret["code"] = code

}

render.JSON(w, map[string]any{"code": code})
render.JSON(w, ret)
}
}

func GetVariants(lemon config.Lemonsqueezy) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
render.JSON(w, lemon.Variants)
}
}

Expand All @@ -62,3 +114,44 @@ func HandleMixpayCallback(orderz core.OrderService) http.HandlerFunc {
render.JSON(w, map[string]any{"code": "SUCCESS"})
}
}

func HandleLemonCallback(orderz core.OrderService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

body := &LemonWebhookPayload{}
if err := param.Binding(r, body); err != nil {
render.Error(w, http.StatusBadRequest, err)
return
}

// @TODO verify signature here. FYI: https://docs.lemonsqueezy.com/api/webhooks#webhook-requests

if body.Meta.EventName != "order_created" {
render.Error(w, http.StatusBadRequest, nil)
return
}

if body.Data.Attributes.Status != "paid" && body.Data.Attributes.Status != "cancelled" {
render.Error(w, http.StatusBadRequest, nil)
return
}

orderID := body.Meta.CustomData.OrderID
userID, _ := strconv.ParseUint(body.Meta.CustomData.UserID, 10, 64)

if orderID == "" || userID == 0 {
render.Error(w, http.StatusBadRequest, nil)
return
}

lemonOrigAmount := decimal.NewFromInt(int64(body.Data.Attributes.TotalUSD + body.Data.Attributes.DiscountTotalUSD)).
Div(decimal.NewFromInt(100))

if err := orderz.HandleLemonCallback(ctx, orderID, userID, lemonOrigAmount, body.Data.Attributes.Status); err != nil {
render.Error(w, http.StatusInternalServerError, err)
}

render.JSON(w, map[string]any{"code": "SUCCESS"})
}
}
6 changes: 5 additions & 1 deletion handler/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

"github.com/go-chi/chi"
"github.com/pandodao/botastic/config"
"github.com/pandodao/botastic/core"
"github.com/pandodao/botastic/handler/app"
"github.com/pandodao/botastic/handler/auth"
Expand Down Expand Up @@ -55,6 +56,7 @@ type (
Config struct {
ClientID string
TrustDomains []string
Lemon config.Lemonsqueezy
}

Server struct {
Expand Down Expand Up @@ -136,11 +138,13 @@ func (s Server) HandleRest() http.Handler {
})

r.With(auth.LoginRequired()).Route("/orders", func(r chi.Router) {
r.Post("/mixpay", order.CreateOrder(s.orderz))
r.Post("/", order.CreateOrder(s.orderz, s.cfg.Lemon))
r.Get("/variants", order.GetVariants(s.cfg.Lemon))
})

r.Route("/callback", func(r chi.Router) {
r.Post("/mixpay", order.HandleMixpayCallback(s.orderz))
r.Post("/lemon", order.HandleLemonCallback(s.orderz))
})

r.NotFound(func(w http.ResponseWriter, r *http.Request) {
Expand Down

0 comments on commit e76e1a2

Please sign in to comment.