diff --git a/constants/pay.go b/constants/pay.go index cd39d457..91826170 100644 --- a/constants/pay.go +++ b/constants/pay.go @@ -6,3 +6,17 @@ const ( PAYMETHOD_STRIPE PayMethod = 1 PAYMETHOD_PAYPAL PayMethod = 2 ) + +type PayStatus int64 + +const ( + PAYSTATUS_SUCCESS PayStatus = 1 + PAYSTATUS_UNSUCCESS PayStatus = 0 +) + +type PayStage int64 + +const ( + PAYSTAGE_DEPOSIT PayStage = 1 // 首付 + PAYSTAGE_REMAINING PayStage = 2 // 尾款 +) diff --git a/model/gmodel/fs_cart_logic.go b/model/gmodel/fs_cart_logic.go index 34824d94..b18f7672 100755 --- a/model/gmodel/fs_cart_logic.go +++ b/model/gmodel/fs_cart_logic.go @@ -87,3 +87,10 @@ func (c *FsCartModel) GetUserCartsByIds(ctx context.Context, userId int64, ids [ err = c.db.WithContext(ctx).Model(&FsCart{}).Where("`id` in (?) and `user_id` = ?", ids, userId).Find(&resp).Error return resp, err } + +func (c *FsCartModel) DeleteCartsByIds(ctx context.Context, ids []int64) ( err error) { + if len(ids) == 0 { + return + } + return c.db.WithContext(ctx).Model(&FsCart{}).Where("`id` in (?)", ids).Delete(&FsCart{}).Error +} diff --git a/model/gmodel/fs_order_logic.go b/model/gmodel/fs_order_logic.go index 16c9a527..b96e4d90 100755 --- a/model/gmodel/fs_order_logic.go +++ b/model/gmodel/fs_order_logic.go @@ -136,6 +136,21 @@ func (m *FsOrderModel) FindCount(ctx context.Context, countBuilder *gorm.DB, fil } } +func (m *FsOrderModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB, filterMap map[string]string) (*FsOrderRel, error) { + var resp FsOrderRel + + if filterMap != nil { + rowBuilder = rowBuilder.Scopes(handler.FilterData(filterMap)) + } + + result := rowBuilder.WithContext(ctx).Limit(1).Find(&resp) + if result.Error != nil { + return nil, result.Error + } else { + return &resp, nil + } +} + // 事务 func (m *FsOrderModel) Trans(ctx context.Context, fn func(ctx context.Context, connGorm *gorm.DB) error) error { tx := m.db.Table(m.name).WithContext(ctx).Begin() diff --git a/model/gmodel/fs_pay_logic.go b/model/gmodel/fs_pay_logic.go index 885c7dbc..d470f2e9 100644 --- a/model/gmodel/fs_pay_logic.go +++ b/model/gmodel/fs_pay_logic.go @@ -1,6 +1,11 @@ package gmodel -import "context" +import ( + "context" + "fusenapi/utils/handler" + + "gorm.io/gorm" +) func (p *FsPayModel) GetListByOrderNumber(ctx context.Context, sn string) (resp []FsPay, err error) { err = p.db.WithContext(ctx).Model(&FsPay{}).Where("`order_number` = ? ", sn).Find(&resp).Error @@ -33,6 +38,69 @@ func (p *FsPayModel) CreateOrUpdate(ctx context.Context, req *FsPay) (resp *FsPa return req, err } +func (m *FsPayModel) RowSelectBuilder(selectData []string) *gorm.DB { + var rowBuilder = m.db.Table(m.name) + + if selectData != nil { + rowBuilder = rowBuilder.Select(selectData) + } else { + rowBuilder = rowBuilder.Select("*") + } + return rowBuilder +} + +func (m *FsPayModel) FindCount(ctx context.Context, countBuilder *gorm.DB, filterMap map[string]string) (int64, error) { + var count int64 + + // 过滤 + if filterMap != nil { + countBuilder = countBuilder.Scopes(handler.FilterData(filterMap)) + } + + result := countBuilder.WithContext(ctx).Limit(1).Count(&count) + if result.Error != nil { + return 0, result.Error + } else { + return count, nil + } +} + +func (m *FsPayModel) FindOneByQuery(ctx context.Context, rowBuilder *gorm.DB, filterMap map[string]string) (*FsPay, error) { + var resp FsPay + + if filterMap != nil { + rowBuilder = rowBuilder.Scopes(handler.FilterData(filterMap)) + } + + result := rowBuilder.WithContext(ctx).Limit(1).Find(&resp) + if result.Error != nil { + return nil, result.Error + } else { + return &resp, nil + } +} + +// 事务 +func (m *FsPayModel) Trans(ctx context.Context, fn func(ctx context.Context, connGorm *gorm.DB) error) error { + tx := m.db.Table(m.name).WithContext(ctx).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + if err := tx.Error; err != nil { + return err + } + + if err := fn(ctx, tx); err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + func (m *FsPayModel) TableName() string { return m.name } diff --git a/model/gmodel/fs_product_design_logic.go b/model/gmodel/fs_product_design_logic.go index a1b4fe62..71f26d17 100755 --- a/model/gmodel/fs_product_design_logic.go +++ b/model/gmodel/fs_product_design_logic.go @@ -35,6 +35,10 @@ func (d *FsProductDesignModel) UpdateBySn(ctx context.Context, sn string, data * return d.db.WithContext(ctx).Model(&FsProductDesign{}).Where("`sn` = ?", sn).Updates(&data).Error } +func (d *FsProductDesignModel) UpdateByIds(ctx context.Context, ids []int64, data *FsProductDesign) error { + return d.db.Table(d.name).WithContext(ctx).Model(&FsProductDesign{}).Where("`id` in ?", ids).Updates(&data).Error +} + func (m *FsProductDesignModel) TableName() string { return m.name } diff --git a/model/gmodel/fs_resources_gen.go b/model/gmodel/fs_resources_gen.go new file mode 100644 index 00000000..432a8113 --- /dev/null +++ b/model/gmodel/fs_resources_gen.go @@ -0,0 +1,26 @@ +package gmodel + +import ( + "gorm.io/gorm" + "time" +) + +// fs_resources 资源表 +type FsResources struct { + ResourceId string `gorm:"primary_key;default:'';" json:"resource_id"` // 资源 ID + UserId *int64 `gorm:"index;default:0;" json:"user_id"` // 用户 ID + GuestId *int64 `gorm:"index;default:0;" json:"guest_id"` // 访客 ID + ResourceType *string `gorm:"index;default:'';" json:"resource_type"` // 资源类型 + ResourceUrl *string `gorm:"default:'';" json:"resource_url"` // 资源 URL + UploadedAt *time.Time `gorm:"index;default:'0000-00-00 00:00:00';" json:"uploaded_at"` // 上传时间 + Metadata *string `gorm:"default:'';" json:"metadata"` // 元数据,json格式,存储图像分率 + MetaKey1 *string `gorm:"index;default:'';" json:"meta_key1"` // 需要关键信息查询的自定义属性1,可以动态增加 +} +type FsResourcesModel struct { + db *gorm.DB + name string +} + +func NewFsResourcesModel(db *gorm.DB) *FsResourcesModel { + return &FsResourcesModel{db: db, name: "fs_resources"} +} diff --git a/model/gmodel/fs_resources_logic.go b/model/gmodel/fs_resources_logic.go new file mode 100644 index 00000000..e68225aa --- /dev/null +++ b/model/gmodel/fs_resources_logic.go @@ -0,0 +1,2 @@ +package gmodel +// TODO: 使用model的属性做你想做的 \ No newline at end of file diff --git a/model/gmodel/fs_user_gen.go b/model/gmodel/fs_user_gen.go index 05b8cdd1..81d0f008 100644 --- a/model/gmodel/fs_user_gen.go +++ b/model/gmodel/fs_user_gen.go @@ -30,7 +30,7 @@ type FsUser struct { IsPhoneAdvertisement *int64 `gorm:"default:0;" json:"is_phone_advertisement"` // 是否接收短信广告 IsOpenRender *int64 `gorm:"default:0;" json:"is_open_render"` // 是否打开个性化渲染(1:开启,0:关闭) IsThousandFace *int64 `gorm:"default:0;" json:"is_thousand_face"` // 是否已经存在千人千面(1:存在,0:不存在) - IsLowRendering *int64 `gorm:"default:0;" json:"is_low_rendering"` // + IsLowRendering *int64 `gorm:"default:0;" json:"is_low_rendering"` // 是否开启低渲染模型渲染 IsRemoveBg *int64 `gorm:"default:1;" json:"is_remove_bg"` // 用户上传logo是否去除背景 } type FsUserModel struct { diff --git a/model/gmodel/var_gen.go b/model/gmodel/var_gen.go index a5916431..2fac48a5 100644 --- a/model/gmodel/var_gen.go +++ b/model/gmodel/var_gen.go @@ -79,6 +79,7 @@ type AllModelsGen struct { FsQuotationRemarkTemplate *FsQuotationRemarkTemplateModel // fs_quotation_remark_template 报价单备注模板 FsQuotationSaler *FsQuotationSalerModel // fs_quotation_saler 报价单业务员表 FsRefundReason *FsRefundReasonModel // fs_refund_reason + FsResources *FsResourcesModel // fs_resources 资源表 FsStandardLogo *FsStandardLogoModel // fs_standard_logo 标准logo FsTags *FsTagsModel // fs_tags 产品分类表 FsToolLogs *FsToolLogsModel // fs_tool_logs 3d设计工具日志表 @@ -169,6 +170,7 @@ func NewAllModels(gdb *gorm.DB) *AllModelsGen { FsQuotationRemarkTemplate: NewFsQuotationRemarkTemplateModel(gdb), FsQuotationSaler: NewFsQuotationSalerModel(gdb), FsRefundReason: NewFsRefundReasonModel(gdb), + FsResources: NewFsResourcesModel(gdb), FsStandardLogo: NewFsStandardLogoModel(gdb), FsTags: NewFsTagsModel(gdb), FsToolLogs: NewFsToolLogsModel(gdb), diff --git a/server/pay/internal/config/config.go b/server/pay/internal/config/config.go index d032023a..0b7a2dcb 100644 --- a/server/pay/internal/config/config.go +++ b/server/pay/internal/config/config.go @@ -10,12 +10,11 @@ type Config struct { rest.RestConf SourceMysql string Auth types.Auth - - PayConfig struct { + PayConfig struct { Stripe struct { Key string - SuccessURL string CancelURL string + SuccessURL string } } } diff --git a/server/pay/internal/handler/routes.go b/server/pay/internal/handler/routes.go index 13f9ba4c..454d6715 100644 --- a/server/pay/internal/handler/routes.go +++ b/server/pay/internal/handler/routes.go @@ -17,6 +17,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/pay/payment-intent", Handler: OrderPaymentIntentHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/api/pay/stripe-webhook", + Handler: StripeWebhookHandler(serverCtx), + }, }, ) } diff --git a/server/pay/internal/handler/stripewebhookhandler.go b/server/pay/internal/handler/stripewebhookhandler.go new file mode 100644 index 00000000..1e0da8fc --- /dev/null +++ b/server/pay/internal/handler/stripewebhookhandler.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "reflect" + + "fusenapi/utils/basic" + + "fusenapi/server/pay/internal/logic" + "fusenapi/server/pay/internal/svc" + "fusenapi/server/pay/internal/types" +) + +func StripeWebhookHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var req types.StripeWebhookReq + userinfo, err := basic.RequestParse(w, r, svcCtx, &req) + if err != nil { + return + } + + // 创建一个业务逻辑层实例 + l := logic.NewStripeWebhookLogic(r.Context(), svcCtx) + + rl := reflect.ValueOf(l) + basic.BeforeLogic(w, r, rl) + + resp := l.StripeWebhook(&req, userinfo) + + if !basic.AfterLogic(w, r, rl, resp) { + basic.NormalAfterLogic(w, r, resp) + } + } +} diff --git a/server/pay/internal/logic/orderpaymentintentlogic.go b/server/pay/internal/logic/orderpaymentintentlogic.go index 98c5f0d0..a9d13c22 100644 --- a/server/pay/internal/logic/orderpaymentintentlogic.go +++ b/server/pay/internal/logic/orderpaymentintentlogic.go @@ -84,7 +84,7 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte } // 判断订单状态以及该支付金额 - // 未支付 + var nowAt int64 = time.Now().Unix() var payAmount int64 if *orderInfo.Status == int64(constants.STATUS_NEW_NOT_PAY) { payAmount = *orderInfo.TotalAmount / 2 @@ -97,37 +97,17 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte payConfig := &pay.Config{} var generatePrepaymentReq = &pay.GeneratePrepaymentReq{ - ProductName: "aa", + ProductName: "支付标题", Amount: payAmount, Currency: "eur", Quantity: 1, - ProductDescription: "ddddddddddddddddddddddd", + ProductDescription: "支付描述", } - if constants.PayMethod(req.PayMethod) == constants.PAYMETHOD_STRIPE { - payConfig.Stripe.Key = l.svcCtx.Config.PayConfig.Stripe.Key - generatePrepaymentReq.SuccessURL = l.svcCtx.Config.PayConfig.Stripe.SuccessURL - generatePrepaymentReq.CancelURL = l.svcCtx.Config.PayConfig.Stripe.CancelURL - } - payDriver := pay.NewPayDriver(req.PayMethod, payConfig) var resData types.OrderPaymentIntentRes // 事务处理 err = orderModel.Trans(l.ctx, func(ctx context.Context, connGorm *gorm.DB) error { - // 支付预付--生成 - prepaymentRes, err := payDriver.GeneratePrepayment(generatePrepaymentReq) - if err != nil { - return err - } - resData.RedirectUrl = prepaymentRes.URL - - // 订单信息--修改 - err = gmodel.NewFsOrderModel(connGorm).Update(ctx, orderInfo) - if err != nil { - return err - } - // 支付记录--处理 //支付记录改为一条订单多条,分首款尾款 - var createdAt int64 = time.Now().Unix() var payStatus int64 = 0 var orderSource int64 = 1 var payStage int64 @@ -150,14 +130,34 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte } payStage = 2 } + + // 支付预付--生成 + if constants.PayMethod(req.PayMethod) == constants.PAYMETHOD_STRIPE { + payConfig.Stripe.Key = l.svcCtx.Config.PayConfig.Stripe.Key + generatePrepaymentReq.SuccessURL = l.svcCtx.Config.PayConfig.Stripe.SuccessURL + generatePrepaymentReq.CancelURL = l.svcCtx.Config.PayConfig.Stripe.CancelURL + } + payDriver := pay.NewPayDriver(req.PayMethod, payConfig) + prepaymentRes, err := payDriver.GeneratePrepayment(generatePrepaymentReq) + if err != nil { + return err + } + resData.RedirectUrl = prepaymentRes.URL + + // 订单信息--修改 + err = gmodel.NewFsOrderModel(connGorm).Update(ctx, orderInfo) + if err != nil { + return err + } + if fspay == nil { fspay = &gmodel.FsPay{ UserId: orderInfo.UserId, OrderNumber: orderInfo.Sn, - CreatedAt: &createdAt, + CreatedAt: &nowAt, } } else { - fspay.UpdatedAt = &createdAt + fspay.UpdatedAt = &nowAt } fspay.PayAmount = &payAmount fspay.PayStage = &payStage diff --git a/server/pay/internal/logic/stripewebhooklogic.go b/server/pay/internal/logic/stripewebhooklogic.go new file mode 100644 index 00000000..82c5275b --- /dev/null +++ b/server/pay/internal/logic/stripewebhooklogic.go @@ -0,0 +1,227 @@ +package logic + +import ( + "encoding/json" + "errors" + "fusenapi/constants" + "fusenapi/model/gmodel" + "fusenapi/utils/auth" + "fusenapi/utils/basic" + "io" + "net/http" + "time" + + "context" + + "fusenapi/server/pay/internal/svc" + "fusenapi/server/pay/internal/types" + + "github.com/stripe/stripe-go/v74" + "github.com/zeromicro/go-zero/core/logx" + "gorm.io/gorm" +) + +type StripeWebhookLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext + Payload []byte +} + +func NewStripeWebhookLogic(ctx context.Context, svcCtx *svc.ServiceContext) *StripeWebhookLogic { + return &StripeWebhookLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +func (l *StripeWebhookLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { + const MaxBodyBytes = int64(65536) + r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes) + defer r.Body.Close() + payload, err := io.ReadAll(r.Body) + if err != nil { + logx.Error(err) + w.WriteHeader(http.StatusServiceUnavailable) + return + } + l.Payload = payload +} + +// 处理逻辑后 w,r 如:重定向, resp 必须重新处理 +// func (l *StripeWebhookLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { +// // httpx.OkJsonCtx(r.Context(), w, resp) +// } + +func (l *StripeWebhookLogic) StripeWebhook(req *types.StripeWebhookReq, userinfo *auth.UserInfo) (resp *basic.Response) { + // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) + // userinfo 传入值时, 一定不为null + + event := stripe.Event{} + + if err := json.Unmarshal(l.Payload, &event); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail") + } + + // Unmarshal the event data into an appropriate struct depending on its Type + switch event.Type { + case "payment_intent.succeeded": + var paymentIntent stripe.PaymentIntent + err := json.Unmarshal(event.Data.Raw, &paymentIntent) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type payment_intent.succeeded") + } + err = l.handlePaymentIntentSucceeded(&paymentIntent) + if err != nil { + return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type Unhandled") + } + case "payment_method.attached": + var paymentMethod stripe.PaymentMethod + err := json.Unmarshal(event.Data.Raw, &paymentMethod) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type payment_method.attached") + } + // ... handle other event types + default: + logx.Error("Unhandled event") + return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type Unhandled") + } + + return resp.SetStatus(basic.CodeOK) +} + +// 成功的付款 +func (l *StripeWebhookLogic) handlePaymentIntentSucceeded(paymentIntent *stripe.PaymentIntent) error { + + // 查询支付记录 + payModel := gmodel.NewFsPayModel(l.svcCtx.MysqlConn) + rsbPay := payModel.RowSelectBuilder(nil) + rsbPay = rsbPay.Where("trade_no = ?", paymentIntent.ID) + payInfo, err := payModel.FindOneByQuery(l.ctx, rsbPay, nil) + if err != nil { + return err + } + if *payInfo.PayStatus == 1 { + return errors.New("pay status 1") + } + //订单信息 + orderDetailTemplateModel := gmodel.NewFsOrderDetailTemplateModel(l.svcCtx.MysqlConn) + orderModel := gmodel.NewFsOrderModel(l.svcCtx.MysqlConn) + rsbOrder := orderModel.RowSelectBuilder(nil) + rsbOrder = rsbOrder.Where("trade_no =?", paymentIntent.ID).Preload("FsOrderDetails") + rsbOrder = rsbOrder.Preload("FsOrderDetails", func(dbPreload *gorm.DB) *gorm.DB { + return dbPreload.Table(orderModel.TableName()).Preload("FsOrderDetailTemplateInfo", func(dbPreload *gorm.DB) *gorm.DB { + return dbPreload.Table(orderDetailTemplateModel.TableName()).Preload("FsProductDesignInfo") + }) + }) + fsOrderRel, err := orderModel.FindOneByQuery(l.ctx, rsbOrder, nil) + if err != nil { + return err + } + + var designIds []int64 + var cartIds []int64 + if len(fsOrderRel.FsOrderDetails) > 0 { + for _, fsOrderDetail := range fsOrderRel.FsOrderDetails { + if fsOrderDetail.FsOrderDetailTemplateInfo.FsProductDesignInfo.Id != 0 { + designIds = append(designIds, fsOrderDetail.FsOrderDetailTemplateInfo.FsProductDesignInfo.Id) + } + cartIds = append(cartIds, *fsOrderDetail.CartId) + } + } + + var nowTime int64 = time.Now().Unix() + + // 支付成功 + if paymentIntent.Status == "succeeded" { + var card string = paymentIntent.LatestCharge.PaymentMethodDetails.Card.Last4 + var brand string = string(paymentIntent.LatestCharge.PaymentMethodDetails.Card.Brand) + + err = orderModel.Trans(l.ctx, func(ctx context.Context, connGorm *gorm.DB) (err error) { + // 更新支付信息 + payModelT := gmodel.NewFsPayModel(connGorm) + *payInfo.PayStatus = 1 + *payInfo.PayTime = nowTime + *payInfo.CardNo = card + *payInfo.Brand = brand + _, err = payModelT.CreateOrUpdate(ctx, payInfo) + if err != nil { + return err + } + + // 更新设计数据 + productDesignModelT := gmodel.NewFsProductDesignModel(connGorm) + var isPay int64 = 1 + err = productDesignModelT.UpdateByIds(ctx, designIds, &gmodel.FsProductDesign{IsPay: &isPay}) + if err != nil { + return err + } + + var orderInfo = &gmodel.FsOrder{} + + // 支付记录是首款 + if *payInfo.PayStage == int64(constants.PAYSTAGE_DEPOSIT) { + *orderInfo.Status = int64(constants.STATUS_NEW_PART_PAY) + *orderInfo.IsPartPay = 1 + *orderInfo.PayedAmount = paymentIntent.Amount + // 删除购物车 + cartModelT := gmodel.NewFsCartModel(connGorm) + err = cartModelT.DeleteCartsByIds(ctx, cartIds) + if err != nil { + return err + } + } + + // 支付记录是尾款 + if *payInfo.PayStage == int64(constants.PAYSTAGE_REMAINING) { + if *orderInfo.Status < int64(constants.STATUS_NEW_PAY_COMPLETED) { + *orderInfo.Status = int64(constants.STATUS_NEW_PAY_COMPLETED) + } + *orderInfo.IsPayCompleted = 1 + *orderInfo.PayedAmount = *orderInfo.PayedAmount + paymentIntent.Amount + } + + // 更新订单信息 + *orderInfo.Ptime = nowTime + orderModelT := gmodel.NewFsOrderModel(connGorm) + err = orderModelT.Update(ctx, orderInfo) + if err != nil { + return err + } + return err + }) + if err != nil { + return err + } + + //千人千面的处理 + // $renderServer = (new RenderService()); + // $renderServer->thousandsFacesV2($order->id); + // //清除用户最新的设计 + // $cache = \Yii::$app->cache; + // $cache->delete(CacheConfigHelper::LAST_DESIGN . $order->user_id); + // //缓存最新订单编号 + // $cache->set(CacheConfigHelper::USER_ORDERNO . $order->user_id, $order->sn); + + // //查询用户邮箱信息 + // $user = \api\models\User::find()->where(['id' => $order->user_id])->one(); + // $redisData = [ + // 'key' => 'receipt_download', + // 'param' => [ + // 'email' => $user->email, + // 'order_id' => $order->id, + // 'pay_id' => $pay->id, + // 'type' => 1,//付款成功为1 + // ] + // ]; + // Email::timely($redisData); + } + + // 订单记录 + return nil +} diff --git a/server/pay/internal/types/types.go b/server/pay/internal/types/types.go index 13a18778..c070431b 100644 --- a/server/pay/internal/types/types.go +++ b/server/pay/internal/types/types.go @@ -16,6 +16,9 @@ type OrderPaymentIntentRes struct { RedirectUrl string `json:"redirect_url"` } +type StripeWebhookReq struct { +} + type Request struct { } diff --git a/server_api/pay.api b/server_api/pay.api index f94d77a9..e110c5d4 100644 --- a/server_api/pay.api +++ b/server_api/pay.api @@ -13,6 +13,9 @@ service pay { @handler OrderPaymentIntentHandler post /api/pay/payment-intent(OrderPaymentIntentReq) returns (response); + + @handler StripeWebhookHandler + get /api/pay/stripe-webhook(StripeWebhookReq) returns (response); } // 生成预付款 @@ -26,4 +29,10 @@ type ( OrderPaymentIntentRes { RedirectUrl string `json:"redirect_url"` } +) + +// StripeWebhook支付通知 +type ( + StripeWebhookReq { + } ) \ No newline at end of file diff --git a/utils/encryption_decryption/md5.go b/utils/encryption_decryption/md5.go new file mode 100644 index 00000000..f2a4a450 --- /dev/null +++ b/utils/encryption_decryption/md5.go @@ -0,0 +1,36 @@ +package encryption_decryption + +import ( + "bytes" + "crypto/md5" + "encoding/hex" + "encoding/json" + "sort" + "strings" +) + +func MakeSign(params map[string]interface{}) string { + // 排序 + keys := make([]string, len(params)) + i := 0 + for k, _ := range params { + keys[i] = k + i++ + } + sort.Strings(keys) + byteBuf := bytes.NewBuffer([]byte{}) + encoder := json.NewEncoder(byteBuf) + encoder.SetEscapeHTML(false) + + err := encoder.Encode(params) + + if err != nil { + panic(err) + } + + data := byteBuf.String() + + h := md5.New() + h.Write([]byte(strings.TrimRight(data, "\n"))) + return hex.EncodeToString(h.Sum(nil)) +}