diff --git a/business/jxstore/cms/order.go b/business/jxstore/cms/order.go new file mode 100644 index 000000000..2e4ad4fc4 --- /dev/null +++ b/business/jxstore/cms/order.go @@ -0,0 +1,56 @@ +package cms + +import ( + "fmt" + + "git.rosy.net.cn/jx-callback/business/jxutils" + + "git.rosy.net.cn/jx-callback/business/model/dao" + + "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" + + "git.rosy.net.cn/jx-callback/business/model" +) + +const ( + OrderTypeDeposit = 1 //保证金 +) + +func CreateOrder(ctx *jxcontext.Context, price, orderType int) (order *model.Order, err error) { + var ( + db = dao.GetDB() + ) + order = &model.Order{ + OrderID: jxutils.GenOrderNo(), + UserID: ctx.GetUserID(), + Type: orderType, + Status: model.OrderStatusWait4Pay, + PayPrice: price, + } + dao.WrapAddIDCULEntity(order, ctx.GetUserName()) + dao.Begin(db) + defer func() { + if r := recover(); r != nil { + dao.Rollback(db) + panic(r) + } + }() + if err = dao.CreateEntity(db, order); err != nil { + dao.Rollback(db) + } + dao.Commit(db) + return order, err +} + +func Pay(ctx *jxcontext.Context, orderID, payType int, vendorPayType string) (err error) { + switch payType { + case model.PayTypeTL: + // if orderPay, err = financial.Pay4OrderByTL(ctx, order, payType, vendorPayType); err == nil && orderPay != nil { + // dao.WrapAddIDCULDEntity(orderPay, ctx.GetUserName()) + // err = dao.CreateEntity(dao.GetDB(), orderPay) + // } + default: + err = fmt.Errorf("支付方式:%d当前不支持", payType) + } + return err +} diff --git a/business/jxstore/financial/financial.go b/business/jxstore/financial/financial.go index f607effb5..60679579e 100644 --- a/business/jxstore/financial/financial.go +++ b/business/jxstore/financial/financial.go @@ -1,119 +1,174 @@ package financial import ( - "context" - "errors" - "fmt" - "mime/multipart" - "path" + "encoding/json" "strings" "time" + "git.rosy.net.cn/baseapi/platformapi/tonglianpayapi" "git.rosy.net.cn/baseapi/utils" + "git.rosy.net.cn/jx-callback/business/auth2/authprovider/weixin" "git.rosy.net.cn/jx-callback/business/jxutils" "git.rosy.net.cn/jx-callback/business/jxutils/jxcontext" - "git.rosy.net.cn/jx-callback/business/jxutils/tasksch" - "git.rosy.net.cn/jx-callback/business/jxutils/weixinmsg" + "git.rosy.net.cn/jx-callback/business/model" "git.rosy.net.cn/jx-callback/business/model/dao" - "git.rosy.net.cn/jx-callback/business/model/legacymodel" "git.rosy.net.cn/jx-callback/globals" "git.rosy.net.cn/jx-callback/globals/api" - "github.com/qiniu/api.v7/storage" ) -type tUploadFileInfo struct { - FileHeader *multipart.FileHeader - StoreID int -} - -func SendFilesToStores(ctx *jxcontext.Context, files []*multipart.FileHeader, title, shopName string, isAsync bool, userName string) (hint string, err error) { - globals.SugarLogger.Debugf("SendFilesToStores, fileCount:%d isAsync:%t, userName:%s", len(files), isAsync, userName) - if len(files) == 0 { - return "", errors.New("没有文件上传!") +func Pay4OrderByTL(ctx *jxcontext.Context, order *model.GoodsOrder, payType int, vendorPayType string) (orderPay *model.OrderPay, err error) { + payCreatedAt := time.Now() + param := &tonglianpayapi.CreateUnitorderOrderParam{ + Trxamt: int(order.ActualPayPrice), + NotifyUrl: globals.TLPayNotifyURL, + Reqsn: order.VendorOrderID, + PayType: vendorPayType, } - - fileList := make([]*tUploadFileInfo, len(files)) - for k, fileHeader := range files { - fileList[k] = &tUploadFileInfo{ - FileHeader: fileHeader, - } - fileNameParts := strings.Split(fileHeader.Filename, "_") - if len(fileNameParts) < 3 { - return "", fmt.Errorf("文件名:%s不规范,没有包含三个必要的部分", fileHeader.Filename) - } - fileList[k].StoreID = int(utils.Str2Int64WithDefault(fileNameParts[0], 0)) - if fileList[k].StoreID < 100000 || fileList[k].StoreID > 1000000 { - return "", fmt.Errorf("文件名:%s不规范,不以合法的京西门店ID开始", fileHeader.Filename) + //暂时做兼容处理 + if vendorPayType == "JSAPI" { + param.PayType = tonglianpayapi.PayTypeWxXcx + } + if vendorPayType == tonglianpayapi.PayTypeWxXcx { + if authInfo, err := ctx.GetV2AuthInfo(); err == nil && authInfo.GetAuthType() == weixin.AuthTypeMini { + param.Acct = authInfo.GetAuthID() } } - putPolicy := storage.PutPolicy{ - Scope: globals.QiniuBucket, - Expires: 10 * 60, - } - upToken := putPolicy.UploadToken(api.QiniuAPI) - cfg := &storage.Config{} - task := tasksch.NewParallelTask("SendFilesToStores", tasksch.NewParallelConfig().SetIsContinueWhenError(true), ctx, - func(t *tasksch.ParallelTask, batchItemList []interface{}, params ...interface{}) (retVal interface{}, err error) { - fileInfo := batchItemList[0].(*tUploadFileInfo) - fileHeader := fileInfo.FileHeader - db := dao.GetDB() - storeID, _ := dao.GetRealLinkStoreID(db, fileInfo.StoreID) - file, err := fileHeader.Open() - globals.SugarLogger.Debugf("SendFilesToStores upload file:%s", fileHeader.Filename) - if err == nil { - ret := storage.PutRet{} - key := "storeBill/" + utils.Int2Str(storeID) + "_" + strings.ToLower(utils.GetUUID()) + path.Ext(fileHeader.Filename) - formUploader := storage.NewFormUploader(cfg) - for i := 0; i < 3; i++ { - if err = formUploader.Put(context.Background(), &ret, upToken, key, file, fileHeader.Size, &storage.PutExtra{}); err == nil { - break - } - } - file.Close() - if err == nil { - billRec := &legacymodel.StoreBill{ - Date: time.Now(), - Url: strings.Replace(jxutils.ComposeQiniuResURL(ret.Key), "http://", "https://", -1), - StoreId: storeID, - BillName: fileHeader.Filename, - ShopName: shopName, - BillTitle: title, - } - if err = dao.CreateEntity(db, billRec); err == nil { - err = weixinmsg.NotifySaleBill(storeID, title, shopName, fmt.Sprintf("%s/billshow/?path=%s", globals.BackstageHost, billRec.Url)) - if err != nil { - globals.SugarLogger.Infof("SendFilesToStores NotifySaleBill file:%s error:%v", fileHeader.Filename, err) - } - err = nil // 忽略微信发送错误 - } else { - globals.SugarLogger.Warnf("SendFilesToStores CreateEntity file:%s error:%v", fileHeader.Filename, err) - } - } else { - globals.SugarLogger.Warnf("SendFilesToStores file:%s failed with error:%v", fileHeader.Filename, err) - } - } else { - globals.SugarLogger.Warnf("SendFilesToStores open file:%s failed with error:%v", fileHeader.Filename, err) + if vendorPayType == tonglianpayapi.PayTypeH5 { + param2 := &tonglianpayapi.CreateH5UnitorderOrderParam{ + Trxamt: int(order.ActualPayPrice), + NotifyUrl: globals.TLPayNotifyURL, + Body: "京西菜市", + Charset: "UTF-8", + } + err = api.TLpayAPI.CreateH5UnitorderOrder(param2) + } else { + result, err := api.TLpayAPI.CreateUnitorderOrder(param) + if err == nil { + var result2 tonglianpayapi.PayInfo + json.Unmarshal([]byte(result.PayInfo), &result2) + prePayID := result2.Package[strings.LastIndex(result2.Package, "=")+1 : len(result2.Package)] + orderPay = &model.OrderPay{ + PayOrderID: param.Reqsn, + PayType: payType, + VendorPayType: vendorPayType, + TransactionID: result.TrxID, + VendorOrderID: order.VendorOrderID, + VendorID: order.VendorID, + Status: 0, + PayCreatedAt: payCreatedAt, + PrepayID: prePayID, + CodeURL: utils.LimitUTF8StringLen(result.PayInfo, 3200), + TotalFee: int(order.ActualPayPrice), } - return retVal, err - }, fileList) - tasksch.HandleTask(task, nil, true).Run() - hint = task.ID - if !isAsync { - _, err = task.GetResult(0) + } } - return task.ID, err + return orderPay, err } -func GetStoreBills(ctx *jxcontext.Context, storeID int) (bills []*legacymodel.StoreBill, err error) { - db := dao.GetDB() - if err = dao.GetRows(db, &bills, ` - SELECT * - FROM store_bill - WHERE store_id = ? - ORDER BY date DESC - LIMIT 10 - `, storeID); err != nil { - return nil, err +func OnTLPayCallback(call *tonglianpayapi.CallBackResult) (err error) { + globals.SugarLogger.Debugf("OnTLPayCallback msg:%s", utils.Format4Output(call, true)) + switch call.TrxCode { + case tonglianpayapi.MsgTypePay: + err = onTLpayFinished(call) + case tonglianpayapi.MsgTypeRefund: + err = onTLpayRefund(call) } - return bills, err + return err +} + +func onTLpayFinished(call *tonglianpayapi.CallBackResult) (err error) { + orderPay := &model.OrderPay{ + PayOrderID: call.CusorderID, + // PayType: model.PayTypeTL, + } + orderPay.DeletedAt = utils.DefaultTimeValue + db := dao.GetDB() + if err = dao.GetEntity(db, orderPay, "PayOrderID", "DeletedAt"); err == nil { + if orderPay.Status != 0 { + globals.SugarLogger.Debugf("already pay msg:%s, err:%v", utils.Format4Output(call, true), err) + return err + } + loc, _ := time.LoadLocation("Local") + t1, _ := time.ParseInLocation("20060102150405", call.PayTime, loc) + orderPay.PayFinishedAt = utils.Time2Pointer(t1) + // orderPay.TransactionID = call.ChnlTrxID + orderPay.OriginalData = utils.Format4Output(call, true) + if call.TrxStatus == tonglianpayapi.TrxStatusSuccess { + orderPay.Status = model.PayStatusYes + } else { + orderPay.Status = model.PayStatusFailed + } + dao.UpdateEntity(db, orderPay) + if call.TrxStatus == tonglianpayapi.TrxStatusSuccess { + // err = OnPayFinished(orderPay) + } + } else { + globals.SugarLogger.Debugf("onTLpayFinished msg:%s, err:%v", utils.Format4Output(call, true), err) + } + return err +} + +func onTLpayRefund(call *tonglianpayapi.CallBackResult) (err error) { + orderPayRefund := &model.OrderPayRefund{ + RefundID: call.CusorderID, + } + db := dao.GetDB() + if err = dao.GetEntity(db, orderPayRefund, "RefundID"); err == nil { + if call.TrxStatus == tonglianpayapi.TrxStatusSuccess { + orderPayRefund.Status = model.RefundStatusYes + } else { + orderPayRefund.Status = model.RefundStatusFailed + } + orderPayRefund.OriginalData = utils.Format4Output(call, true) + dao.UpdateEntity(db, orderPayRefund) + } else if dao.IsNoRowsError(err) { + globals.SugarLogger.Warnf("收到异常的退款事件, call:%s", utils.Format4Output(call, true)) + } + + orderPay := &model.OrderPay{ + VendorOrderID: orderPayRefund.VendorOrderID, + VendorID: jxutils.GetPossibleVendorIDFromVendorOrderID(orderPayRefund.VendorOrderID), + PayType: model.PayTypeWX, + Status: model.PayStatusYes, + } + orderPay.DeletedAt = utils.DefaultTimeValue + if err = dao.GetEntity(db, orderPay, "VendorOrderID", "VendorID", "PayType", "Status", "DeletedAt"); err == nil { + orderPay.Status = model.PayStatusRefund + dao.UpdateEntity(db, orderPay) + } + return err +} + +func RefundOrderByTL(ctx *jxcontext.Context, orderPay *model.OrderPay, refundID string, refundFee int, refundDesc string) (orderPayRefund *model.OrderPayRefund, err error) { + result, err := api.TLpayAPI.PayRefund(&tonglianpayapi.PayRefundParam{ + Trxamt: refundFee, + Reqsn: utils.GetUUID(), + Remark: refundDesc, + OldTrxID: orderPay.TransactionID, + }) + if err == nil { + orderPayRefund = &model.OrderPayRefund{ + RefundID: refundID, + VendorRefundID: result.TrxID, + VendorOrderID: orderPay.VendorOrderID, + VendorID: orderPay.VendorID, + Status: model.RefundStatusNo, + TransactionID: orderPay.TransactionID, + RefundFee: refundFee, + RefundCreatedAt: time.Now(), + } + dao.WrapAddIDCULDEntity(orderPayRefund, ctx.GetUserName()) + db := dao.GetDB() + if result.TrxStatus == tonglianpayapi.TrxStatusSuccess { + orderPayRefund.Status = model.RefundStatusYes + } else { + orderPayRefund.Status = model.RefundStatusFailed + } + orderPayRefund.OriginalData = utils.Format4Output(result, true) + dao.CreateEntity(db, orderPayRefund) + + orderPay.Status = model.PayStatusRefund + dao.UpdateEntity(db, orderPay) + } + return orderPayRefund, err } diff --git a/business/jxutils/jxutils.go b/business/jxutils/jxutils.go index 68d80e79e..049519bad 100644 --- a/business/jxutils/jxutils.go +++ b/business/jxutils/jxutils.go @@ -172,7 +172,7 @@ func GetPossibleVendorIDFromVendorOrderID(vendorOrderID string) (vendorID int) { } func GenOrderNo() (orderNo int64) { - const prefix = 88 + var prefix = utils.Str2Int64(time.Now().Format("20060102")) const randPartNum = 1000 orderNo = time.Now().Unix() - orderNoBeginTimestamp orderNo = orderNo * randPartNum diff --git a/business/model/order.go b/business/model/order.go index 00d220e47..c1cf33aa2 100644 --- a/business/model/order.go +++ b/business/model/order.go @@ -9,9 +9,8 @@ const ( ) const ( - PayTypeWX = 1 // 微信支付 - PayTypeTL = 2 // 通联宝支付 - PayTypeTL_DiscountCard = 3 // 通联宝支付(会员折扣卡) + PayTypeWX = 1 // 微信支付 + PayTypeTL = 2 // 通联宝支付 PayStatusNo = 0 PayStatusYes = 1 @@ -449,7 +448,7 @@ type Order struct { OrderID int64 `orm:"column(order_id)" json:"orderID"` //订单号 UserID string `orm:"column(user_id);size(48)" json:"userID"` //用户ID Type int `json:"type"` //订单类型 - Status int `json:"status"` //订单状态 + Status int `json:"status"` //订单状态,待支付2,已支付5,支付成功110,支付失败115 PayPrice int `json:"payPrice"` //支付金额 Comment string `orm:"size(255)" json:"comment"` //备注 } diff --git a/controllers/order_controller.go b/controllers/order_controller.go index 28156623b..564377ff6 100644 --- a/controllers/order_controller.go +++ b/controllers/order_controller.go @@ -1,6 +1,7 @@ package controllers import ( + "git.rosy.net.cn/jx-callback/business/jxstore/cms" "github.com/astaxie/beego" ) @@ -11,31 +12,30 @@ type OrderController struct { // @Title 支付 // @Description 支付 // @Param token header string true "认证token" -// @Param type formData int true "支付类型/账单类型" -// @Param price formData int true "支付金额" +// @Param orderID formData int true "订单号" // @Param payType formData int true "支付平台类型" // @Param vendorPayType formData string true "平台支付类型" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /Pay [post] func (c *OrderController) Pay() { - + c.callPay(func(params *tOrderPayParams) (retVal interface{}, errCode string, err error) { + err = cms.Pay(params.Ctx, params.OrderID, params.PayType, params.VendorPayType) + return retVal, "", err + }) } // @Title 创建订单 // @Description 创建订单 // @Param token header string true "认证token" -// @Param jxOrder formData string true "订单信息" -// @Param addressID formData int64 true "配送地址ID" -// @Param fromStoreID formData int fasle "物料配送门店" -// @Param createType formData int false "创建类型, 0:预创建, 1:创建" -// @Param isDeliverySelf formData bool false "是否是自提单" +// @Param type formData int true "支付类型/账单类型" +// @Param price formData int true "支付金额" // @Success 200 {object} controllers.CallResult // @Failure 200 {object} controllers.CallResult // @router /CreateOrder [post] func (c *OrderController) CreateOrder() { c.callCreateOrder(func(params *tOrderCreateOrderParams) (retVal interface{}, errCode string, err error) { - + retVal, err = cms.CreateOrder(params.Ctx, params.Type, params.Price) return retVal, "", err }) } diff --git a/globals/beegodb/beegodb.go b/globals/beegodb/beegodb.go index 81f6516af..b0f68c60f 100644 --- a/globals/beegodb/beegodb.go +++ b/globals/beegodb/beegodb.go @@ -22,6 +22,7 @@ func Init() { orm.RegisterModel(&model.Job{}, &model.JobCategory{}, &model.JobStep{}) orm.RegisterModel(&model.JobOrder{}) + orm.RegisterModel(&model.OperateEvent{}) orm.RegisterModel(&model.NewConfig{}, &legacymodel.Config{}) // create table orm.RunSyncdb("default", false, true)