package com.gamedog.vididin.manager import com.ama.core.architecture.util.AndroidUtil import com.ama.core.architecture.util.DateUtil import com.ama.core.architecture.util.DeviceUtil import com.ama.core.architecture.util.MD5Util import com.ama.core.architecture.util.NetUtil import com.ama.core.architecture.util.SpUtil import com.ama.core.architecture.util.eventbus.NotifyMan import com.gamedog.statisticreporter.StatisticUtil import com.gamedog.vididin.VidiConst import com.gamedog.vididin.VididinEvents import com.gamedog.vididin.beans.RECORD_CASH_MINUS_WITHDRAW_SUCCESS import com.gamedog.vididin.beans.RecordCash import com.gamedog.vididin.beans.req.PayInitReq import com.gamedog.vididin.beans.req.PayoutCheckReq import com.gamedog.vididin.beans.resp.WithdrawRecord import com.gamedog.vididin.core.login.login.AccountManager import com.gamedog.vididin.manager.WithdrawManager.Companion.STATE_NEED_WATCH_AD import com.gamedog.vididin.netbase.NetworkUtil import com.gamedog.vididin.netbase.Result import com.viddin.videos.free.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.concurrent.locks.ReentrantLock class WithdrawManager private constructor() { private val mBgScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val mRecordLocker = ReentrantLock() private val mRecordList: MutableList by lazy { SpUtil.instance().getList(SpUtil.KEY_WITHDRAW_HISTORY_LIST).toMutableList() } private val mWithdrawItemList: MutableList by lazy { val itemList = SpUtil.instance().getList(SpUtil.KEY_WITHDRAW_ITEM_LIST).toMutableList() if (itemList.isEmpty()) { itemList.addAll(generateItemList()) saveInfos2Sp(itemList) } itemList } companion object { const val EACH_SUB_ITEM_CASH_NUM: Double = 1.0 //----------------------- FROM pagsmile.proto ------------------------------ // init接口 status // 1可提现 2:条件未达成 3:已提现, 4:禁止提现, 5:提现中 const val INIT_OK = 1 const val ERROR_NOT_REACH_CONDITION = 2 const val ERROR_ALREADY_WITHDRAWED = 3 const val ERROR_FORBID = 4 const val ERROR_WITHDRAWING = 5 // init接口 服务器 error 字段 // 0成功,1失败,2签名验证失败,3客户端版本过低,4 ts长度错误 const val ERROR_FAILED_UNKNOW = 1 + 10 const val ERROR_APP_VERSION_LOW = 3 + 10 /* payout接口: 错误码, 0成功,1失败,2签名验证失败,3客户端版本过低,4uuid错误,5所在地国家或地区不在提现限制内,6提现金额不符对应的产品id,7提现产品id不对,8达到提现金额限制,9提现次数超过限制,10今日没有提现机会,11提现账号达到次数限制,12身份审核条件不满足,不能提现,13巴西提现参数 document_type 错误, 14巴西提现参数 document_id 错误,15 巴西提现参数 AccountType 错误,16 巴西提现参数 Name 错误,17巴西提现参数 Account 和 DocumentId 不同,18巴西提现参数account_type为CPF时 对应的 account 错误,19巴西提现参数account_type为CNPJ时 对应的 account 错误,20巴西提现参数 account_type 错误, 21巴西提现参数 document_type 错误,22巴西提现参数account_type为CPF时 对应的 document_id 错误,23巴西提现参数account_type为CNPJ时 对应的 document_id 错误,24 ts长度错误,25 没提0.1就提现其它的 */ const val ERROR_PAYOUT_UNKNOW = 1 + 20 const val ERROR_PAYOUT_APP_VERSION_LOW = 3 + 20 const val ERROR_PAYOUT_COUNTRY_RESTRICTION = 5 + 20 const val ERROR_PAYOUT_REACH_TOTAL_LIMIT = 8 + 20 const val ERROR_PAYOUT_REACH_TIMES_LIMIT = 9 + 20 const val ERROR_PAYOUT_TODAY_NO_CHANCE = 10 + 20 const val ERROR_PAYOUT_ACCOUNT_REACH_TIMES_LIMIT = 11 + 20 const val ERROR_PAYOUT_ACCOUNT_INVALID = 18 + 20 const val ERROR_PAYOUT_USER_IDENTITY_LIMIT = 12 + 20 const val ERROR_PAYOUT_MUST_WITHDRAW01_FIRST = 25 + 20 // subBean 状态 const val STATE_NEED_WATCH_AD: Int = 0 const val STATE_COULD_WITHDRAW: Int = 1 const val STATE_WITHDRAWING: Int = 2 const val STATE_HAS_WITHDRAWED: Int = 3 // 提现交易状态 提现状态 0: 未启动 1:提现中,2:提现成功,3:提现失败 const val TRANSACTION_STATE_UNSTART : Int = 0 const val TRANSACTION_STATE_ONGOING : Int = 1 const val TRANSACTION_STATE_SUCCESS : Int = 2 const val TRANSACTION_STATE_FAIL : Int = 3 @Volatile private var instance: WithdrawManager? = null fun instance(): WithdrawManager { return instance ?: synchronized(this) { instance ?: WithdrawManager().also { instance = it } } } } init { loopCheckTransactionState() } private fun generateItemList(): MutableList { val itemList = mutableListOf() itemList.add(WithdrawItem(0, 0.1, isBigWithDraw = false)) itemList.add(WithdrawItem(1, 10.0, AndroidUtil.randomInt(50, 70), generateSubItemList(10.0))) itemList.add(WithdrawItem(2, 20.0, AndroidUtil.randomInt(50, 70), generateSubItemList(20.0))) itemList.add(WithdrawItem(3, 50.0, AndroidUtil.randomInt(50, 70), generateSubItemList(50.0))) itemList.add(WithdrawItem(4, 100.0, AndroidUtil.randomInt(50, 70), generateSubItemList(100.0))) itemList.add(WithdrawItem(5, 300.0, AndroidUtil.randomInt(50, 70), generateSubItemList(300.0))) return itemList } private fun generateSubItemList(totalCashInItem: Double): List { val subItemList = mutableListOf() val subItemCount: Int = (totalCashInItem/EACH_SUB_ITEM_CASH_NUM).toInt() for (i in 0..subItemCount-1) { val initProgress = AndroidUtil.randomInt(50, 70) subItemList.add(WithdrawSubItem(i, EACH_SUB_ITEM_CASH_NUM, initProgress, initProgress, 0.0)) } return subItemList } private fun saveInfos2Sp(itemList: MutableList) { SpUtil.instance().putList(SpUtil.KEY_WITHDRAW_ITEM_LIST, itemList) } fun getItemList(): List { return mWithdrawItemList } fun getItem(itemIndex: Int): WithdrawItem { return mWithdrawItemList[itemIndex] } fun getHasWithdrawSuccessCashCount(): Double { var count = 0.0 for (record in mRecordList) { if (record.state == TRANSACTION_STATE_SUCCESS) { count += record.cashNum } } // extra withdraw count from zeroBuy count += ZeroManager.instance().getZeroHasWithdrawSuccessCashCount() return count } fun getHasWithdrawSuccessCashCount(itemIndex: Int): Double { var count = 0.0 for (record in mRecordList) { if (record.state == TRANSACTION_STATE_SUCCESS && record.itemIndex == itemIndex) { count += record.cashNum } } return count } fun addAdEarnForSubBean(itemIndex: Int, selectedSubIndex: Int, earnMoneyNum: Double) : Boolean { if (itemIndex >= 0 && itemIndex < mWithdrawItemList.size) { try { val subBean = mWithdrawItemList[itemIndex].subItemList[selectedSubIndex] subBean.hasEarnMoneyByAd += earnMoneyNum * 5 // dollar 2 bariz calculateSubBeanProgress(subBean) //传入 itembean 更新 selectedIndex为下一个 saveInfos2Sp(mWithdrawItemList) notifyProgressUpdated(subBean) return true } catch (e: Exception) { e.printStackTrace() } } return false } private fun notifyProgressUpdated(subBean: WithdrawSubItem) { NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_SUB_ITEM_PROGRESS_UPDATED, NotifyMan.NotifyData(subBean.dayIndex)) } private fun notifySelectedSubBeanChanged(subBean: WithdrawSubItem) { NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_SELECTED_SUB_ITEM_CHANGED, NotifyMan.NotifyData(subBean.dayIndex)) } private fun notifyItemListChanged() { NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_ITEM_LIST_CHANGED, null) } private fun calculateSubBeanProgress(subBean: WithdrawSubItem) { val needEarnProgress = 100 - subBean.startAdProgress if (subBean.hasEarnMoneyByAd >= subBean.cashTotal) { subBean.currentAdProgress = 100 // update state if (subBean.withdrawState == STATE_NEED_WATCH_AD) { subBean.withdrawState = STATE_COULD_WITHDRAW } } else { val newProgress = subBean.startAdProgress + (needEarnProgress * (subBean.hasEarnMoneyByAd / subBean.cashTotal)).toInt() subBean.currentAdProgress = if (newProgress >= 100) 100 else newProgress } } fun updateSubBeanState(subBean: WithdrawSubItem, newState: Int) { subBean.withdrawState = newState saveInfos2Sp(mWithdrawItemList) } fun startItem(curItem: WithdrawItem) { if (curItem.startMs <= 0L) { curItem.startMs = DateUtil.getCurTimeMs() - (if (curItem.totalCashNum == 50.0) 24 * 3600000 * 4 else 0) saveInfos2Sp(mWithdrawItemList) } } fun saveRecordHasNotifyState(recordNo: String) { val recordBean = getRecord(recordNo) recordBean?.let { it.hasShowResultDialog = true saveRecords2Sp() } } fun saveNewRecord(newRecord: WithdrawRecord) { try { mRecordLocker.lock() mRecordList.add(newRecord) } finally { mRecordLocker.unlock() } saveRecords2Sp() loopCheckTransactionState() } private fun saveRecords2Sp() { SpUtil.instance().putList(SpUtil.KEY_WITHDRAW_HISTORY_LIST,getClonedRecordList()) } fun updateRecord(recordNo: String, newState: Int, failType: Int = 0) { var needSaveSp = false try { mRecordLocker.lock() mRecordList.forEachIndexed { index, record -> if (record.recordNo == recordNo) { if (record.state != newState) { needSaveSp =true record.state = newState record.failReason = failType if (newState == STATE_HAS_WITHDRAWED) { checkIfItemFinishAndReset(record.itemIndex) } } return@forEachIndexed } } } finally { mRecordLocker.unlock() } if (needSaveSp) { saveRecords2Sp() } } private fun checkIfItemFinishAndReset(itemIndex: Int) { var needReset = false if (itemIndex == 0) { needReset = true } else { var allSubItemFinish = true mWithdrawItemList[itemIndex].subItemList.forEach { if (it.withdrawState != STATE_HAS_WITHDRAWED) { allSubItemFinish = false return@forEach } } needReset = allSubItemFinish } if (needReset) { resetItem(itemIndex) } } private fun resetItem(itemIndex: Int) { val needResetItem = mWithdrawItemList[itemIndex] needResetItem.apply { totalProgress = 0 startMs = 0L hasStarted = false subItemList.forEach { subItem -> subItem.apply { currentAdProgress = startAdProgress hasEarnMoneyByAd = 0.0 withdrawState = STATE_NEED_WATCH_AD } } } notifyItemListChanged() } fun getClonedRecordList(): List { try { mRecordLocker.lock() val clonedList = mutableListOf() clonedList.addAll(mRecordList) return clonedList } finally { mRecordLocker.unlock() } return emptyList() } private fun getRecord(recordNo: String): WithdrawRecord? { try { mRecordLocker.lock() mRecordList.forEachIndexed { index, record -> if (record.recordNo == recordNo) { return record } } } finally { mRecordLocker.unlock() } return null } private fun loopCheckTransactionState() { var unCheckCount = 0 try { mRecordLocker.lock() mRecordList.forEach { record -> if (record.state == TRANSACTION_STATE_ONGOING) { unCheckCount++ } } } finally { mRecordLocker.unlock() } if (unCheckCount > 0) { try { mRecordLocker.lock() mRecordList.forEachIndexed { index, record -> if (record.state == TRANSACTION_STATE_ONGOING) { mBgScope.launch { requestCheck(record.recordNo) } } } } finally { mRecordLocker.unlock() } } } private suspend fun requestCheck(recordNo: String) { val requestParam = applyInitFields(PayoutCheckReq()).apply { record_no = recordNo } val reqResult = NetworkUtil.callApi { NetworkUtil.apiservice().withdrawCheck(requestParam) } when (reqResult) { is Result.Loading -> { } is Result.Success -> { val checkResult = reqResult.data.data var failedType = ERROR_FAILED_UNKNOW when (checkResult?.error) { 0 -> { when (checkResult.status) { // 提现状态 1:提现中,2:提现成功,3:提现失败 1 -> { delay(10000) loopCheckTransactionState() } 2 -> { handleTransactionSuccess(recordNo) } 3 -> { failedType = ERROR_FAILED_UNKNOW } } } 1 -> { failedType = ERROR_FAILED_UNKNOW } 3 -> { failedType = ERROR_APP_VERSION_LOW } } if (failedType > 0) { handleTransactionFailed(recordNo, failedType) } } is Result.Error -> { } } } private fun handleTransactionSuccess(recordNo: String) { updateRecord(recordNo, TRANSACTION_STATE_SUCCESS) notifyWithdrawCheckResult(recordNo) val recordBean = getRecord(recordNo) recordBean?.let { RecordsManager.instance().updateCashRecord(RecordCash( RECORD_CASH_MINUS_WITHDRAW_SUCCESS, it.cashNum.toDouble(), true).apply { uuid = recordNo }) updateFirstWithdraw01Task(it) sendWithdrawResultStatistic(it.cashNum, true, 0) } } private fun updateFirstWithdraw01Task(recordBean: WithdrawRecord) { if (recordBean.cashNum == 0.1 && !TaskManager.instance().newbieFirstWithdrawStatus().getStatusBean().hasClaimReward) { TaskManager.instance().newbieFirstWithdrawStatus().claimReward() } } private fun handleTransactionFailed(recordNo: String, failedType: Int) { updateRecord(recordNo, TRANSACTION_STATE_FAIL, failedType) val recordBean = WithdrawManager.instance?.getRecord(recordNo) recordBean?.let { sendWithdrawResultStatistic(it.cashNum, false, failedType) } notifyWithdrawCheckResult(recordNo) } private fun sendWithdrawResultStatistic(cashNum: Double, isSuccess: Boolean, failType: Int) { StatisticUtil.reportEvents(StatisticUtil.KEY_Withdrawal_Reason, mapOf("Reason_Type" to (if (isSuccess) "Success" else "Fail"), "Fail_Reason" to failType.toString() + ": " + getFailHintStrRes(failType), "Withdrawal_Position" to cashNum, "Withdrawal_Day" to 1)) } fun getFailHintStrRes(failType: Int) : Int { var failTextRes = R.string.withdraw_normal_fail when (failType) { ERROR_NOT_REACH_CONDITION -> { failTextRes = R.string.withdraw_fail_not_reach_condition } ERROR_ALREADY_WITHDRAWED -> { failTextRes = R.string.withdraw_fail_already_withdraw } ERROR_FORBID -> { failTextRes = R.string.withdraw_fail_forbidden } ERROR_WITHDRAWING -> { failTextRes = R.string.withdraw_fail_withdrawing } ERROR_FAILED_UNKNOW -> { failTextRes = R.string.withdraw_normal_fail } ERROR_APP_VERSION_LOW -> { failTextRes = R.string.withdraw_fail_version_toolow } ERROR_PAYOUT_UNKNOW -> { failTextRes = R.string.withdraw_normal_fail } ERROR_PAYOUT_APP_VERSION_LOW -> { failTextRes = R.string.withdraw_fail_version_toolow } ERROR_PAYOUT_COUNTRY_RESTRICTION -> { failTextRes = R.string.withdraw_fail_country_restriction } ERROR_PAYOUT_REACH_TOTAL_LIMIT -> { failTextRes = R.string.withdraw_fail_reach_amount_limit } ERROR_PAYOUT_REACH_TIMES_LIMIT -> { failTextRes = R.string.withdraw_fail_reach_times_limit } ERROR_PAYOUT_TODAY_NO_CHANCE -> { failTextRes = R.string.withdraw_fail_today_no_chance } ERROR_PAYOUT_ACCOUNT_REACH_TIMES_LIMIT -> { failTextRes = R.string.withdraw_account_times_limit } ERROR_PAYOUT_ACCOUNT_INVALID -> { failTextRes = R.string.withdraw_account_invalid } ERROR_PAYOUT_USER_IDENTITY_LIMIT -> { failTextRes = R.string.withdraw_fail_user_identity_limit } ERROR_PAYOUT_MUST_WITHDRAW01_FIRST -> { failTextRes = R.string.withdraw_fail_must_withdraw_01_first } } return failTextRes } private fun notifyWithdrawCheckResult(recordNo: String) { val recordBean = getRecord(recordNo) recordBean.let { NotifyMan.instance().sendEvent(VididinEvents.EVENT_WITHDRAW_CHECK_RESULT_UPDATED, NotifyMan.NotifyData(it)) } } fun applyInitFields(dataBean: T): T { dataBean.apply { platform = "Android" deviceid = DeviceUtil.generateDeviceId() version = AndroidUtil.getAppVersionInfo() ip = NetUtil.getLocalIpAddress() ts = (System.currentTimeMillis()/1000).toString() val signOrigin = "${VidiConst.WITHDRAW_MD5KEY}platform=${platform}deviceid=${deviceid}version=${version}ip=${ip}ts=$ts" sign = MD5Util.md5ForWithDraw(signOrigin) } return dataBean } fun getItemProgress(itemIndex: Int): Double { var itemProgress = 0.0 if (itemIndex in 0..mWithdrawItemList.size-1) { val curItem = mWithdrawItemList[itemIndex] val userCashTotal = AccountManager.getCash() if (curItem.hasStarted) { return 1.0 } else { itemProgress = if ((userCashTotal / curItem.totalCashNum) > 1.0) 1.0 else userCashTotal / curItem.totalCashNum } } return itemProgress } fun setItemStarted(itemIndex: Int) { if (itemIndex in 0..mWithdrawItemList.size-1) { val curItem = mWithdrawItemList[itemIndex] if (!curItem.hasStarted) { curItem.hasStarted = true curItem.startMs = System.currentTimeMillis() AccountManager.adjustCash(-1 * curItem.totalCashNum) saveInfos2Sp(mWithdrawItemList) notifyItemListChanged() } } } } data class WithdrawItem( val index: Int, val totalCashNum: Double, var totalProgress: Int = 0, val subItemList: List = emptyList(), var startMs: Long = 0L, var hasStarted: Boolean = false, var isBigWithDraw: Boolean = true ) data class WithdrawSubItem( val dayIndex: Int, val cashTotal: Double, val startAdProgress: Int = 0, var currentAdProgress: Int = 0, var hasEarnMoneyByAd: Double = 0.0, var withdrawState: Int = STATE_NEED_WATCH_AD, )