Initial commit
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package jiahua
|
||||
|
||||
import "time"
|
||||
|
||||
// PayTypeParam 查询支付通道请求参数
|
||||
type PayTypeParam struct {
|
||||
Moneys []int64 `json:"moneys"`
|
||||
DevType string `json:"devType"`
|
||||
PlatformID string `json:"platformID"`
|
||||
}
|
||||
|
||||
// RchgType 支付通道信息
|
||||
type RchgType struct {
|
||||
Money int64 `json:"money"`
|
||||
Alipay bool `json:"alipay"`
|
||||
Wechat bool `json:"wechat"`
|
||||
Union bool `json:"union"`
|
||||
}
|
||||
|
||||
// PayCenterMsg 查询支付通道响应
|
||||
type PayCenterMsg struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data []RchgType `json:"data"`
|
||||
}
|
||||
|
||||
// PayCenterRchg 下单请求参数
|
||||
type PayCenterRchg struct {
|
||||
TransNo string `json:"transNo"`
|
||||
PlatformID string `json:"platformID"`
|
||||
UID string `json:"uid"`
|
||||
DevID string `json:"devID"`
|
||||
UserIP string `json:"userIP"`
|
||||
Name string `json:"name"`
|
||||
DevType string `json:"devType"`
|
||||
Money int64 `json:"money"`
|
||||
Channel string `json:"channel"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
NotifyUrl string `json:"notifyUrl"`
|
||||
PType string `json:"pType"`
|
||||
PayMethod string `json:"payMethod"`
|
||||
}
|
||||
|
||||
// PayCenterRchgMsg 下单响应
|
||||
type PayCenterRchgMsg struct {
|
||||
Err string `json:"err"`
|
||||
PayUrl string `json:"payUrl"`
|
||||
OID string `json:"oid"`
|
||||
Mode string `json:"mode"`
|
||||
CID string `json:"cid"`
|
||||
Rate string `json:"rate"`
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package jiahua
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"videoweb/app/appg"
|
||||
"videoweb/pub/common/log"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/common/utils"
|
||||
"videoweb/pub/pubdef"
|
||||
)
|
||||
|
||||
const (
|
||||
notifyURLSuffix = "/api/web/third/paycenter/bill/callback"
|
||||
)
|
||||
|
||||
// GetPayChannelByMoney 向嘉华支付中心查询指定金额(分)支持的支付方式
|
||||
func GetPayChannelByMoney(ctx context.Context, moneys []int64, devType string) ([]RchgType, *srvdef.SrvErr) {
|
||||
if devType != "android" {
|
||||
devType = "ios"
|
||||
}
|
||||
param := PayTypeParam{
|
||||
Moneys: moneys,
|
||||
DevType: devType,
|
||||
PlatformID: pubdef.AppNameZhifu,
|
||||
}
|
||||
reqBody, err := json.Marshal(param)
|
||||
if err != nil {
|
||||
return nil, srvdef.New(srvdef.Err, "", err.Error())
|
||||
}
|
||||
resp := &PayCenterMsg{}
|
||||
url := appg.Conf.TransactionUrl + "/api/recharge/payType"
|
||||
code, err := utils.POSTJsonWithJResp(resp, url, nil, reqBody)
|
||||
if err != nil {
|
||||
log.Error("jiahua:GetPayChannelByMoney failed", log.Any("url", url), log.E(err))
|
||||
return nil, srvdef.New(srvdef.Err, "查询支付通道失败", err.Error())
|
||||
}
|
||||
if code != http.StatusOK || resp.Code != 200 {
|
||||
log.Error("jiahua:GetPayChannelByMoney bad resp", log.Any("code", code), log.Any("resp.Code", resp.Code))
|
||||
return nil, srvdef.New(srvdef.Err, "查询支付通道失败", "")
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
// SubmitOrder 向嘉华支付中心提交下单
|
||||
// 返回 SubmitVipOrderInfo(含 PayUrl、Oid 等),供调用方存入本地订单。
|
||||
func SubmitOrder(ctx context.Context,
|
||||
userId int64, userName string,
|
||||
tradeNo string, amount int64,
|
||||
payMode string, sysType string, devType string, ip string,
|
||||
) (*pubdef.SubmitVipOrderInfo, *srvdef.SrvErr) {
|
||||
|
||||
reqParam := &PayCenterRchg{
|
||||
TransNo: tradeNo,
|
||||
PlatformID: pubdef.AppNameZhifu,
|
||||
UID: strconv.FormatInt(userId, 10),
|
||||
Name: userName,
|
||||
UserIP: ip,
|
||||
DevType: sysType,
|
||||
Money: amount,
|
||||
Channel: "self",
|
||||
CreatedAt: time.Now(),
|
||||
PayMethod: payMode,
|
||||
PType: payMode,
|
||||
NotifyUrl: appg.Conf.LocalWebUrl + notifyURLSuffix,
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(reqParam)
|
||||
if err != nil {
|
||||
return nil, srvdef.New(srvdef.Err, "", err.Error())
|
||||
}
|
||||
|
||||
resp := &PayCenterRchgMsg{}
|
||||
url := appg.Conf.TransactionUrl + "/api/recharge/recharge"
|
||||
code, err := utils.POSTJsonWithJResp(resp, url, nil, reqBody)
|
||||
if err != nil {
|
||||
log.Error("jiahua:SubmitOrder POSTJsonWithJResp failed", log.Any("url", url), log.E(err))
|
||||
return nil, srvdef.New(srvdef.Err, "调用支付中心失败", err.Error())
|
||||
}
|
||||
if code != http.StatusOK {
|
||||
log.Error("jiahua:SubmitOrder bad http code", log.Any("code", code))
|
||||
return nil, srvdef.New(srvdef.Err, "调用支付中心失败", "")
|
||||
}
|
||||
if resp.Err != "" {
|
||||
log.Error("jiahua:SubmitOrder resp error", log.Any("err", resp.Err), log.Any("tradeNo", tradeNo))
|
||||
return nil, srvdef.New(srvdef.Err, "支付中心下单失败", resp.Err)
|
||||
}
|
||||
|
||||
return &pubdef.SubmitVipOrderInfo{
|
||||
TradeNo: tradeNo,
|
||||
Oid: resp.OID,
|
||||
PayUrl: resp.PayUrl,
|
||||
PayUrlMode: resp.Mode,
|
||||
PayChannel: resp.CID,
|
||||
PayRate: resp.Rate,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package rechargeser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"videoweb/app/service/rechargeser/jiahua"
|
||||
"videoweb/pub/common/log"
|
||||
"videoweb/pub/common/srvdef"
|
||||
"videoweb/pub/common/useragent"
|
||||
"videoweb/pub/dataReport"
|
||||
"videoweb/pub/models/eventmod"
|
||||
"videoweb/pub/models/rechargepackagemod"
|
||||
"videoweb/pub/models/usermod"
|
||||
"videoweb/pub/models/vipordermod"
|
||||
"videoweb/pub/mongos"
|
||||
"videoweb/pub/pubdef"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// SubmitOrderResp 创建第三方支付订单的响应
|
||||
type SubmitOrderResp struct {
|
||||
TradeNo string `json:"tradeNo"` // 本地订单号(可用于查询)
|
||||
PayUrl string `json:"payUrl"` // 支付链接
|
||||
PayUrlMode string `json:"payUrlMode"` // 链接打开方式
|
||||
IsOpenNewBrowser bool `json:"isOpenNewBrowser"` // 是否需要外部浏览器打开
|
||||
}
|
||||
|
||||
// CreateOrder 创建第三方支付 VIP 订单。
|
||||
// 防重复:15 秒内同一用户+同一套餐的 pending 订单直接返回原支付链接。
|
||||
func CreateOrder(ctx context.Context,
|
||||
userId int64, userName string,
|
||||
packageId int64, payMode string,
|
||||
sysType string, devType string, ip string,
|
||||
districtCode string,
|
||||
) (*SubmitOrderResp, *srvdef.SrvErr) {
|
||||
|
||||
pkg, serr := rechargepackagemod.FindOneRechargePackage(ctx, bson.M{
|
||||
rechargepackagemod.FN.RechargePackage.Id: packageId,
|
||||
rechargepackagemod.FN.RechargePackage.PackageType: rechargepackagemod.PackageTypeVip,
|
||||
rechargepackagemod.FN.RechargePackage.Status: rechargepackagemod.StatusEnabled,
|
||||
})
|
||||
if serr != nil || pkg == nil {
|
||||
return nil, srvdef.New(srvdef.ErrNoData, "会员套餐不存在或已下架", "")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dupFilter := bson.M{
|
||||
vipordermod.FN.VipOrder.UserId: userId,
|
||||
vipordermod.FN.VipOrder.CardId: packageId,
|
||||
vipordermod.FN.VipOrder.PayMode: payMode,
|
||||
vipordermod.FN.VipOrder.Status: pubdef.VipOrderPending,
|
||||
vipordermod.FN.VipOrder.CreatedAt: bson.M{"$gte": now.Add(-15 * time.Second)},
|
||||
}
|
||||
existing, _ := vipordermod.FindOneVipOrder(ctx, dupFilter)
|
||||
if existing != nil && existing.PayUrl != "" {
|
||||
return &SubmitOrderResp{
|
||||
TradeNo: existing.TradeNo,
|
||||
PayUrl: existing.PayUrl,
|
||||
PayUrlMode: existing.PayUrlMode,
|
||||
IsOpenNewBrowser: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
tradeNo := pubdef.GetTrandNoWithUUID()
|
||||
amount := pkg.Price
|
||||
|
||||
payInfo, serr := jiahua.SubmitOrder(ctx, userId, userName, tradeNo, amount, payMode, sysType, devType, ip)
|
||||
if serr != nil {
|
||||
return nil, serr
|
||||
}
|
||||
|
||||
order := &vipordermod.VipOrder{
|
||||
Id: mongos.Lid.VipOrder.Get(),
|
||||
OrderType: vipordermod.OrderTypeVip,
|
||||
UserId: userId,
|
||||
CardId: packageId, // 兼容历史字段,存购买套餐ID
|
||||
CardName: pkg.Name,
|
||||
Amount: amount,
|
||||
PayMode: payMode,
|
||||
TradeNo: tradeNo,
|
||||
Oid: payInfo.Oid,
|
||||
Status: pubdef.VipOrderPending,
|
||||
PayUrl: payInfo.PayUrl,
|
||||
PayUrlMode: payInfo.PayUrlMode,
|
||||
PayChannel: payInfo.PayChannel,
|
||||
PayRate: payInfo.PayRate,
|
||||
IPAddr: ip,
|
||||
SysType: sysType,
|
||||
DistrictCode: strings.TrimSpace(districtCode),
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if serr = vipordermod.InsertOneVipOrder(ctx, order); serr != nil {
|
||||
log.Error("rechargeser:CreateOrder InsertOneVipOrder failed",
|
||||
log.Any("tradeNo", tradeNo), log.E(serr))
|
||||
return nil, serr
|
||||
}
|
||||
|
||||
go reportOrderCreated(ctx, order, pkg, userId, ip)
|
||||
|
||||
return &SubmitOrderResp{
|
||||
TradeNo: tradeNo,
|
||||
PayUrl: payInfo.PayUrl,
|
||||
PayUrlMode: payInfo.PayUrlMode,
|
||||
IsOpenNewBrowser: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetOrderHistory 查询用户的 VIP 订单历史
|
||||
func GetOrderHistory(ctx context.Context, userId int64, page, pageSize int64) (vipordermod.VipOrderList, int64, *srvdef.SrvErr) {
|
||||
filter := bson.M{
|
||||
vipordermod.FN.VipOrder.UserId: userId,
|
||||
vipordermod.FN.VipOrder.OrderType: vipordermod.OrderTypeVip,
|
||||
}
|
||||
total, serr := vipordermod.CountVipOrder(ctx, filter)
|
||||
if serr != nil {
|
||||
return nil, 0, serr
|
||||
}
|
||||
|
||||
skip := (page - 1) * pageSize
|
||||
opts := options.Find().
|
||||
SetSort(bson.M{vipordermod.FN.VipOrder.CreatedAt: -1}).
|
||||
SetSkip(skip).
|
||||
SetLimit(pageSize)
|
||||
list, serr := vipordermod.FindManyVipOrder(ctx, filter, opts)
|
||||
return list, total, serr
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息(供 app 层展示当前 VIP 状态)
|
||||
func GetUserInfo(ctx context.Context, userId int64) (*usermod.User, *srvdef.SrvErr) {
|
||||
return usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: userId})
|
||||
}
|
||||
|
||||
func reportOrderCreated(ctx context.Context, order *vipordermod.VipOrder, pkg *rechargepackagemod.RechargePackage, userId int64, ip string) {
|
||||
user, _ := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: userId})
|
||||
districtCode := ""
|
||||
if user != nil {
|
||||
districtCode = user.DistrictCode
|
||||
}
|
||||
|
||||
ua := useragent.UA{SysType: order.SysType}
|
||||
eventId := newEventId(pubdef.TrackerAppId(), user.Id, order.TradeNo, order.CreatedAt.Unix())
|
||||
commonField := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderCreated, districtCode, ua, strconv.FormatInt(user.Id, 10), ip)
|
||||
|
||||
commonField.Payload = dataReport.JsonRawMessage(&dataReport.OrderCreatedEvent{
|
||||
OrderID: order.TradeNo,
|
||||
OrderType: "vip_subscription",
|
||||
ProductID: strconv.FormatInt(pkg.Id, 10),
|
||||
ProductName: pkg.Name,
|
||||
Amount: dataReport.JsonNumber(order.Amount),
|
||||
Currency: "CNY",
|
||||
CreateTime: dataReport.JsonNumber(order.CreatedAt.Unix()),
|
||||
})
|
||||
|
||||
eventData, _ := json.Marshal(commonField)
|
||||
if err := eventmod.InsertOneEvent(ctx, &eventmod.Event{
|
||||
EventId: commonField.EventID,
|
||||
EventType: commonField.Event,
|
||||
Data: string(eventData),
|
||||
Status: eventmod.Default,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}); err != nil {
|
||||
log.Error("reportOrderCreated InsertOneEvent failed",
|
||||
log.Any("tradeNo", order.TradeNo), log.E(err))
|
||||
}
|
||||
}
|
||||
|
||||
func newEventId(appId string, uid int64, orderId interface{}, createTime int64) string {
|
||||
input := fmt.Sprintf("%v%v%v%v", appId, uid, orderId, createTime)
|
||||
h := md5.New()
|
||||
h.Write([]byte(input))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
Reference in New Issue
Block a user