Merge branch 'develop' of https://gitee.com/fusenpack/fusenapi into feature/auth
This commit is contained in:
		
						commit
						719c41db00
					
				| @ -6,3 +6,17 @@ const ( | |||||||
| 	PAYMETHOD_STRIPE PayMethod = 1 | 	PAYMETHOD_STRIPE PayMethod = 1 | ||||||
| 	PAYMETHOD_PAYPAL PayMethod = 2 | 	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 // 尾款 | ||||||
|  | ) | ||||||
|  | |||||||
| @ -12,8 +12,12 @@ const ( | |||||||
| 	WEBSOCKET_RENDER_IMAGE = "WEBSOCKET_RENDER_IMAGE" | 	WEBSOCKET_RENDER_IMAGE = "WEBSOCKET_RENDER_IMAGE" | ||||||
| 	//数据格式错误 | 	//数据格式错误 | ||||||
| 	WEBSOCKET_ERR_DATA_FORMAT = "WEBSOCKET_ERR_DATA_FORMAT" | 	WEBSOCKET_ERR_DATA_FORMAT = "WEBSOCKET_ERR_DATA_FORMAT" | ||||||
| 	// | 	//第三方登录通知 | ||||||
|  | 	WEBSOCKET_THIRD_PARTY_LOGIN_NOTIFY = "WEBSOCKET_THIRD_PARTY_LOGIN_NOTIFY" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // 云渲染完成通知api需要的签名字符串 | // 云渲染完成通知api需要的签名字符串 | ||||||
| const RENDER_NOTIFY_SIGN_KEY = "fusen-render-notify-%s-%d" | const RENDER_NOTIFY_SIGN_KEY = "fusen-render-notify-%s-%d" | ||||||
|  | 
 | ||||||
|  | // 第三方登录通知api需要的签名字符串 | ||||||
|  | const THIRD_PARTY_LOGIN_NOTIFY_SIGN_KEY = "fusen-render-notify-%s-%d" | ||||||
|  | |||||||
| @ -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 | 	err = c.db.WithContext(ctx).Model(&FsCart{}).Where("`id` in (?) and `user_id` = ?", ids, userId).Find(&resp).Error | ||||||
| 	return resp, err | 	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 | ||||||
|  | } | ||||||
|  | |||||||
| @ -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 { | 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() | 	tx := m.db.Table(m.name).WithContext(ctx).Begin() | ||||||
|  | |||||||
| @ -1,6 +1,11 @@ | |||||||
| package gmodel | 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) { | 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 | 	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 | 	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 { | func (m *FsPayModel) TableName() string { | ||||||
| 	return m.name | 	return m.name | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 | 	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 { | func (m *FsProductDesignModel) TableName() string { | ||||||
| 	return m.name | 	return m.name | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,3 +1 @@ | |||||||
| package gmodel | package gmodel | ||||||
| 
 |  | ||||||
| // TODO: 使用model的属性做你想做的 |  | ||||||
|  | |||||||
| @ -52,6 +52,7 @@ type AllModelsGen struct { | |||||||
| 	FsOrderDetailTemplate     *FsOrderDetailTemplateModel     // fs_order_detail_template 订单模板详细表 | 	FsOrderDetailTemplate     *FsOrderDetailTemplateModel     // fs_order_detail_template 订单模板详细表 | ||||||
| 	FsOrderRemark             *FsOrderRemarkModel             // fs_order_remark 订单备注表 | 	FsOrderRemark             *FsOrderRemarkModel             // fs_order_remark 订单备注表 | ||||||
| 	FsPay                     *FsPayModel                     // fs_pay 支付记录 | 	FsPay                     *FsPayModel                     // fs_pay 支付记录 | ||||||
|  | 	FsPayEvent                *FsPayEventModel                // fs_pay_event 支付回调事件日志 | ||||||
| 	FsProduct                 *FsProductModel                 // fs_product 产品表 | 	FsProduct                 *FsProductModel                 // fs_product 产品表 | ||||||
| 	FsProductCopy1            *FsProductCopy1Model            // fs_product_copy1 产品表 | 	FsProductCopy1            *FsProductCopy1Model            // fs_product_copy1 产品表 | ||||||
| 	FsProductDesign           *FsProductDesignModel           // fs_product_design 产品设计表 | 	FsProductDesign           *FsProductDesignModel           // fs_product_design 产品设计表 | ||||||
| @ -143,6 +144,7 @@ func NewAllModels(gdb *gorm.DB) *AllModelsGen { | |||||||
| 		FsOrderDetailTemplate:     NewFsOrderDetailTemplateModel(gdb), | 		FsOrderDetailTemplate:     NewFsOrderDetailTemplateModel(gdb), | ||||||
| 		FsOrderRemark:             NewFsOrderRemarkModel(gdb), | 		FsOrderRemark:             NewFsOrderRemarkModel(gdb), | ||||||
| 		FsPay:                     NewFsPayModel(gdb), | 		FsPay:                     NewFsPayModel(gdb), | ||||||
|  | 		FsPayEvent:                NewFsPayEventModel(gdb), | ||||||
| 		FsProduct:                 NewFsProductModel(gdb), | 		FsProduct:                 NewFsProductModel(gdb), | ||||||
| 		FsProductCopy1:            NewFsProductCopy1Model(gdb), | 		FsProductCopy1:            NewFsProductCopy1Model(gdb), | ||||||
| 		FsProductDesign:           NewFsProductDesignModel(gdb), | 		FsProductDesign:           NewFsProductDesignModel(gdb), | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| Name: pay | Name: pay | ||||||
| Host: 0.0.0.0 | Host: 0.0.0.0 | ||||||
| Port: 9915 | Port: 9915 | ||||||
|  | Timeout: 15000 | ||||||
| SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest | SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest | ||||||
| Auth: | Auth: | ||||||
|     AccessSecret: fusen2023 |     AccessSecret: fusen2023 | ||||||
| @ -9,5 +10,6 @@ Auth: | |||||||
| PayConfig: | PayConfig: | ||||||
|   Stripe: |   Stripe: | ||||||
|     Key: "sk_test_51IisojHygnIJZeghPVSBhkwySfcyDV4SoAduIxu3J7bvSJ9cZMD96LY1LO6SpdbYquLJX5oKvgEBB67KT9pecfCy00iEC4pp9y" |     Key: "sk_test_51IisojHygnIJZeghPVSBhkwySfcyDV4SoAduIxu3J7bvSJ9cZMD96LY1LO6SpdbYquLJX5oKvgEBB67KT9pecfCy00iEC4pp9y" | ||||||
|  |     EndpointSecret: "whsec_f5f9a121d43af3789db7459352f08cf523eb9e0fbf3381f91ba6c97c324c174d" | ||||||
|     SuccessURL: "http://www.baidu.com" |     SuccessURL: "http://www.baidu.com" | ||||||
|     CancelURL: "http://www.baidu.com" |     CancelURL: "http://www.baidu.com" | ||||||
| @ -10,12 +10,12 @@ type Config struct { | |||||||
| 	rest.RestConf | 	rest.RestConf | ||||||
| 	SourceMysql string | 	SourceMysql string | ||||||
| 	Auth        types.Auth | 	Auth        types.Auth | ||||||
| 
 |  | ||||||
| 	PayConfig   struct { | 	PayConfig   struct { | ||||||
| 		Stripe struct { | 		Stripe struct { | ||||||
|  | 			EndpointSecret string | ||||||
| 			Key            string | 			Key            string | ||||||
| 			SuccessURL string |  | ||||||
| 			CancelURL      string | 			CancelURL      string | ||||||
|  | 			SuccessURL     string | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,6 +17,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { | |||||||
| 				Path:    "/api/pay/payment-intent", | 				Path:    "/api/pay/payment-intent", | ||||||
| 				Handler: OrderPaymentIntentHandler(serverCtx), | 				Handler: OrderPaymentIntentHandler(serverCtx), | ||||||
| 			}, | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Method:  http.MethodPost, | ||||||
|  | 				Path:    "/api/pay/stripe-webhook", | ||||||
|  | 				Handler: StripeWebhookHandler(serverCtx), | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										48
									
								
								server/pay/internal/handler/stripewebhookhandler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								server/pay/internal/handler/stripewebhookhandler.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | package handler | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"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) { | ||||||
|  | 
 | ||||||
|  | 		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 { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var req types.StripeWebhookReq | ||||||
|  | 
 | ||||||
|  | 		// userinfo, err := basic.RequestParse(w, r, svcCtx, &req) | ||||||
|  | 		// if err != nil { | ||||||
|  | 		// 	return | ||||||
|  | 		// } | ||||||
|  | 
 | ||||||
|  | 		req.Payload = payload | ||||||
|  | 		req.StripeSignature = r.Header.Get("Stripe-Signature") | ||||||
|  | 
 | ||||||
|  | 		// 创建一个业务逻辑层实例 | ||||||
|  | 		l := logic.NewStripeWebhookLogic(r.Context(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 		rl := reflect.ValueOf(l) | ||||||
|  | 		basic.BeforeLogic(w, r, rl) | ||||||
|  | 
 | ||||||
|  | 		resp := l.StripeWebhook(&req, nil) | ||||||
|  | 
 | ||||||
|  | 		if !basic.AfterLogic(w, r, rl, resp) { | ||||||
|  | 			basic.NormalAfterLogic(w, r, resp) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -84,7 +84,7 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// 判断订单状态以及该支付金额 | 	// 判断订单状态以及该支付金额 | ||||||
| 	// 未支付 | 	var nowAt int64 = time.Now().Unix() | ||||||
| 	var payAmount int64 | 	var payAmount int64 | ||||||
| 	if *orderInfo.Status == int64(constants.STATUS_NEW_NOT_PAY) { | 	if *orderInfo.Status == int64(constants.STATUS_NEW_NOT_PAY) { | ||||||
| 		payAmount = *orderInfo.TotalAmount / 2 | 		payAmount = *orderInfo.TotalAmount / 2 | ||||||
| @ -97,37 +97,18 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte | |||||||
| 
 | 
 | ||||||
| 	payConfig := &pay.Config{} | 	payConfig := &pay.Config{} | ||||||
| 	var generatePrepaymentReq = &pay.GeneratePrepaymentReq{ | 	var generatePrepaymentReq = &pay.GeneratePrepaymentReq{ | ||||||
| 		ProductName:        "aa", | 		OrderSn:            req.Sn, | ||||||
|  | 		ProductName:        "支付标题", | ||||||
| 		Amount:             payAmount, | 		Amount:             payAmount, | ||||||
| 		Currency:           "eur", | 		Currency:           "eur", | ||||||
| 		Quantity:           1, | 		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 | 	var resData types.OrderPaymentIntentRes | ||||||
| 	// 事务处理 | 	// 事务处理 | ||||||
| 	err = orderModel.Trans(l.ctx, func(ctx context.Context, connGorm *gorm.DB) error { | 	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 payStatus int64 = 0 | ||||||
| 		var orderSource int64 = 1 | 		var orderSource int64 = 1 | ||||||
| 		var payStage int64 | 		var payStage int64 | ||||||
| @ -150,18 +131,37 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte | |||||||
| 			} | 			} | ||||||
| 			payStage = 2 | 			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 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 订单信息--修改 | ||||||
|  | 		err = gmodel.NewFsOrderModel(connGorm).Update(ctx, orderInfo) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if fspay == nil { | 		if fspay == nil { | ||||||
| 			fspay = &gmodel.FsPay{ | 			fspay = &gmodel.FsPay{ | ||||||
| 				UserId:      orderInfo.UserId, | 				UserId:      orderInfo.UserId, | ||||||
| 				OrderNumber: orderInfo.Sn, | 				OrderNumber: orderInfo.Sn, | ||||||
| 				CreatedAt:   &createdAt, | 				CreatedAt:   &nowAt, | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			fspay.UpdatedAt = &createdAt | 			fspay.UpdatedAt = &nowAt | ||||||
| 		} | 		} | ||||||
| 		fspay.PayAmount = &payAmount | 		fspay.PayAmount = &payAmount | ||||||
| 		fspay.PayStage = &payStage | 		fspay.PayStage = &payStage | ||||||
| 		fspay.TradeNo = &prepaymentRes.TradeNo | 		//fspay.TradeNo = &prepaymentRes.TradeNo | ||||||
| 		fspay.PaymentMethod = &req.PayMethod | 		fspay.PaymentMethod = &req.PayMethod | ||||||
| 		fspay.OrderSource = &orderSource | 		fspay.OrderSource = &orderSource | ||||||
| 		fspay.PayStatus = &payStatus | 		fspay.PayStatus = &payStatus | ||||||
| @ -171,6 +171,9 @@ func (l *OrderPaymentIntentLogic) OrderPaymentIntent(req *types.OrderPaymentInte | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		resData.RedirectUrl = prepaymentRes.URL | ||||||
|  | 		resData.ClientSecret = prepaymentRes.ClientSecret | ||||||
|  | 
 | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										296
									
								
								server/pay/internal/logic/stripewebhooklogic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								server/pay/internal/logic/stripewebhooklogic.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,296 @@ | |||||||
|  | package logic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fusenapi/constants" | ||||||
|  | 	"fusenapi/model/gmodel" | ||||||
|  | 	"fusenapi/utils/auth" | ||||||
|  | 	"fusenapi/utils/basic" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"fusenapi/server/pay/internal/svc" | ||||||
|  | 	"fusenapi/server/pay/internal/types" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stripe/stripe-go/v74" | ||||||
|  | 	"github.com/stripe/stripe-go/v74/webhook" | ||||||
|  | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | 	"gorm.io/gorm" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type StripeWebhookLogic struct { | ||||||
|  | 	logx.Logger | ||||||
|  | 	ctx    context.Context | ||||||
|  | 	svcCtx *svc.ServiceContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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) { | ||||||
|  | 
 | ||||||
|  | // } | ||||||
|  | 
 | ||||||
|  | // 处理逻辑后 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 | ||||||
|  | 
 | ||||||
|  | 	stripe.Key = l.svcCtx.Config.PayConfig.Stripe.Key | ||||||
|  | 	event := stripe.Event{} | ||||||
|  | 
 | ||||||
|  | 	if err := json.Unmarshal(req.Payload, &event); err != nil { | ||||||
|  | 		logx.Error(err) | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	endpointSecret := l.svcCtx.Config.PayConfig.Stripe.EndpointSecret | ||||||
|  | 	signatureHeader := req.StripeSignature | ||||||
|  | 	event, err := webhook.ConstructEvent(req.Payload, signatureHeader, endpointSecret) | ||||||
|  | 	if err != nil { | ||||||
|  | 		logx.Error(err) | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "Webhook signature verification failed") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Unmarshal the event data into an appropriate struct depending on its Type | ||||||
|  | 	switch event.Type { | ||||||
|  | 	case "charge.succeeded": | ||||||
|  | 		// var charge stripe.Charge | ||||||
|  | 		// err := json.Unmarshal(event.Data.Raw, &charge) | ||||||
|  | 		// if err != nil { | ||||||
|  | 		// 	logx.Error(err) | ||||||
|  | 		// 	return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type charge.succeeded") | ||||||
|  | 		// } | ||||||
|  | 
 | ||||||
|  | 	case "checkout.session.completed": | ||||||
|  | 		// checkout checkout.session.completed 处理逻辑 | ||||||
|  | 		// var session stripe.CheckoutSession | ||||||
|  | 		// err := json.Unmarshal(event.Data.Raw, &session) | ||||||
|  | 		// if err != nil { | ||||||
|  | 		// 	logx.Error(err) | ||||||
|  | 		// 	return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "pay notify Unmarshal fail event.Type payment_intent.succeeded") | ||||||
|  | 		// } | ||||||
|  | 		// fmt.Println("checkout.session.completed") | ||||||
|  | 		// err = l.handlePaymentSessionCompleted(session.ID, session.PaymentIntent.ID) | ||||||
|  | 		// if err != nil { | ||||||
|  | 		// 	return resp.SetStatusWithMessage(basic.CodeAesCbcDecryptionErr, "checkout.session.completed fail") | ||||||
|  | 		// } | ||||||
|  | 	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) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // session完成 | ||||||
|  | // func (l *StripeWebhookLogic) handlePaymentSessionCompleted(sessionId string, tradeNo string) (err error) { | ||||||
|  | // 	// 查询支付记录 | ||||||
|  | // 	payModel := gmodel.NewFsPayModel(l.svcCtx.MysqlConn) | ||||||
|  | // 	rsbPay := payModel.RowSelectBuilder(nil) | ||||||
|  | // 	rsbPay = rsbPay.Where("session_id = ?", sessionId) | ||||||
|  | // 	payInfo, err := payModel.FindOneByQuery(l.ctx, rsbPay, nil) | ||||||
|  | // 	if err != nil { | ||||||
|  | // 		return err | ||||||
|  | // 	} | ||||||
|  | // 	if *payInfo.PayStatus == 0 { | ||||||
|  | // 		*payInfo.TradeNo = tradeNo | ||||||
|  | // 		_, err = payModel.CreateOrUpdate(l.ctx, payInfo) | ||||||
|  | // 		if err != nil { | ||||||
|  | // 			return err | ||||||
|  | // 		} | ||||||
|  | // 	} else { | ||||||
|  | // 		return errors.New("pay status 1") | ||||||
|  | // 	} | ||||||
|  | // 	return err | ||||||
|  | // } | ||||||
|  | 
 | ||||||
|  | // 成功的付款 | ||||||
|  | func (l *StripeWebhookLogic) handlePaymentIntentSucceeded(paymentIntent *stripe.PaymentIntent) error { | ||||||
|  | 	orderSn, ok := paymentIntent.Metadata["order_sn"] | ||||||
|  | 	if !ok || orderSn == "" { | ||||||
|  | 		return errors.New("order_sn not found") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 查询支付记录 | ||||||
|  | 	payModel := gmodel.NewFsPayModel(l.svcCtx.MysqlConn) | ||||||
|  | 	rsbPay := payModel.RowSelectBuilder(nil) | ||||||
|  | 	rsbPay = rsbPay.Where("order_number = ?", orderSn) | ||||||
|  | 	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) | ||||||
|  | 	fsOrderDetailModel := gmodel.NewFsOrderDetailModel(l.svcCtx.MysqlConn) | ||||||
|  | 	fsProductDesignModel := gmodel.NewFsProductDesignModel(l.svcCtx.MysqlConn) | ||||||
|  | 
 | ||||||
|  | 	rsbOrder := orderModel.RowSelectBuilder(nil) | ||||||
|  | 	rsbOrder = rsbOrder.Where("sn =?", orderSn).Preload("FsOrderDetails") | ||||||
|  | 	rsbOrder = rsbOrder.Preload("FsOrderDetails", func(dbPreload *gorm.DB) *gorm.DB { | ||||||
|  | 		return dbPreload.Table(fsOrderDetailModel.TableName()).Preload("FsOrderDetailTemplateInfo", func(dbPreload *gorm.DB) *gorm.DB { | ||||||
|  | 			return dbPreload.Table(orderDetailTemplateModel.TableName()).Preload("FsProductDesignInfo", func(dbPreload *gorm.DB) *gorm.DB { | ||||||
|  | 				return dbPreload.Table(fsProductDesignModel.TableName()) | ||||||
|  | 			}) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | 	fsOrderRelInfo, err := orderModel.FindOneByQuery(l.ctx, rsbOrder, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var designIds []int64 | ||||||
|  | 	var cartIds []int64 | ||||||
|  | 	if len(fsOrderRelInfo.FsOrderDetails) > 0 { | ||||||
|  | 		for _, fsOrderDetail := range fsOrderRelInfo.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 | ||||||
|  | 		var brand string | ||||||
|  | 		if paymentIntent.LatestCharge.PaymentMethodDetails != nil { | ||||||
|  | 			if paymentIntent.LatestCharge.PaymentMethodDetails.Card != nil { | ||||||
|  | 				if paymentIntent.LatestCharge.PaymentMethodDetails.Card.Last4 != "" { | ||||||
|  | 					card = paymentIntent.LatestCharge.PaymentMethodDetails.Card.Last4 | ||||||
|  | 				} | ||||||
|  | 				if paymentIntent.LatestCharge.PaymentMethodDetails.Card.Brand != "" { | ||||||
|  | 					brand = 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{} | ||||||
|  | 			var orderStatus int64 | ||||||
|  | 			var orderIsPartPay int64 | ||||||
|  | 			var orderPayedAmount int64 | ||||||
|  | 			var orderIsPayCompleted int64 | ||||||
|  | 			// 支付记录是首款 | ||||||
|  | 			if *payInfo.PayStage == int64(constants.PAYSTAGE_DEPOSIT) { | ||||||
|  | 				orderStatus = int64(constants.STATUS_NEW_PART_PAY) | ||||||
|  | 				orderIsPartPay = 1 | ||||||
|  | 				orderInfo.IsPartPay = &orderIsPartPay | ||||||
|  | 				orderPayedAmount = 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) { | ||||||
|  | 					orderStatus = int64(constants.STATUS_NEW_PAY_COMPLETED) | ||||||
|  | 				} | ||||||
|  | 				orderIsPayCompleted = 1 | ||||||
|  | 				orderInfo.IsPayCompleted = &orderIsPayCompleted | ||||||
|  | 				orderPayedAmount = *orderInfo.PayedAmount + paymentIntent.Amount | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// 更新订单信息 | ||||||
|  | 			orderInfo.Id = fsOrderRelInfo.Id | ||||||
|  | 			orderInfo.Status = &orderStatus | ||||||
|  | 			orderInfo.Ptime = &nowTime | ||||||
|  | 			orderInfo.PayedAmount = &orderPayedAmount | ||||||
|  | 			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 | ||||||
|  | } | ||||||
| @ -14,6 +14,12 @@ type OrderPaymentIntentReq struct { | |||||||
| 
 | 
 | ||||||
| type OrderPaymentIntentRes struct { | type OrderPaymentIntentRes struct { | ||||||
| 	RedirectUrl  string `json:"redirect_url"` | 	RedirectUrl  string `json:"redirect_url"` | ||||||
|  | 	ClientSecret string `json:"clientSecret"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type StripeWebhookReq struct { | ||||||
|  | 	Payload         []byte `json:"base_byte_slice,optional"` | ||||||
|  | 	StripeSignature string `json:"Stripe-Signature"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Request struct { | type Request struct { | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { | |||||||
| 		[]rest.Route{ | 		[]rest.Route{ | ||||||
| 			{ | 			{ | ||||||
| 				Method:  http.MethodGet, | 				Method:  http.MethodGet, | ||||||
| 				Path:    "/api/product-template/get_product_template_tags", | 				Path:    "/api/product-template-tag/get_product_template_tags", | ||||||
| 				Handler: GetProductTemplateTagsHandler(serverCtx), | 				Handler: GetProductTemplateTagsHandler(serverCtx), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | |||||||
| @ -47,6 +47,7 @@ func (l *GetProductTemplateTagsLogic) GetProductTemplateTags(req *types.GetProdu | |||||||
| 	list := make([]types.GetProductTemplateTagsRsp, 0, len(productTemplateTags)) | 	list := make([]types.GetProductTemplateTagsRsp, 0, len(productTemplateTags)) | ||||||
| 	for _, v := range productTemplateTags { | 	for _, v := range productTemplateTags { | ||||||
| 		list = append(list, types.GetProductTemplateTagsRsp{ | 		list = append(list, types.GetProductTemplateTagsRsp{ | ||||||
|  | 			Id:    v.Id, | ||||||
| 			Tag:   *v.Title, | 			Tag:   *v.Title, | ||||||
| 			Cover: *v.CoverImg, | 			Cover: *v.CoverImg, | ||||||
| 		}) | 		}) | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ type GetProductTemplateTagsReq struct { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type GetProductTemplateTagsRsp struct { | type GetProductTemplateTagsRsp struct { | ||||||
|  | 	Id    int64  `json:"id"` | ||||||
| 	Tag   string `json:"tag"` | 	Tag   string `json:"tag"` | ||||||
| 	Cover string `json:"cover"` | 	Cover string `json:"cover"` | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,6 +22,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { | |||||||
| 				Path:    "/api/websocket/render_notify", | 				Path:    "/api/websocket/render_notify", | ||||||
| 				Handler: RenderNotifyHandler(serverCtx), | 				Handler: RenderNotifyHandler(serverCtx), | ||||||
| 			}, | 			}, | ||||||
|  | 			{ | ||||||
|  | 				Method:  http.MethodPost, | ||||||
|  | 				Path:    "/api/websocket/third_party_login_notify", | ||||||
|  | 				Handler: ThirdPartyLoginNotifyHandler(serverCtx), | ||||||
|  | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,35 @@ | |||||||
|  | package handler | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"reflect" | ||||||
|  | 
 | ||||||
|  | 	"fusenapi/utils/basic" | ||||||
|  | 
 | ||||||
|  | 	"fusenapi/server/websocket/internal/logic" | ||||||
|  | 	"fusenapi/server/websocket/internal/svc" | ||||||
|  | 	"fusenapi/server/websocket/internal/types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func ThirdPartyLoginNotifyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { | ||||||
|  | 	return func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 
 | ||||||
|  | 		var req types.ThirdPartyLoginNotifyReq | ||||||
|  | 		userinfo, err := basic.RequestParse(w, r, svcCtx, &req) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 创建一个业务逻辑层实例 | ||||||
|  | 		l := logic.NewThirdPartyLoginNotifyLogic(r.Context(), svcCtx) | ||||||
|  | 
 | ||||||
|  | 		rl := reflect.ValueOf(l) | ||||||
|  | 		basic.BeforeLogic(w, r, rl) | ||||||
|  | 
 | ||||||
|  | 		resp := l.ThirdPartyLoginNotify(&req, userinfo) | ||||||
|  | 
 | ||||||
|  | 		if !basic.AfterLogic(w, r, rl, resp) { | ||||||
|  | 			basic.NormalAfterLogic(w, r, resp) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -44,14 +44,17 @@ var ( | |||||||
| 	} | 	} | ||||||
| 	//升级websocket | 	//升级websocket | ||||||
| 	upgrade = websocket.Upgrader{ | 	upgrade = websocket.Upgrader{ | ||||||
| 		ReadBufferSize: 1024 * 10, //最大可读取大小 10M | 		//最大可读取大小 10M | ||||||
|  | 		ReadBufferSize: 1024 * 10, | ||||||
| 		//握手超时时间15s | 		//握手超时时间15s | ||||||
| 		HandshakeTimeout: time.Second * 15, | 		HandshakeTimeout: time.Second * 15, | ||||||
| 		//允许跨域 | 		//允许跨域 | ||||||
| 		CheckOrigin: func(r *http.Request) bool { | 		CheckOrigin: func(r *http.Request) bool { | ||||||
| 			return true | 			return true | ||||||
| 		}, | 		}, | ||||||
|  | 		//写的缓存池 | ||||||
| 		WriteBufferPool: &buffPool, | 		WriteBufferPool: &buffPool, | ||||||
|  | 		//是否支持压缩 | ||||||
| 		EnableCompression: true, | 		EnableCompression: true, | ||||||
| 	} | 	} | ||||||
| 	//websocket连接存储 | 	//websocket连接存储 | ||||||
| @ -78,13 +81,14 @@ func (l *DataTransferLogic) DataTransfer(svcCtx *svc.ServiceContext, w http.Resp | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	defer conn.Close() | 	defer conn.Close() | ||||||
| 	w.Header().Set("Connection", "Upgrade") |  | ||||||
| 	rsp := types.DataTransferData{} |  | ||||||
| 	//鉴权不成功10秒后断开 | 	//鉴权不成功10秒后断开 | ||||||
| 	/*isAuth, _ := l.checkAuth(svcCtx, r) | 	/*isAuth, _ := l.checkAuth(svcCtx, r) | ||||||
| 	if !isAuth { | 	if !isAuth { | ||||||
| 		time.Sleep(time.Second) //兼容下火狐 | 		time.Sleep(time.Second) //兼容下火狐 | ||||||
| 		rsp.T = constants.WEBSOCKET_UNAUTH | 		rsp := types.DataTransferData{ | ||||||
|  | 			T: constants.WEBSOCKET_UNAUTH, | ||||||
|  | 			D: nil, | ||||||
|  | 		} | ||||||
| 		b, _ := json.Marshal(rsp) | 		b, _ := json.Marshal(rsp) | ||||||
| 		//先发一条正常信息 | 		//先发一条正常信息 | ||||||
| 		_ = conn.WriteMessage(websocket.TextMessage, b) | 		_ = conn.WriteMessage(websocket.TextMessage, b) | ||||||
| @ -98,8 +102,8 @@ func (l *DataTransferLogic) DataTransfer(svcCtx *svc.ServiceContext, w http.Resp | |||||||
| 		conn:      conn, | 		conn:      conn, | ||||||
| 		uniqueId:  uniqueId, | 		uniqueId:  uniqueId, | ||||||
| 		closeChan: make(chan struct{}, 1), | 		closeChan: make(chan struct{}, 1), | ||||||
| 		inChan:    make(chan []byte, 100), | 		inChan:    make(chan []byte, 1000), | ||||||
| 		outChan:   make(chan []byte, 100), | 		outChan:   make(chan []byte, 1000), | ||||||
| 		renderProperty: renderProperty{ | 		renderProperty: renderProperty{ | ||||||
| 			renderImageTask:        make(map[string]struct{}), | 			renderImageTask:        make(map[string]struct{}), | ||||||
| 			renderImageTaskCtlChan: make(chan renderImageControlChanItem, 100), | 			renderImageTaskCtlChan: make(chan renderImageControlChanItem, 100), | ||||||
| @ -110,9 +114,7 @@ func (l *DataTransferLogic) DataTransfer(svcCtx *svc.ServiceContext, w http.Resp | |||||||
| 	defer ws.close() | 	defer ws.close() | ||||||
| 	//把连接成功消息发回去 | 	//把连接成功消息发回去 | ||||||
| 	time.Sleep(time.Second) //兼容下火狐 | 	time.Sleep(time.Second) //兼容下火狐 | ||||||
| 	rsp.T = constants.WEBSOCKET_CONNECT_SUCCESS | 	b := ws.respondDataFormat(constants.WEBSOCKET_CONNECT_SUCCESS, uniqueId) | ||||||
| 	rsp.D = uniqueId |  | ||||||
| 	b, _ := json.Marshal(rsp) |  | ||||||
| 	_ = conn.WriteMessage(websocket.TextMessage, b) | 	_ = conn.WriteMessage(websocket.TextMessage, b) | ||||||
| 	//循环读客户端信息 | 	//循环读客户端信息 | ||||||
| 	go ws.readLoop() | 	go ws.readLoop() | ||||||
| @ -243,8 +245,18 @@ func (w *wsConnectItem) sendToOutChan(data []byte) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 获取需要渲染图片的map key | // 获取需要渲染图片的map key | ||||||
| func (w *wsConnectItem) getRenderImageMapKey(productId, templateTagId int64, algorithmVersion string) string { | func (w *wsConnectItem) getRenderImageMapKey(productId, templateTagId, logoId int64, algorithmVersion string) string { | ||||||
| 	return fmt.Sprintf("%d-%d-%s", productId, templateTagId, algorithmVersion) | 	return fmt.Sprintf("%d-%d-%d-%s", productId, templateTagId, logoId, algorithmVersion) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化返回数据 | ||||||
|  | func (w *wsConnectItem) respondDataFormat(msgType string, data interface{}) []byte { | ||||||
|  | 	d := types.DataTransferData{ | ||||||
|  | 		T: msgType, | ||||||
|  | 		D: data, | ||||||
|  | 	} | ||||||
|  | 	b, _ := json.Marshal(d) | ||||||
|  | 	return b | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 处理接受到的数据 | // 处理接受到的数据 | ||||||
| @ -252,6 +264,7 @@ func (w *wsConnectItem) dealwithReciveData(data []byte) { | |||||||
| 	var parseInfo types.DataTransferData | 	var parseInfo types.DataTransferData | ||||||
| 	if err := json.Unmarshal(data, &parseInfo); err != nil { | 	if err := json.Unmarshal(data, &parseInfo); err != nil { | ||||||
| 		logx.Error("invalid format of websocket message") | 		logx.Error("invalid format of websocket message") | ||||||
|  | 		w.outChan <- w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket message:"+string(data)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	d, _ := json.Marshal(parseInfo.D) | 	d, _ := json.Marshal(parseInfo.D) | ||||||
|  | |||||||
| @ -1,10 +1,6 @@ | |||||||
| package logic | package logic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/sha256" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"fusenapi/constants" | 	"fusenapi/constants" | ||||||
| 	"fusenapi/utils/basic" | 	"fusenapi/utils/basic" | ||||||
| 	"time" | 	"time" | ||||||
| @ -45,7 +41,7 @@ func (l *RenderNotifyLogic) RenderNotify(req *types.RenderNotifyReq) (resp *basi | |||||||
| 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid param time") | 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid param time") | ||||||
| 	} | 	} | ||||||
| 	//验证签名 sha256 | 	//验证签名 sha256 | ||||||
| 	notifyByte, _ := json.Marshal(req.Info) | 	/*notifyByte, _ := json.Marshal(req.Info) | ||||||
| 	h := sha256.New() | 	h := sha256.New() | ||||||
| 	h.Write([]byte(fmt.Sprintf(constants.RENDER_NOTIFY_SIGN_KEY, string(notifyByte), req.Time))) | 	h.Write([]byte(fmt.Sprintf(constants.RENDER_NOTIFY_SIGN_KEY, string(notifyByte), req.Time))) | ||||||
| 	signHex := h.Sum(nil) | 	signHex := h.Sum(nil) | ||||||
| @ -53,7 +49,7 @@ func (l *RenderNotifyLogic) RenderNotify(req *types.RenderNotifyReq) (resp *basi | |||||||
| 	//fmt.Println(sign) | 	//fmt.Println(sign) | ||||||
| 	if req.Sign != sign { | 	if req.Sign != sign { | ||||||
| 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid sign") | 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid sign") | ||||||
| 	} | 	}*/ | ||||||
| 	//遍历websocket链接把数据传进去 | 	//遍历websocket链接把数据传进去 | ||||||
| 	mapConnPool.Range(func(key, value any) bool { | 	mapConnPool.Range(func(key, value any) bool { | ||||||
| 		//断言连接 | 		//断言连接 | ||||||
| @ -61,21 +57,21 @@ func (l *RenderNotifyLogic) RenderNotify(req *types.RenderNotifyReq) (resp *basi | |||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 		renderKey := ws.getRenderImageMapKey(req.Info.ProductId, req.Info.TemplateTagId, req.Info.AlgorithmVersion) | 		//关闭标识 | ||||||
|  | 		if ws.isClose { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 		renderKey := ws.getRenderImageMapKey(req.Info.ProductId, req.Info.TemplateTagId, req.Info.LogoId, req.Info.AlgorithmVersion) | ||||||
| 		//查询有无该渲染任务 | 		//查询有无该渲染任务 | ||||||
| 		_, ok = ws.renderProperty.renderImageTask[renderKey] | 		_, ok = ws.renderProperty.renderImageTask[renderKey] | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 		rspData := types.DataTransferData{ | 		b := ws.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, types.RenderImageRspMsg{ | ||||||
| 			T: constants.WEBSOCKET_RENDER_IMAGE, |  | ||||||
| 			D: types.RenderImageRspMsg{ |  | ||||||
| 			ProductId:     req.Info.ProductId, | 			ProductId:     req.Info.ProductId, | ||||||
| 			TemplateTagId: req.Info.TemplateTagId, | 			TemplateTagId: req.Info.TemplateTagId, | ||||||
| 			Image:         req.Info.Image, | 			Image:         req.Info.Image, | ||||||
| 			}, | 		}) | ||||||
| 		} |  | ||||||
| 		b, _ := json.Marshal(rspData) |  | ||||||
| 		//删除对应的需要渲染的图片map | 		//删除对应的需要渲染的图片map | ||||||
| 		ws.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{ | 		ws.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{ | ||||||
| 			Option: 0, //0删除 1添加 | 			Option: 0, //0删除 1添加 | ||||||
|  | |||||||
| @ -0,0 +1,78 @@ | |||||||
|  | package logic | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fusenapi/constants" | ||||||
|  | 	"fusenapi/utils/auth" | ||||||
|  | 	"fusenapi/utils/basic" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"fusenapi/server/websocket/internal/svc" | ||||||
|  | 	"fusenapi/server/websocket/internal/types" | ||||||
|  | 
 | ||||||
|  | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type ThirdPartyLoginNotifyLogic struct { | ||||||
|  | 	logx.Logger | ||||||
|  | 	ctx    context.Context | ||||||
|  | 	svcCtx *svc.ServiceContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewThirdPartyLoginNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ThirdPartyLoginNotifyLogic { | ||||||
|  | 	return &ThirdPartyLoginNotifyLogic{ | ||||||
|  | 		Logger: logx.WithContext(ctx), | ||||||
|  | 		ctx:    ctx, | ||||||
|  | 		svcCtx: svcCtx, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 处理进入前逻辑w,r | ||||||
|  | // func (l *ThirdPartyLoginNotifyLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { | ||||||
|  | // } | ||||||
|  | 
 | ||||||
|  | // 处理逻辑后 w,r 如:重定向, resp 必须重新处理 | ||||||
|  | // func (l *ThirdPartyLoginNotifyLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { | ||||||
|  | // // httpx.OkJsonCtx(r.Context(), w, resp) | ||||||
|  | // } | ||||||
|  | 
 | ||||||
|  | func (l *ThirdPartyLoginNotifyLogic) ThirdPartyLoginNotify(req *types.ThirdPartyLoginNotifyReq, userinfo *auth.UserInfo) (resp *basic.Response) { | ||||||
|  | 	if time.Now().Unix()-120 > req.Time /*|| req.Time > time.Now().Unix() */ { | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param: time is invalid") | ||||||
|  | 	} | ||||||
|  | 	if req.Info.WebsocketId <= 0 { | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:websocket_id is required") | ||||||
|  | 	} | ||||||
|  | 	if req.Info.Token == "" { | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:token is required") | ||||||
|  | 	} | ||||||
|  | 	//验证签名 sha256 | ||||||
|  | 	/*notifyByte, _ := json.Marshal(req.Info) | ||||||
|  | 	h := sha256.New() | ||||||
|  | 	h.Write([]byte(fmt.Sprintf(constants.THIRD_PARTY_LOGIN_NOTIFY_SIGN_KEY, string(notifyByte), req.Time))) | ||||||
|  | 	signHex := h.Sum(nil) | ||||||
|  | 	sign := hex.EncodeToString(signHex) | ||||||
|  | 	//fmt.Println(sign) | ||||||
|  | 	if req.Sign != sign { | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "invalid sign") | ||||||
|  | 	}*/ | ||||||
|  | 	//查询对应websocket连接 | ||||||
|  | 	val, ok := mapConnPool.Load(req.Info.WebsocketId) | ||||||
|  | 	if !ok { | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeOK, "success:websocket connection is not exists") | ||||||
|  | 	} | ||||||
|  | 	ws, ok := val.(wsConnectItem) | ||||||
|  | 	if !ok { | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeServiceErr, "type of websocket connect object is err") | ||||||
|  | 	} | ||||||
|  | 	b := ws.respondDataFormat(constants.WEBSOCKET_THIRD_PARTY_LOGIN_NOTIFY, types.ThirdPartyLoginRspMsg{ | ||||||
|  | 		Token: req.Info.Token, | ||||||
|  | 	}) | ||||||
|  | 	select { | ||||||
|  | 	case <-ws.closeChan: | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeOK, "websocket connect object is closed") | ||||||
|  | 	case ws.outChan <- b: | ||||||
|  | 		return resp.SetStatusWithMessage(basic.CodeOK, "success") | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -2,6 +2,7 @@ package logic | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"fusenapi/constants" | ||||||
| 	"fusenapi/server/websocket/internal/types" | 	"fusenapi/server/websocket/internal/types" | ||||||
| 	"github.com/zeromicro/go-zero/core/logx" | 	"github.com/zeromicro/go-zero/core/logx" | ||||||
| ) | ) | ||||||
| @ -18,31 +19,6 @@ type renderImageControlChanItem struct { | |||||||
| 	Key    string //map的key | 	Key    string //map的key | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 渲染请求数据处理发送云渲染服务处理 |  | ||||||
| func (w *wsConnectItem) SendToCloudRender(data []byte) { |  | ||||||
| 	var renderImageData types.RenderImageReqMsg |  | ||||||
| 	if err := json.Unmarshal(data, &renderImageData); err != nil { |  | ||||||
| 		logx.Error("invalid format of websocket render image message", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	logx.Info("收到请求云渲染图片数据:", renderImageData) |  | ||||||
| 	//把需要渲染的图片任务加进去 |  | ||||||
| 	for _, productId := range renderImageData.ProductIds { |  | ||||||
| 		select { |  | ||||||
| 		case <-w.closeChan: //连接关闭了 |  | ||||||
| 			return |  | ||||||
| 		default: |  | ||||||
| 			//加入渲染任务 |  | ||||||
| 			key := w.getRenderImageMapKey(productId, renderImageData.TemplateTagId, renderImageData.AlgorithmVersion) |  | ||||||
| 			w.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{ |  | ||||||
| 				Option: 1, //0删除 1添加 |  | ||||||
| 				Key:    key, |  | ||||||
| 			} |  | ||||||
| 			// TODO 数据发送给云渲染服务器 |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 操作连接中渲染任务的增加/删除 | // 操作连接中渲染任务的增加/删除 | ||||||
| func (w *wsConnectItem) operationRenderTask() { | func (w *wsConnectItem) operationRenderTask() { | ||||||
| 	for { | 	for { | ||||||
| @ -61,3 +37,29 @@ func (w *wsConnectItem) operationRenderTask() { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // 渲染请求数据处理发送云渲染服务处理 | ||||||
|  | func (w *wsConnectItem) SendToCloudRender(data []byte) { | ||||||
|  | 	var renderImageData types.RenderImageReqMsg | ||||||
|  | 	if err := json.Unmarshal(data, &renderImageData); err != nil { | ||||||
|  | 		w.outChan <- w.respondDataFormat(constants.WEBSOCKET_ERR_DATA_FORMAT, "invalid format of websocket render image message:"+string(data)) | ||||||
|  | 		logx.Error("invalid format of websocket render image message", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	logx.Info("收到请求云渲染图片数据:", renderImageData) | ||||||
|  | 	//把需要渲染的图片任务加进去 | ||||||
|  | 	for _, productId := range renderImageData.ProductIds { | ||||||
|  | 		select { | ||||||
|  | 		case <-w.closeChan: //连接关闭了 | ||||||
|  | 			return | ||||||
|  | 		default: | ||||||
|  | 			//加入渲染任务 | ||||||
|  | 			key := w.getRenderImageMapKey(productId, renderImageData.TemplateTagId, renderImageData.LogoId, renderImageData.AlgorithmVersion) | ||||||
|  | 			w.renderProperty.renderImageTaskCtlChan <- renderImageControlChanItem{ | ||||||
|  | 				Option: 1, //0删除 1添加 | ||||||
|  | 				Key:    key, | ||||||
|  | 			} | ||||||
|  | 			// TODO 数据发送给云渲染服务器 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ type DataTransferData struct { | |||||||
| type RenderImageReqMsg struct { | type RenderImageReqMsg struct { | ||||||
| 	ProductIds       []int64 `json:"product_ids"`                //产品 id | 	ProductIds       []int64 `json:"product_ids"`                //产品 id | ||||||
| 	TemplateTagId    int64   `json:"template_tag_id"`            //模板标签id | 	TemplateTagId    int64   `json:"template_tag_id"`            //模板标签id | ||||||
|  | 	LogoId           int64   `json:"logo_id"`                    //logoid | ||||||
| 	AlgorithmVersion string  `json:"algorithm_version,optional"` //算法版本 | 	AlgorithmVersion string  `json:"algorithm_version,optional"` //算法版本 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -20,9 +21,14 @@ type RenderImageRspMsg struct { | |||||||
| 	ProductId        int64  `json:"product_id"`                 //产品 id | 	ProductId        int64  `json:"product_id"`                 //产品 id | ||||||
| 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | ||||||
| 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | ||||||
|  | 	LogoId           int64  `json:"logo_id"`                    //logoid | ||||||
| 	Image            string `json:"image"`                      //渲染后的图片 | 	Image            string `json:"image"`                      //渲染后的图片 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type ThirdPartyLoginRspMsg struct { | ||||||
|  | 	Token string `json:"token"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type RenderNotifyReq struct { | type RenderNotifyReq struct { | ||||||
| 	Sign string     `json:"sign"` | 	Sign string     `json:"sign"` | ||||||
| 	Time int64      `json:"time"` | 	Time int64      `json:"time"` | ||||||
| @ -33,9 +39,21 @@ type NotifyInfo struct { | |||||||
| 	ProductId        int64  `json:"product_id"`                 //产品id | 	ProductId        int64  `json:"product_id"`                 //产品id | ||||||
| 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | ||||||
| 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | ||||||
|  | 	LogoId           int64  `json:"logo_id"`                    //logoid | ||||||
| 	Image            string `json:"image"` | 	Image            string `json:"image"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type ThirdPartyLoginNotifyReq struct { | ||||||
|  | 	Sign string                `json:"sign"` | ||||||
|  | 	Time int64                 `json:"time"` | ||||||
|  | 	Info ThirdPartyLoginNotify `json:"info"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ThirdPartyLoginNotify struct { | ||||||
|  | 	WebsocketId uint64 `json:"websocket_id"` | ||||||
|  | 	Token       string `json:"token"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type Request struct { | type Request struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,6 +13,9 @@ service pay { | |||||||
| 
 | 
 | ||||||
| 	@handler OrderPaymentIntentHandler | 	@handler OrderPaymentIntentHandler | ||||||
| 	post /api/pay/payment-intent(OrderPaymentIntentReq) returns (response); | 	post /api/pay/payment-intent(OrderPaymentIntentReq) returns (response); | ||||||
|  | 
 | ||||||
|  | 	@handler StripeWebhookHandler | ||||||
|  | 	post /api/pay/stripe-webhook(StripeWebhookReq) returns (response); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 生成预付款 | // 生成预付款 | ||||||
| @ -25,5 +28,14 @@ type ( | |||||||
| 	} | 	} | ||||||
| 	OrderPaymentIntentRes { | 	OrderPaymentIntentRes { | ||||||
| 		RedirectUrl  string `json:"redirect_url"` | 		RedirectUrl  string `json:"redirect_url"` | ||||||
|  | 		ClientSecret string `json:"clientSecret"` | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // StripeWebhook支付通知 | ||||||
|  | type ( | ||||||
|  | 	StripeWebhookReq { | ||||||
|  | 		Payload         []byte `json:"base_byte_slice,optional"` | ||||||
|  | 		StripeSignature string `json:"Stripe-Signature"` | ||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
| @ -12,7 +12,7 @@ import "basic.api" | |||||||
| service product-template-tag { | service product-template-tag { | ||||||
| 	//获取产品模板标签列表 | 	//获取产品模板标签列表 | ||||||
| 	@handler GetProductTemplateTagsHandler | 	@handler GetProductTemplateTagsHandler | ||||||
| 	get /api/product-template/get_product_template_tags(GetProductTemplateTagsReq) returns (response); | 	get /api/product-template-tag/get_product_template_tags(GetProductTemplateTagsReq) returns (response); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //获取产品模板标签列表 | //获取产品模板标签列表 | ||||||
| @ -20,6 +20,7 @@ type GetProductTemplateTagsReq { | |||||||
| 	Limit int `form:"limit"` | 	Limit int `form:"limit"` | ||||||
| } | } | ||||||
| type GetProductTemplateTagsRsp { | type GetProductTemplateTagsRsp { | ||||||
|  | 	Id    int64  `json:"id"` | ||||||
| 	Tag   string `json:"tag"` | 	Tag   string `json:"tag"` | ||||||
| 	Cover string `json:"cover"` | 	Cover string `json:"cover"` | ||||||
| } | } | ||||||
| @ -12,9 +12,12 @@ service websocket { | |||||||
| 	//websocket数据交互 | 	//websocket数据交互 | ||||||
| 	@handler DataTransferHandler | 	@handler DataTransferHandler | ||||||
| 	get /api/websocket/data_transfer(request) returns (response); | 	get /api/websocket/data_transfer(request) returns (response); | ||||||
| 	//渲染完了通知接口 | 	//云渲染完了通知接口 | ||||||
| 	@handler RenderNotifyHandler | 	@handler RenderNotifyHandler | ||||||
| 	post /api/websocket/render_notify(RenderNotifyReq) returns (response); | 	post /api/websocket/render_notify(RenderNotifyReq) returns (response); | ||||||
|  | 	//第三方登录通知接口 | ||||||
|  | 	@handler ThirdPartyLoginNotifyHandler | ||||||
|  | 	post /api/websocket/third_party_login_notify(ThirdPartyLoginNotifyReq) returns (response); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //websocket数据交互 | //websocket数据交互 | ||||||
| @ -25,14 +28,19 @@ type DataTransferData { | |||||||
| type RenderImageReqMsg { //websocket接受要云渲染处理的数据 | type RenderImageReqMsg { //websocket接受要云渲染处理的数据 | ||||||
| 	ProductIds       []int64 `json:"product_ids"`                //产品 id | 	ProductIds       []int64 `json:"product_ids"`                //产品 id | ||||||
| 	TemplateTagId    int64   `json:"template_tag_id"`            //模板标签id | 	TemplateTagId    int64   `json:"template_tag_id"`            //模板标签id | ||||||
|  | 	LogoId           int64   `json:"logo_id"`                    //logoid | ||||||
| 	AlgorithmVersion string  `json:"algorithm_version,optional"` //算法版本 | 	AlgorithmVersion string  `json:"algorithm_version,optional"` //算法版本 | ||||||
| } | } | ||||||
| type RenderImageRspMsg { //websocket发送渲染完的数据 | type RenderImageRspMsg { //websocket发送渲染完的数据 | ||||||
| 	ProductId        int64  `json:"product_id"`                 //产品 id | 	ProductId        int64  `json:"product_id"`                 //产品 id | ||||||
| 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | ||||||
| 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | ||||||
|  | 	LogoId           int64  `json:"logo_id"`                    //logoid | ||||||
| 	Image            string `json:"image"`                      //渲染后的图片 | 	Image            string `json:"image"`                      //渲染后的图片 | ||||||
| } | } | ||||||
|  | type ThirdPartyLoginRspMsg { //websocket三方登录的通知数据 | ||||||
|  | 	Token string `json:"token"` | ||||||
|  | } | ||||||
| //渲染完了通知接口 | //渲染完了通知接口 | ||||||
| type RenderNotifyReq { | type RenderNotifyReq { | ||||||
| 	Sign string     `json:"sign"` | 	Sign string     `json:"sign"` | ||||||
| @ -43,5 +51,16 @@ type NotifyInfo { | |||||||
| 	ProductId        int64  `json:"product_id"`                 //产品id | 	ProductId        int64  `json:"product_id"`                 //产品id | ||||||
| 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | 	TemplateTagId    int64  `json:"template_tag_id"`            //模板标签id | ||||||
| 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | 	AlgorithmVersion string `json:"algorithm_version,optional"` //算法版本 | ||||||
|  | 	LogoId           int64  `json:"logo_id"`                    //logoid | ||||||
| 	Image            string `json:"image"` | 	Image            string `json:"image"` | ||||||
| } | } | ||||||
|  | //第三方登录通知接口 | ||||||
|  | type ThirdPartyLoginNotifyReq { | ||||||
|  | 	Sign string                `json:"sign"` | ||||||
|  | 	Time int64                 `json:"time"` | ||||||
|  | 	Info ThirdPartyLoginNotify `json:"info"` | ||||||
|  | } | ||||||
|  | type ThirdPartyLoginNotify { | ||||||
|  | 	WebsocketId uint64 `json:"websocket_id"` | ||||||
|  | 	Token       string `json:"token"` | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								utils/encryption_decryption/md5.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								utils/encryption_decryption/md5.go
									
									
									
									
									
										Normal file
									
								
							| @ -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)) | ||||||
|  | } | ||||||
| @ -24,6 +24,7 @@ type Pay interface { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type GeneratePrepaymentReq struct { | type GeneratePrepaymentReq struct { | ||||||
|  | 	OrderSn            string    `json:"order_sn"`            // 订单编号 | ||||||
| 	Amount             int64     `json:"amount"`              // 支付金额 | 	Amount             int64     `json:"amount"`              // 支付金额 | ||||||
| 	Currency           string    `json:"currency"`            // 支付货币 | 	Currency           string    `json:"currency"`            // 支付货币 | ||||||
| 	ProductName        string    `json:"product_name"`        // 商品名称 | 	ProductName        string    `json:"product_name"`        // 商品名称 | ||||||
| @ -37,4 +38,6 @@ type GeneratePrepaymentReq struct { | |||||||
| type GeneratePrepaymentRes struct { | type GeneratePrepaymentRes struct { | ||||||
| 	URL          string `json:"url"`          // 支付重定向地址 | 	URL          string `json:"url"`          // 支付重定向地址 | ||||||
| 	TradeNo      string `json:"trade_no"`     //交易ID | 	TradeNo      string `json:"trade_no"`     //交易ID | ||||||
|  | 	ClientSecret string `json:"clientSecret"` //交易密钥 | ||||||
|  | 	SessionId    string `json:"session_id"`   //SessionId | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ type Stripe struct { | |||||||
| // 生成预付款 | // 生成预付款 | ||||||
| func (stripePay *Stripe) GeneratePrepayment(req *GeneratePrepaymentReq) (res *GeneratePrepaymentRes, err error) { | func (stripePay *Stripe) GeneratePrepayment(req *GeneratePrepaymentReq) (res *GeneratePrepaymentRes, err error) { | ||||||
| 	var productData stripe.CheckoutSessionLineItemPriceDataProductDataParams | 	var productData stripe.CheckoutSessionLineItemPriceDataProductDataParams | ||||||
|  | 	// productData.Metadata = map[string]string{"order_id": "33333333333333"} | ||||||
| 
 | 
 | ||||||
| 	if req.ProductName != "" { | 	if req.ProductName != "" { | ||||||
| 		productData.Name = stripe.String(req.ProductName) | 		productData.Name = stripe.String(req.ProductName) | ||||||
| @ -30,7 +31,14 @@ func (stripePay *Stripe) GeneratePrepayment(req *GeneratePrepaymentReq) (res *Ge | |||||||
| 	// productData.Images = images | 	// productData.Images = images | ||||||
| 	stripe.Key = stripePay.Key | 	stripe.Key = stripePay.Key | ||||||
| 
 | 
 | ||||||
|  | 	// session 方式 | ||||||
| 	params := &stripe.CheckoutSessionParams{ | 	params := &stripe.CheckoutSessionParams{ | ||||||
|  | 		PaymentIntentData: &stripe.CheckoutSessionPaymentIntentDataParams{Metadata: map[string]string{"order_sn": req.OrderSn}}, | ||||||
|  | 		// Params:            stripe.Params{Metadata: map[string]string{"order_id": "1111111111111"}}, | ||||||
|  | 		PaymentMethodTypes: stripe.StringSlice([]string{ | ||||||
|  | 			"card", | ||||||
|  | 			// "ideal", | ||||||
|  | 		}), | ||||||
| 		LineItems: []*stripe.CheckoutSessionLineItemParams{ | 		LineItems: []*stripe.CheckoutSessionLineItemParams{ | ||||||
| 			{ | 			{ | ||||||
| 				PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{ | 				PriceData: &stripe.CheckoutSessionLineItemPriceDataParams{ | ||||||
| @ -47,12 +55,24 @@ func (stripePay *Stripe) GeneratePrepayment(req *GeneratePrepaymentReq) (res *Ge | |||||||
| 	} | 	} | ||||||
| 	result, err := session.New(params) | 	result, err := session.New(params) | ||||||
| 
 | 
 | ||||||
|  | 	// 密钥方式 | ||||||
|  | 	// params := &stripe.PaymentIntentParams{ | ||||||
|  | 	// 	Amount:   stripe.Int64(req.Amount), | ||||||
|  | 	// 	Currency: stripe.String(string(req.Currency)), | ||||||
|  | 	// 	AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{ | ||||||
|  | 	// 		Enabled: stripe.Bool(true), | ||||||
|  | 	// 	}, | ||||||
|  | 	// } | ||||||
|  | 	// result, err := paymentintent.New(params) | ||||||
|  | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &GeneratePrepaymentRes{ | 	return &GeneratePrepaymentRes{ | ||||||
| 		URL: result.URL, | 		URL: result.URL, | ||||||
| 		TradeNo: result.ID, | 		//TradeNo: result.ID, | ||||||
|  | 		SessionId: result.ID, | ||||||
|  | 		// ClientSecret: result.ClientSecret, | ||||||
| 	}, err | 	}, err | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user