local Currency_Model = {} local private = {} local EventSystem = require('Event/EventSystem') local get_order_id_pb = require("Proto/get_order_id_pb") local JSON = require("cjson") local SDKPAY_WAITING_KEY = "SDKPAY_WAITING_KEY" local CHINA_FORMAT = "%d元" local IAP_MILLION_NUM = 1000000 local skuDetailDict = {} local sym_money --货币符号 local num_separate --分隔位数 local byte_decimal --小数点符号byte local byte_separate --分隔符符号byte local unitIAP = 4010 -- 1元的iap用作汇率转换 local friendGiftIAPIndex = 8040 -- 金兰之邀的独行之路档位在表里面的 iapindex private.totalRechargeTokens = 0 --累充代币 private.totalRechargeMoney = 0 --累充人民币 private.firstIAPIndexArray = nil --首充已获取的IAP索引号 --新添加支付类型的时候需要确认并修改二级确认弹窗(日本使用) Currency_Model.PayType = { None = get_order_id_pb.None, -- 无效充值 Common = get_order_id_pb.Common, -- 通用充值 MonthCard = get_order_id_pb.MonthCard, -- 月卡 Fund = get_order_id_pb.Fund, -- 基金 DailyGift = get_order_id_pb.DailyGift, -- 每日礼包 PushGift = get_order_id_pb.PushGift, -- 定向礼包 DirectGift = get_order_id_pb.DirectGift, -- 直购礼包 HonorOrder = get_order_id_pb.HonorOrder, -- 战令 SubHonorOrder = get_order_id_pb.SubHonorOrder, -- 彩金—战令 HeroFund = get_order_id_pb.HeroFund, -- 成长基金 FriendGift = get_order_id_pb.TakeUp1, -- 金兰之邀 HeroFund = get_order_id_pb.HeroFund, -- 名X之路 RecallGift = get_order_id_pb.RecallGift, --召回礼包 SubjectMonthHonorOrder = get_order_id_pb.SubjectMonthHonorOrder, --主题月战令 } --除了龙玉都是软货币,目前只用到了龙玉 Currency_Model.CurrencyType = { Energy = 2, SilverCoin = 3, GoldIngot = 4, DragonJade = 5, ZhanJiangLing = 6, GuildGold = 7, --势力货币 GuildWuZi = 10, --势力物资 GuildCapital = 10, --势力资金 YanWuJade = 11, --演武币 PrimaryBingPu = 12, --初级兵谱 IntermediateBingPu = 13, --中级兵谱 SeniorBingPu = 14, --高级兵谱 QiXinLing = 16, --齐心令 ZhanJiLing = 17, --战技令 RechargeCredit = 30, --充值积分 EternalRewardCoin = 2004, -- 常驻抽卡商店消费代币 } function Currency_Model.OnStartup() private.softToken = {} private.hardToken = {} private.isInit = 0 private.tempChangedCurreny = {} NetManager.RegisterMsgCB(MessageEnums.MessageGetOrderID, private.GetOrderIDResp) NetManager.RegisterMsgCB(MessageEnums.MessageG2CPushPCOrderInfo, private.OnReceivePayItemSuccess) end function private.OnReceivePayItemSuccess(msgNotify) logging.crit(LTAG.CRIT, msgNotify) local payInfo = {af_currency=msgNotify.currencyType, af_revenue=tonumber(msgNotify.currencyType.currencyAmount)} BIManager.DispatchBIClientEvent(BIManager.EventName.af_purchase, "event_parameters", JSON.encode(payInfo), "orderId", msgNotify.OrderID) end function Currency_Model.GetFirstIAPIndexArray() return private.firstIAPIndexArray end function Currency_Model.SyncRechargeAmount(rechargeInfo) private.firstIAPIndexArray = rechargeInfo.IAPIndex private.totalRechargeMoney = rechargeInfo.accMoney private.totalRechargeTokens = rechargeInfo.accGameMoney EventSystem.Dispatch(EventID.UI_Shop_Recharge) end function Currency_Model.GetTotalRechargeTokens() return private.totalRechargeTokens end function Currency_Model.GetTotalRechargeMoney() return private.totalRechargeMoney end function Currency_Model.GetTotalRechargeCredit() return private.hardToken[Currency_Model.CurrencyType.RechargeCredit] or 0 end function Currency_Model.UpdateSoftTokenList(msgId, msgCurrency, isInit) private.UpdateCurrencys(private.softToken, msgId, msgCurrency, isInit, "CfgId") end function private.UpdateCurrencys(currencyTable, msgId, msgCurrency, isInit, idName) if not IsProtoDataNotNil(msgCurrency) then return end local hasChangeCurrency = false for i, v in ipairs(msgCurrency) do --Debit可以支持显示负数 local currencyCount = v.Count - (v.Debit or 0) if not currencyTable[v[idName]] or currencyTable[v[idName]]~=currencyCount then currencyTable[v[idName]] = currencyCount table.insert(private.tempChangedCurreny, v) hasChangeCurrency = true end end --TODO 临时优化方案,未来需要将货币变化事件拆分成更细粒度,各个模块只监听自己需要的货币事件,避免无必要的响应 if not isInit and hasChangeCurrency then if msgId ~= MessageEnums.MessageTreasureSoulSlotRefresh then EventSystem.Dispatch(EventID.UI_RefreshCurrency) end CTL.Currency.CheckAndDispatchCurrencyEvent(msgId, private.tempChangedCurreny) table.clearArray(private.tempChangedCurreny) end Currency_Model.SendCurrencyInfoToBI() end function Currency_Model.UpdateHardTokenList(msgId, msgCurrency, isInit) private.UpdateCurrencys(private.hardToken, msgId, msgCurrency, isInit, "CfgID") end function private.GetCurrencyJson(tabletest) local result="" if private.GetTableCount(tabletest)==0 then result="" else local temp={} --cjson 如果里面的key是int 类型,会有问题,转换一下 for k,v in pairs(tabletest) do temp[tostring(k)]=v end result=JSON.encode(temp) end return result end function Currency_Model.SendCurrencyInfoToBI() local hardCurrencyRemain = private.GetCurrencyJson(private.hardToken) local softCurrencyRemain = private.GetCurrencyJson(private.softToken) local hardCurrencyCount=Currency_Model.GetDragonJade() --龙玉数量 local softCurrencyCount= Currency_Model.GetSoftTokenCountById(Currency_Model.CurrencyType.GoldIngot) --元宝数量 SDKUtil.SetCurrency(hardCurrencyRemain,softCurrencyRemain,hardCurrencyCount,softCurrencyCount) end function private.GetTableCount(tabletest) local count=0 if tabletest==nil then return count end for k,v in pairs(tabletest) do count = count + 1 end return count end function Currency_Model.GetSoftTokenCountById(id) return private.softToken[id] or 0 end function private.IsSoftTokenEnough(id, targetCount) local curCount = private.softToken[id] if curCount == nil then return false end return curCount >= targetCount end --该方法用于通用货币判断,内部根据ID处理,后续有新逻辑再优化里面内容 function Currency_Model.IsMoneyEnough(moneyID, count) if count > 0 then if Currency_Model.CurrencyType.DragonJade == moneyID then if count > Currency_Model.GetDragonJade() then return false end elseif not private.IsSoftTokenEnough(moneyID, count) then if UTL.Item.GetItemStockCount(moneyID) >= count then return true end return false end end return true end --龙玉 function Currency_Model.GetDragonJade() return private.hardToken[Currency_Model.CurrencyType.DragonJade] or 0 end function Currency_Model.GetMoneyCount(moneyID) if Currency_Model.CurrencyType.DragonJade == moneyID then return private.hardToken[Currency_Model.CurrencyType.DragonJade] or 0 elseif Currency_Model.CurrencyType.RechargeCredit == moneyID then return private.hardToken[Currency_Model.CurrencyType.RechargeCredit] or 0 elseif Currency_Model.CurrencyType.GuildWuZi == moneyID or Currency_Model.CurrencyType.GuildCapital == moneyID then return MDL.Guild.GetGuildWuziCount() else return Currency_Model.GetSoftTokenCountById(moneyID) end return 0 end function Currency_Model.IsMoneyTypeByItemID(itemId) if not itemId then return false end local itemData = DataSheetManager.ItemData:GetDataByID(itemId) if itemData.FirstType == LuaConst.ItemType.Materials then if itemData.SecondType == 5 or itemData.SecondType == 7 then return true end end if itemData.FirstType == LuaConst.ItemType.Guild then return true end return false end function Currency_Model.IsGoldHeroCurrencyTypeByItemID(itemId) if not itemId then return false end local itemData = DataSheetManager.ItemData:GetDataByID(itemId) if itemData.FirstType == LuaConst.ItemType.TeamExp then if itemData.SecondType == 3 then return true end end return false end function Currency_Model.IsSuperGoldCurrencyTypeByItemID(itemId) if not itemId then return false end for i, v in DataSheetManager.SuperGoldBossData:GetEnumerator() do if itemId == v.EnergyItem then return true end end return false end function Currency_Model.IsNotOpenRechargeFunc() return DEF.NOT_OPEN_RECHARGE ~= nil end function Currency_Model.IsNotShowRechargeButton() return DEF.NOT_SHOW_RECHARGE_BUTTON ~= nil end --新添加支付的时候需要确认并修改二级确认弹窗(日本使用) function Currency_Model.GetOrderIDReq(iapIndex, payType, buyItemId, activityId, activityType) if GameAppInfo.IsJapanesePackage() and payType ~= Currency_Model.PayType.Common then --日语版本除充值外需要二级确认弹窗 local title --传入IDS的Id或者字符串(为了区分表中取到的IDS和运营活动AMS数据中的json解析的字符串) local time --传入时间戳显示时间,传入string视为无限制时间,不传则不显示时间 local limit --传入限购次数,不传则不显示限购 if payType == Currency_Model.PayType.MonthCard then --月卡 title = Const_IDS["IDS_JP_Purchase_MonthCard" .. buyItemId] time = "IDS" elseif payType == Currency_Model.PayType.HonorOrder then --战令 time = MDL.OperationActivity.GetOperationActivityInfo(MDL.OperationActivity.ActivityType.HonorOrder, MDL.HonorOrder.HonorOrderActivityId).endTime title = Const_IDS["IDS_HonorOrder_DianCangZhanLing"] elseif payType == Currency_Model.PayType.SubHonorOrder then --彩金战令 time = MDL.OperationActivity.GetOperationActivityInfo(activityType, activityId).endTime title = Const_IDS["IDS_SuperGoldHeroActivity_HonorOrder_ChildTitle2"] elseif payType == Currency_Model.PayType.Fund then --基金 title = Const_IDS[DataSheetManager.PreferentialOrder:GetDataByID(buyItemId).ActivityName] limit = 1 elseif payType == Currency_Model.PayType.DailyGift then --每日礼包 if buyItemId == 0 then title = Const_IDS["IDS_JP_Purchase_DailyGift"] else title = Const_IDS[DataSheetManager.DailyGift:GetDataByID(buyItemId).Name] end elseif payType == Currency_Model.PayType.DirectGift then --直购礼包 local giftInfo = MDL.DirectGift.GetGiftInfo(activityId, buyItemId) if giftInfo.data.TimeType ~= MDL.OperationActivity.TimeType.Forever then local startTime, endTime = MDL.DirectGift.GetGiftTimeStamp(giftInfo.info) if activityType == MDL.OperationActivity.ActivityType.BattlefieldMuster then --沙场点兵(号令天下)会提前10分钟结束出售 time = CTL.CommandTheWorld.GetShowGiftEndTime() else time = endTime end end title = MDL.OperationActivity.CommonGiftReward:GetDataByID(giftInfo.data.GiftID).Name if giftInfo.data.LimitType ~= LuaConst.LimitBuyType.NoLimit then limit = giftInfo.data.TimesLimit - MDL.DirectGift.GetGiftBuyingCount(activityId, buyItemId) end elseif payType == Currency_Model.PayType.PushGift then --定向礼包 time = MDL.PushGift.GetPushGiftInfo(activityId, buyItemId).endTime title = MDL.OperationActivity.PushGiftData:GetDataByActivityID_ID(activityId, buyItemId).PushTabName limit = 1 elseif payType == Currency_Model.PayType.FriendGift then -- 日本地区需要特殊处理金兰之邀三个礼包对应的二次确认 Title time = MDL.OperationActivity.GetOperationActivityInfo(activityType, activityId).endTime if iapIndex == friendGiftIAPIndex then title = Const_IDS["IDS_FriendRecharge_Title2"] else title = Const_IDS["IDS_FriendRecharge_Title1"] end --日本的金兰因为之前是复用的其他礼包 服务器这里说不能改 所以要强行赋值回 SubHonorOrder payType = MDL.Currency.PayType.SubHonorOrder end local context = { iapIndex = iapIndex, title = title, time = time, limit = limit, callBack = function() private.SendOrderIDReq(iapIndex, payType, buyItemId, activityId, activityType) end, } UIManager.OpenView(Const_UI_Lua.UIEnum.UIPurchaseConfirmView, context) else private.SendOrderIDReq(iapIndex, payType, buyItemId, activityId, activityType) end end function private.SendOrderIDReq(iapIndex, payType, buyItemId, activityId, activityType) if Currency_Model.IsNotOpenRechargeFunc() then CTL.Tips.AddTipsMsg(Const_IDS.IDS_ERRORCODE_9999_InfoCode_TestNoPay_Tips) return end if GameAppInfo.IsPCPackage() then if not CTL.WinLogin.IsUsePCNtSdk() then CTL.Tips.AddTipsMsg(Const_IDS.IDS_PCPackage_Pay) return end end local msg = get_order_id_pb.GetOrderIDReq() msg.IapIndex = iapIndex msg.PlatformType = tonumber(SDKUtil.PlatformType) msg.ExtraParam = string.format("%s:%s:%s:%s:%s:", UTL.Time.GetServerTimestamp(), GameAppInfo.Global_ProductId, AppInfo.marketVersion, iapIndex, SDKUtil.PlatformType) --这两个activity相关的参数是运营活动才使用的 activityId = activityId or 0 activityType = activityType or 0 --mac上buyItemId为nil 会报错 buyItemId = buyItemId or "nil" local BuyParamStr BuyParamStr = string.format("|%s|%s|%s|%s", payType, buyItemId, activityId, activityType) msg.ExtraParam = msg.ExtraParam .. BuyParamStr logging.log(LTAG.LOG, "GetOrderIDReq 支付协议 拼接字符串:", msg.ExtraParam) NetManager.SendMessage(MessageEnums.MessageGetOrderID, msg) end function private.GetOrderIDResp(msgResp) logging.log(LTAG.LOG, "MessageGetOrderID", msgResp) if msgResp.Resp.Code == 0 then local productName = MDL.Shop.GetIapNameByIndex(msgResp.IapIndex) local payAmount = MDL.Shop.GetIapPriceByIndex(msgResp.IapIndex) local iapID = MDL.Shop.GetIapIDByIndex(msgResp.IapIndex) --测试发现小米系统会杀掉内存占用超过1.5G内存的后台程序,而小米渠道包的支付界面比较特殊,当支付界面打开时游戏程序会被切为后台,导致容易出现闪退 --所以增加一个特殊处理,拉起支付前尝试释放内存,减少被杀的概率 if UNITY_ANDROID and GameAppInfo.IsChinesePackage() and string.find(SystemInfo.deviceModel, "Xiaomi", 1, true) then UTL.MessageBox.ShowWaitingWithDelay(SDKPAY_WAITING_KEY) AuxUtil.OnLowMemory(true) TimerUtil.Interval(function() UTL.MessageBox.CloseWaitingWithDelay(SDKPAY_WAITING_KEY) private.StartSDKPayByDevice(msgResp.OrderID, iapID, productName, payAmount, msgResp.ExtraParam, msgResp.IapIndex, msgResp.GoodsID) end, 1, 1) else private.StartSDKPayByDevice(msgResp.OrderID,iapID,productName,payAmount,msgResp.ExtraParam, msgResp.IapIndex, msgResp.GoodsID) end end end function private.StartSDKPayByDevice(orderID, iapID, productName, payAmount, extraParam, iapIndex, realGoodsId) if GameAppInfo.IsPCPackage() then SDKUtil.HeroPay(orderID, realGoodsId, productName, payAmount, extraParam, 1) else SDKUtil.HeroPay(orderID, iapID, productName, payAmount, extraParam) end end function Currency_Model.CheatSetSkuDetail(jsonStr) if string.isNilOrEmpty(jsonStr) then skuDetailDict = {} sym_money = nil byte_decimal = nil num_separate = nil byte_separate = nil else Currency_Model.SetSkuDetail(jsonStr) end end function Currency_Model.SetSkuDetail(jsonStr) local content = JSON.decode(jsonStr) local currencyCode = nil for k, v in pairs(content) do local detail = nil if not GameAppInfo.IsPCPackage() and not GameAppInfo.IsMacPackage() then detail = JSON.decode(v) else detail = v end skuDetailDict[k] = detail logging.log(LTAG.LOG, " SetSkuDetail ", k, skuDetailDict[k]) --计算货币参数:分隔位数,分隔符号,小数点符号 if (byte_decimal == nil or byte_separate == nil) and detail.price ~= JSON.null then local sym, money = string.match(detail.price, "([^%d]*)([%d]*[%A]*)") sym_money = sym--货币符号 local index = 0 --分隔位 for i = #money, 1, -1 do local byte = string.byte(money, i) if UTL.String.IsByteNumber(byte) then --如果是数字,索引值加1 index = index + 1 else --如果是符号,判断是小数点还是分隔符 if index < 3 then --小于3只能是小数点,此时需要重置分隔位索引值 byte_decimal = byte index = 0 else --大于等于3,说明是分隔符 num_separate = index byte_separate = byte break end end end end currencyCode = detail.currencyCode end logging.crit(LTAG.CRIT, "CurrencyCode", sym_money, currencyCode) logging.log(LTAG.LOG, "Sku Param", byte_decimal, byte_separate, num_separate) end function Currency_Model.GetFullyIAPPriceByIapID(iapID) local iap for _, v in DataSheetManager.IAP:GetEnumerator() do if v.IapID == iapID then iap = v.Index break end end if iap then return Currency_Model.GetFullyIAPPrice(iap) end return nil end --统一调用获取实际货币显示,传iap表的主键 --返回带货币符号的价格 function Currency_Model.GetFullyIAPPrice(iap) local iapData = DataSheetManager.IAP:GetDataByIndex(iap) if GameAppInfo.IsChinesePackage() == true then --国内包,无其他语言,直接返回"元" return string.format(CHINA_FORMAT, iapData.Price) end local skuDetail = skuDetailDict[iapData.IapID] if skuDetail == nil then --日本保底货币符号 if GameAppInfo.IsJapanesePackage() then return "¥" .. private.GoogleFormatForJP(iapData.Price) elseif GameAppInfo.IsVietnamPackage() then return private.GoogleFormatForVN(iapData.Price) .. "₫" else --保底,直接返回美元符号 return "$" .. iapData.Price end end return private.GoogleFormat(skuDetail.price) end --获取iap数值 function Currency_Model.GetIAPPriceNum(iap) local iapData = DataSheetManager.IAP:GetDataByIndex(iap) local skuDetail = skuDetailDict[iapData.IapID] if GameAppInfo.IsOverseaPackage() == false or skuDetail == nil then return iapData.Price end local num = skuDetail.priceAmountMicros / IAP_MILLION_NUM if byte_decimal then --四舍五入保留两位小数 return math.floor(num * 100 + 0.5) / 100 else --无小数,直接四舍五入取整返回 return math.floor(num + 0.5) end end -- 将活动配置的货币转换为对应iap数值 function Currency_Model.GetPriceExchangeRate(num, isCents) -- 如果 isCents 为美分,则将美分转换为美元 if isCents then num = num * 0.01 end local iapData = DataSheetManager.IAP:GetDataByIndex(unitIAP) local skuDetail = skuDetailDict[iapData.IapID] if skuDetail then local skuNum = skuDetail.priceAmountMicros / IAP_MILLION_NUM num = num * skuNum / iapData.Price end -- 此处与策划沟通,需要将货币向上取整的方式 保留两位小数 return math.ceil(num * 100) / 100 end --将数值拼上货币符号,分隔符和小数点 function Currency_Model.FormatPriceNum(num) if GameAppInfo.IsChinesePackage() == true then --国内包,无其他语言,直接返回"元" return string.format(CHINA_FORMAT, num) end --保底,直接返回美元符号 if GameAppInfo.IsJapanesePackage() and sym_money == nil then return "¥" .. num elseif GameAppInfo.IsVietnamPackage() and sym_money == nil then return num .. "₫" elseif sym_money == nil then --保底,直接返回美元符号 return "$" .. num end --无分隔符的情况,直接返回 if byte_separate == nil then --这里的num是已经保留两位小数的值了,不用再考虑末尾.00的情况 return sym_money .. num end local result = {} local intNum, decimal = math.modf(num) intNum = tostring(intNum) for i = 1, #intNum do result[i] = string.byte(intNum, i) end --插入分隔符 --计算分隔符的索引位置 local len = #intNum local insertNum = math.modf(len / num_separate) for i = 1, insertNum do --插入位置是在后面第num_separate个位置 local pos = (len - (num_separate - 1)) if pos > 1 then table.insert(result, pos, byte_separate) len = len - num_separate end end --小数部分,byte_decimal为空不显示小数部分 if decimal > 0 and byte_decimal then --modf得到的小数部分,先保留两位小数 decimal = string.format("%.2f", decimal) --先将小数符号插入 table.insert(result, byte_decimal) --舍弃小数前边的"0.",从第3位开始 for i = 3, #decimal do table.insert(result, string.byte(decimal, i)) end end local content = string.char(table.unpack(result)) if GameAppInfo.IsVietnamPackage() then return content .. sym_money else return sym_money .. content end end --谷歌要求不显示【小数点后00】 function private.GoogleFormat(content) --越南地区的货币本地化要通过客户端的规则处理一下 if GameAppInfo.IsVietnamPackage() then return private.GoogleFormatForVN(content) end if byte_decimal == nil then --小数点符号为空,说明iap不带小数点,直接返回 return content end local sym_decimal = string.char(byte_decimal) local removeStr = "%" .. sym_decimal .. "00" local result = string.gsub(content, removeStr, "") return result end -- 日本保底读表获取价格不显示【小数点后00】 function private.GoogleFormatForJP(content) return string.split(content,".")[1] end -- 越南处理本地化价格,每3位数字用"."来分割 function private.GoogleFormatForVN(content) local price = string.split(content,".")[1] return private.PriceSplitWithPoint(price) end function private.PriceSplitWithPoint(price) if not string.isNilOrEmpty(price) then local newPrice = "" local priceTable = {} local pointPos = 4 local counter = 1 for i = 1, #price do priceTable[i] = string.sub(string.reverse(price),i,i) end for i,v in pairs(priceTable) do if counter == pointPos then newPrice = v .. "." .. newPrice counter = 2 else newPrice = v .. newPrice counter = counter + 1 end end return newPrice end end -- IAP 物品对应获得充值积分数 function Currency_Model.GetIAPRewardAmountBy(iapId) local iapIndex = private.GetIAPIndexByIAPId(iapId) or -1 if iapIndex < 0 then logging.error(LTAG.ERROR, "IAP数据不存在,IAPID:", iapId) return 0 end return Currency_Model.GetAmountByIAPIndex(iapIndex) end function private.GetIAPIndexByIAPId(iapId) for _, data in DataSheetManager.IAP:GetEnumerator() do if iapId == data.IapID then return data.Index end end return nil end function Currency_Model.GetAmountByIAPIndex(iapIndex) local iapConfig = DataSheetManager.IAP:GetDataByIndex(iapIndex) if iapConfig.Item_Condition_Table and #iapConfig.Item_Condition_Table > 0 then for i, reward in ipairs(iapConfig.Item_Condition_Table) do if reward.GetID == Currency_Model.CurrencyType.RechargeCredit then return reward.GetAmount end end end --返回空说明没有配置 end return Currency_Model