This commit is contained in:
lwl0608 2024-06-13 16:43:31 +08:00
commit d28f58a733
18 changed files with 238 additions and 23 deletions

View File

@ -35,6 +35,7 @@ const (
PayTypeYeepayAlipayH5 = "yeepay_alipay_h5" // 易宝 微信h5支付
PayTypeYeepayWxpayH5 = "yeepay_wxpay_h5" // 易宝 微信h5支付
PayTypeCoin = "coin" // 金币支付
PayTypeApplepay = "applepay" // ios iap 支付
)
const (
@ -42,6 +43,7 @@ const (
CallBackPayTypeWxpay = "wxpay"
CallBackPayTypeYeepay = "yeepay"
CallBackPayTypeCoin = "coin"
CallBackPayTypeApplepay = "applepay"
)
const (

View File

@ -9,9 +9,12 @@ import (
type ApiZoneVO struct {
*dbstruct.Zone
AdmissionPrice int64 `json:"admission_price" bson:"admission_price"` // 空间解锁价格, 单位: 分
AdmissionCoinPrice int64 `json:"admission_coin_price" bson:"admission_coin_price"` // 空间解锁价格, 单位: 金币
IronfanshipPrice int64 `json:"ironfanship_price" bson:"ironfanship_price"` // 铁粉解锁价格, 单位: 分
IronfanshipCoinPrice int64 `json:"ironfanship_coin_price" bson:"ironfanship_coin_price"` // 铁粉解锁价格, 单位: 金币
IsSuperfanshipEnabled int `json:"is_superfanship_enabled" bson:"is_superfanship_enabled"` // 是否开启超粉空间 0: 关闭, 1: 开启
SuperfanshipPrice int64 `json:"superfanship_price" bson:"superfanship_price"` // 超粉价格, 单位: 分
SuperfanshipCoinPrice int64 `json:"superfanship_coin_price" bson:"superfanship_coin_price"` // 超粉价格, 单位: 金币
SuperfanshipValidPeriod int `json:"superfanship_valid_period" bson:"superfanship_valid_period"` // 超粉有效期类型, SuperfanshipValidPeriod*
IsSuperfanshipGiveWechat int `json:"is_superfanship_give_wechat" bson:"is_superfanship_give_wechat"` // 是否开启超粉空间赠送微信 0: 不赠送, 1: 赠送
StreamerExt *streamerproto.ApiListExtVO `json:"streamer_ext"`
@ -55,9 +58,12 @@ func (vo *ApiZoneVO) SetIsSuperfanshipUnlocked(is_superfanship_unlocked int64) {
func (vo *ApiZoneVO) CopyZoneVas(zv *dbstruct.ZoneVas) {
if zv != nil {
vo.AdmissionPrice = zv.AdmissionPrice
vo.AdmissionCoinPrice = zv.GetAdmissionCoinPrice()
vo.IronfanshipPrice = zv.IronfanshipPrice
vo.IronfanshipCoinPrice = zv.GetIronfanshipCoinPrice()
vo.IsSuperfanshipEnabled = zv.IsSuperfanshipEnabled
vo.SuperfanshipPrice = zv.SuperfanshipPrice
vo.SuperfanshipCoinPrice = zv.GetSuperfanshipCoinPrice()
vo.SuperfanshipValidPeriod = zv.SuperfanshipValidPeriod
vo.IsSuperfanshipGiveWechat = zv.IsSuperfanshipGiveWechat
}

View File

@ -15,6 +15,7 @@ type ApiZoneMomentVO struct {
IsZoneMomentUnlocked int64 `json:"is_zone_moment_unlocked"`
Expenditure int64 `json:"expenditure"`
IronfanshipPrice int64 `json:"ironfanship_price"`
IronfanshipCoinPrice int64 `json:"ironfanship_coin_price"`
BuyerCnt int64 `json:"buyer_cnt"` // 动态购买人数
IsSuperfanshipEnabled int64 `json:"is_superfanship_enabled"`
}

View File

@ -5,19 +5,29 @@ import (
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"service/bizcommon/util"
"service/app/mix/service"
"service/library/logger"
"service/library/payclients/applepaycli"
)
func ApplepayCallback(ctx *gin.Context) {
var bodyParam map[string]interface{}
var notify = applepaycli.ApplepayRevenueNotify{}
buf, err := ioutil.ReadAll(ctx.Request.Body)
logger.Info("ApplepayCallback body: %v", string(buf))
err = json.Unmarshal(buf, &bodyParam)
err = json.Unmarshal(buf, &notify)
if err != nil {
logger.Error("arg parse fail: %v", err)
ReplyJsonError(ctx, http.StatusBadRequest, "参数解析失败")
return
}
logger.Info("ApplepayCallback param: %v", util.ToJson(bodyParam))
err = service.DefaultService.ApplepayCallback(ctx, notify)
if err != nil {
logger.Error("ApplepayCallback fail: %v", err)
ReplyJsonError(ctx, -1, err.Error())
return
}
ctx.String(200, "success")
}

View File

@ -6,6 +6,7 @@ import (
"service/app/mix/service"
"service/bizcommon/common"
"service/bizcommon/util"
"service/dbstruct"
"service/library/logger"
"time"
@ -50,6 +51,12 @@ func CreateOrder(ctx *gin.Context) {
ReplyErrCodeMsg(ctx, errcode.ErrCodeBadParam)
return
}
// 检查能用金币支付的商品
if req.PayType == vasproto.PayTypeCoin && !dbstruct.CoinPayValidProductIdMap[req.ProductId] {
ReplyErrorMsg(ctx, "商品错误")
return
}
data, ec, err := service.DefaultService.CreateOrder(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("CreateOrder fail, req: %v, ec: %v", util.ToJson(req), ec)

View File

@ -6,6 +6,7 @@ import (
vasproto "service/api/proto/vas/proto"
"service/app/mix/service"
"service/bizcommon/util"
"service/dbstruct"
"service/library/logger"
)
@ -16,6 +17,12 @@ func ZoneCreateOrder(ctx *gin.Context) {
ReplyErrCodeMsg(ctx, errcode.ErrCodeBadParam)
return
}
// 检查能用金币支付的商品
if req.PayType == vasproto.PayTypeCoin && !dbstruct.CoinPayValidProductIdMap[req.ProductId] {
ReplyErrorMsg(ctx, "商品错误")
return
}
data, ec, err := service.DefaultService.ZoneCreateOrder(ctx, req)
if ec != errcode.ErrCodeVasSrvOk {
logger.Error("ZoneCreateOrder fail, req: %v, ec: %v", util.ToJson(req), ec)

View File

@ -428,7 +428,7 @@ func (v *Vas) CreateOrderByCoin(ctx *gin.Context, req *vasproto.CreateOrderReq,
logger.Error("CheckWalletExist fail, mid: %v, err: %v", req.Mid, err)
return
}
if wallet.GetCoins() <= priceCoin {
if wallet.GetCoins() < priceCoin {
err = errs.ErrVasNoEnoughCoin
logger.Error("not enough coin, mid: %v, coins: %v, err: %v", req.Mid, wallet.Coins, err)
return
@ -454,7 +454,7 @@ func (v *Vas) CreateOrderByCoin(ctx *gin.Context, req *vasproto.CreateOrderReq,
Oid3: goproto.String(req.Oid3),
ProductId: goproto.String(req.ProductId),
PayType: goproto.String(req.PayType),
PayAmount: goproto.Int64(product.RealPrice),
PayAmount: goproto.Int64(priceCoin),
Coins: goproto.Int64(product.ValueCoins),
OrderStatus: goproto.Int32(dbstruct.VasOrderStatusInit),
OrderFrom: goproto.String(req.From),
@ -1547,6 +1547,9 @@ func (v *Vas) PayCallback(ctx *gin.Context, p *vasproto.PayCallbackParamIn) erro
Count: goproto.Int64(order.GetCoins()),
Ct: goproto.Int64(time.Now().Unix()),
}
if order.GetPayType() == vasproto.PayTypeCoin {
ch.TypeId = goproto.String(ch.GetTypeId() + ":_:pay_type=coin")
}
switch product.Id {
case dbstruct.ProductIdMembership:
ch.SType = goproto.Int32(dbstruct.CHSTypeChargeMembership)
@ -2266,6 +2269,9 @@ func (v *Vas) UnlockMembership(ctx *gin.Context, tx *sqlx.Tx, mid int64, product
// 计算收入
var totalDias = int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
if order.GetPayType() == vasproto.PayTypeCoin {
totalDias = order.GetPayAmount()
}
incomeList, err := v.calcAndUpdateIncome(ctx, tx, order.GetUid(), mid, order.GetDid(), orderId, order.GetProductId(), totalDias, dbstruct.CHSTypeIncomeMembership)
if err != nil {
logger.Error("calcAndUpdateIncome fail, order: %v, err: %v", util.ToJson(order), err)
@ -2598,7 +2604,7 @@ func (v *Vas) refundMembership(ctx *gin.Context, order *dbstruct.Order, req *vas
}
// 退款
err = v.payRefund(ctx, order)
err = v.payRefund(ctx, tx, order)
if err != nil {
return err
}
@ -2696,7 +2702,7 @@ func (v *Vas) refundCoins(ctx *gin.Context, order *dbstruct.Order, req *vasproto
}
// 退款
err = v.payRefund(ctx, order)
err = v.payRefund(ctx, tx, order)
if err != nil {
return err
}
@ -2794,7 +2800,7 @@ func (v *Vas) refundMoneyContactWechat(ctx *gin.Context, order *dbstruct.Order,
}
// 退款
err = v.payRefund(ctx, order)
err = v.payRefund(ctx, tx, order)
if err != nil {
return err
}
@ -3020,7 +3026,7 @@ func (v *Vas) refundZoneAdmission(ctx *gin.Context, order *dbstruct.Order, req *
}
// 退款
err = v.payRefund(ctx, order)
err = v.payRefund(ctx, tx, order)
if err != nil {
return err
}
@ -3247,7 +3253,7 @@ func (v *Vas) refundZoneMoment(ctx *gin.Context, order *dbstruct.Order, req *vas
}
// 退款
err = v.payRefund(ctx, order)
err = v.payRefund(ctx, tx, order)
if err != nil {
return err
}
@ -3483,7 +3489,7 @@ func (v *Vas) refundZoneSuperfanship(ctx *gin.Context, order *dbstruct.Order, re
}
// 退款
err = v.payRefund(ctx, order)
err = v.payRefund(ctx, tx, order)
if err != nil {
return err
}
@ -3706,7 +3712,7 @@ func (v *Vas) refundCoinContactWechat(ctx *gin.Context, order *dbstruct.CoinOrde
return nil
}
func (v *Vas) payRefund(ctx *gin.Context, order *dbstruct.Order) error {
func (v *Vas) payRefund(ctx *gin.Context, tx *sqlx.Tx, order *dbstruct.Order) error {
var (
orderId = order.GetID()
)
@ -3784,6 +3790,12 @@ func (v *Vas) payRefund(ctx *gin.Context, order *dbstruct.Order) error {
return err
}
logger.Info("wxpayCli.RefundOne success, orderId: %v", orderId)
case vasproto.PayTypeCoin:
err := v.store.IncCoins(ctx, tx, order.GetMid(), order.GetPayAmount())
if err != nil {
logger.Error("coin.RefundOne fail, orderId: %v, err: %v", orderId, err)
return err
}
}
return nil
}

View File

@ -294,7 +294,10 @@ func (v *Vas) UnlockZoneMoment(ctx *gin.Context, tx *sqlx.Tx, order *dbstruct.Or
}
// 计算收入
totalDias := int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
var totalDias = int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
if order.GetPayType() == vasproto.PayTypeCoin {
totalDias = order.GetPayAmount()
}
incomeList, err := v.calcAndUpdateIncome(ctx, tx, order.GetUid(), mid, order.GetDid(), orderId, order.GetProductId(), totalDias, dbstruct.CHSTypeIncomeZoneStreamer)
if err != nil {
logger.Error("calcAndUpdateIncome fail, order: %v, err: %v", util.ToJson(order), err)
@ -374,7 +377,10 @@ func (v *Vas) UnlockZoneAdmission(ctx *gin.Context, tx *sqlx.Tx, order *dbstruct
}
// 计算收入
totalDias := int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
var totalDias = int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
if order.GetPayType() == vasproto.PayTypeCoin {
totalDias = order.GetPayAmount()
}
incomeList, err := v.calcAndUpdateIncome(ctx, tx, order.GetUid(), mid, order.GetDid(), orderId, order.GetProductId(), totalDias, dbstruct.CHSTypeIncomeZoneStreamer)
if err != nil {
logger.Error("calcAndUpdateIncome fail, order: %v, err: %v", util.ToJson(order), err)
@ -424,7 +430,10 @@ func (v *Vas) UnlockZoneSuperfanship(ctx *gin.Context, tx *sqlx.Tx, order *dbstr
}
// 计算收入
totalDias := int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
var totalDias = int64(float64(order.GetPayAmount()) / 100.0 * 10.0)
if order.GetPayType() == vasproto.PayTypeCoin {
totalDias = order.GetPayAmount()
}
incomeList, err := v.calcAndUpdateIncome(ctx, tx, order.GetUid(), mid, order.GetDid(), orderId, order.GetProductId(), totalDias, dbstruct.CHSTypeIncomeZoneStreamer)
if err != nil {
logger.Error("calcAndUpdateIncome fail, order: %v, err: %v", util.ToJson(order), err)

View File

@ -1040,6 +1040,7 @@ func (s *Service) GetMembershipProductList(ctx *gin.Context, req *vasproto.GetMe
ec = errcode.ErrCodeVasSrvFail
return
}
product.RealCoinPrice = product.GetRealPriceCoin()
data = &vasproto.GetMembershipProductListData{
Product: product,
}

View File

@ -1843,6 +1843,7 @@ func (s *Service) utilFillZoneMomentsWithApiVOInfo(ctx *gin.Context, list []*dbs
// 7.填充所有信息
for _, vo := range volist {
vo.ZoneMoment.CoinPrice = vo.ZoneMoment.GetCoinPrice()
zid := vo.ZoneMoment.GetZid()
@ -1886,6 +1887,7 @@ func (s *Service) utilFillZoneMomentsWithApiVOInfo(ctx *gin.Context, list []*dbs
// 铁粉价格
if zv, ok := zvMap[zid]; ok {
vo.IronfanshipPrice = zv.IronfanshipPrice
vo.IronfanshipCoinPrice = zv.GetIronfanshipCoinPrice()
vo.IsSuperfanshipEnabled = int64(zv.IsSuperfanshipEnabled)
}
}

View File

@ -4,16 +4,21 @@ import (
"encoding/base64"
"fmt"
goproto "google.golang.org/protobuf/proto"
"service/api/base"
"service/api/errcode"
"service/api/errs"
accountproto "service/api/proto/account/proto"
vasproto "service/api/proto/vas/proto"
vericodeproto "service/api/proto/vericode/proto"
zoneproto "service/api/proto/zone/proto"
"service/bizcommon/common"
"service/bizcommon/util"
"service/dbstruct"
"service/library/logger"
"service/library/mycrypto"
"service/library/payclients/applepaycli"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
@ -313,6 +318,14 @@ func (s *Service) chListCharge(ctx *gin.Context, chList []*dbstruct.ConsumeHisto
item.Desc = "解锁空间超粉退款"
item.Change = fmt.Sprintf("+%.1f元", float64(util.AbsInt64(chDB.GetChange()))/100.0)
}
if strings.Contains(chDB.GetTypeId(), ":_:pay_type=coin") && strings.Contains(item.Change, "元") {
oldChange := item.Change
newChange := ""
if len(item.Change) > 0 {
newChange += oldChange[:1]
}
newChange += fmt.Sprintf("%d金币", chDB.GetChange())
}
list = append(list, item)
}
@ -1079,3 +1092,52 @@ func (s *Service) OpManualUnlockWechat(ctx *gin.Context, req *zoneproto.OpManual
}
return
}
func (s *Service) ApplepayCallback(ctx *gin.Context, notify applepaycli.ApplepayRevenueNotify) error {
var (
event = notify.Event
mid, _ = strconv.ParseInt(event.AppUserID, 10, 64)
)
switch event.Type {
case applepaycli.NotifyTypeNonRenewingPurchase:
// 验证用户
acnt, _ := _DefaultAccount.GetAccountMapByMids(ctx, []int64{mid})
if _, ok := acnt[mid]; !ok {
err := fmt.Errorf("invalid user")
logger.Error("ApplepayCallback fail, invalid user, mid: %v, notify: %v", mid, notify)
return err
}
// 先创建订单
createOrderParam := &vasproto.CreateOrderReq{
BaseRequest: base.BaseRequest{
Mid: mid,
DevType: common.DeviceTypeIos,
},
ProductId: event.ProductID,
PayType: vasproto.PayTypeCoin,
Oid3: event.AppID,
}
data, err := _DefaultVas.CreateOrder(ctx, createOrderParam)
if err != nil {
logger.Error("CreateOrder fail, param: %v, err: %v", createOrderParam, err)
return err
}
orderId := data.OrderId
// 手动回调
payCallbackParam := &vasproto.PayCallbackParamIn{
OrderId: orderId,
OutOrderId: event.TransactionID,
CallbackPayType: vasproto.CallBackPayTypeApplepay,
}
err = _DefaultVas.PayCallback(ctx, payCallbackParam)
if err != nil {
logger.Error("PayCallback fail, param: %v, err: %v", payCallbackParam, err)
return err
}
}
return nil
}

View File

@ -201,3 +201,7 @@ func VerisonCompare(ver1 string, ver2 string) (bool, error) {
}
return false, nil
}
func RoundUp(num float64) int64 {
return int64(math.Ceil(num))
}

7
bizcommon/util/x_test.go Normal file
View File

@ -0,0 +1,7 @@
package util
import "testing"
func TestRound(t *testing.T) {
Round()
}

View File

@ -1,5 +1,7 @@
package dbstruct
import "service/bizcommon/util"
// 商品id
const (
ProductIdOpCoin = "op_coin" // op充值的金币
@ -50,6 +52,13 @@ var ProductIdDescMap = map[string]string{
ProductIdH5ZoneSuperfanship: "解锁超粉",
}
var CoinPayValidProductIdMap = map[string]bool{
ProductIdMembership: true,
ProductIdH5ZoneMoment: true,
ProductIdH5ZoneAdmission: true,
ProductIdH5ZoneSuperfanship: true,
}
// 商品类型
const (
ProductTypeCoins = "coins" // 商品类型:金币
@ -98,5 +107,5 @@ type Product struct {
}
func (p Product) GetRealPriceCoin() int64 {
return p.RealPrice / 10
return util.RoundUp(float64(p.RealPrice) / 10.0)
}

View File

@ -1,6 +1,9 @@
package dbstruct
import "math"
import (
"math"
"service/bizcommon/util"
)
// 用户增值信息
const (
@ -122,6 +125,18 @@ func (p ZoneVas) GetSuperfanshipDurationDesc() string {
}
}
func (p ZoneVas) GetAdmissionCoinPrice() int64 {
return util.RoundUp(float64(p.AdmissionPrice) / 10.0)
}
func (p ZoneVas) GetIronfanshipCoinPrice() int64 {
return util.RoundUp(float64(p.IronfanshipPrice) / 10.0)
}
func (p ZoneVas) GetSuperfanshipCoinPrice() int64 {
return util.RoundUp(float64(p.SuperfanshipPrice) / 10.0)
}
// 空间动态价格
type ZoneMomentPrice struct {
MomentId int64 `json:"id" bson:"_id"` // 动态id

View File

@ -1,5 +1,7 @@
package dbstruct
import "service/bizcommon/util"
type ZoneMoment struct {
Id *int64 `json:"id" bson:"_id"` // 私密圈动态表id
Mid *int64 `json:"mid" bson:"mid"` // 用户表id
@ -31,7 +33,7 @@ type ZoneMoment struct {
Ct *int64 `json:"ct" bson:"ct"` // 创建时间
Ut *int64 `json:"ut" bson:"ut"` // 更新时间
DelFlag *int64 `json:"del_flag" bson:"del_flag"` // 删除标记
CoinPrice int64 `json:"coin_price"` // 单帖价格,单位人民币分
}
func (p *ZoneMoment) GetId() int64 {
@ -136,3 +138,7 @@ func (p *ZoneMomentStatInfo) GetCount() int64 {
}
return 0
}
func (p *ZoneMoment) GetCoinPrice() int64 {
return util.RoundUp(float64(p.CoinPrice) / 10.0)
}

View File

@ -0,0 +1,46 @@
package applepaycli
const (
NotifyTypeNonRenewingPurchase = "NON_RENEWING_PURCHASE" // 非续费型消费
)
type Event struct {
EventTimestampMs int64 `json:"event_timestamp_ms"`
ProductID string `json:"product_id"`
PeriodType string `json:"period_type"`
PurchasedAtMs int64 `json:"purchased_at_ms"`
ExpirationAtMs int64 `json:"expiration_at_ms"`
Environment string `json:"environment"`
EntitlementID string `json:"entitlement_id"`
EntitlementIDs []string `json:"entitlement_ids"`
PresentedOfferingID string `json:"presented_offering_id"`
TransactionID string `json:"transaction_id"`
OriginalTransactionID string `json:"original_transaction_id"`
IsFamilyShare bool `json:"is_family_share"`
CountryCode string `json:"country_code"`
AppUserID string `json:"app_user_id"`
Aliases []string `json:"aliases"`
OriginalAppUserID string `json:"original_app_user_id"`
Currency string `json:"currency"`
Price float64 `json:"price"`
PriceInPurchasedCurrency float64 `json:"price_in_purchased_currency"`
SubscriberAttributes struct {
AttConsentStatus struct {
Value string `json:"value"`
UpdatedAtMs int64 `json:"updated_at_ms"`
} `json:"$attConsentStatus"`
} `json:"subscriber_attributes"`
Store string `json:"store"`
TakehomePercentage float64 `json:"takehome_percentage"`
OfferCode string `json:"offer_code"`
TaxPercentage float64 `json:"tax_percentage"`
CommissionPercentage float64 `json:"commission_percentage"`
Type string `json:"type"`
ID string `json:"id"`
AppID string `json:"app_id"`
}
type ApplepayRevenueNotify struct {
Event Event `json:"event"`
APIVersion string `json:"api_version"`
}

View File

@ -26,6 +26,15 @@ type C struct {
}
func main() {
numbers := []float64{12.9, 99.9}
for _, number := range numbers {
rounded := util.RoundUp(number)
fmt.Printf("%.1f -> %d\n", number, rounded)
}
return
c := C{
A: &A{Mid: 111},
B: &B{Mid: 222},