feat(payment): 添加会员折扣功能并重构支付逻辑

- 新增 getMemberDiscount 接口用于查询会员折扣
- 在人脸识别支付中集成会员折扣计算逻辑
- 将支付金额计算从 vipPrice 切换到 specPrice 并应用会员折扣
- 在初始化活动中添加设备配置获取功能
- 使用 ConstraintLayout 重构主餐品列表布局
- 添加 Timber 日志框架替换原有 Log 工具
- 重构 MainScreenPresentation 中的食物价格计算逻辑
- 移除重复的设备配置获取代码,统一在 InitActivity 中处理
This commit is contained in:
2026-04-27 17:52:45 +08:00
parent 416a8366e1
commit 8f1f15dd74
10 changed files with 113 additions and 55 deletions
@@ -17,9 +17,11 @@ import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.utils.FoodVectorTool
import com.sw.dualscreen.utils.L
import com.sw.dualscreen.utils.NetworkUtils
import com.sw.dualscreen.utils.SpTool
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.AppUtil
import com.sw.plate.utils.ToastUtils
import kotlinx.coroutines.launch
class InitActivity : BaseActivity<ActivityInitBinding>() {
@@ -180,11 +182,32 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
loadNetworkErrorDialog()
return
}
getDeviceConfig()
getCollectedFoodVector()
}
}
}
private fun getDeviceConfig() {
userViewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
ToastUtils.showToast("获取设备配置数据失败")
return@runOnUiThread
}
var settlementMode = 1
//结算模式:1-独立支付,2-联合结算
//settlementMode = if (deviceConfig.payType == 2) 0 else 1
// TODO: 2022/10/13 修改为独立支付
SpTool.settlementMode = settlementMode
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
}
}
}
private fun loadNetworkErrorDialog() {
RemindDialog(
context = context,
@@ -86,6 +86,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.time.LocalDateTime
import java.time.ZoneId
@@ -200,6 +201,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() {
super.initialize()
settlementMode = SpTool.settlementMode
isPageVisible = true
// 拦截扫码枪按键事件,防止触发页面按钮点击
registerKeyEvent()
@@ -212,9 +214,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
initData()
}
private fun initLayoutEatDetailBinding(mode: Int) {
private fun initLayoutEatDetailBinding() {
Timber.tag(TAG).d("initLayoutEatDetailBinding,settlementMode=$settlementMode")
binding.flFoodList.removeAllViews()
if (mode == 0) {
if (settlementMode == 0) {
//联合支付
val b = LayoutMainFoodList2Binding.inflate(layoutInflater, binding.flFoodList, true)
rvFoodList = b.rvFoodList
@@ -255,25 +258,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
private fun initView() {
initLayoutEatDetailBinding(SpTool.settlementMode)
viewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
ToastUtils.showToast("获取设备配置数据失败")
initLayoutEatDetailBinding(SpTool.settlementMode)
return@runOnUiThread
}
//结算模式:1-独立支付,2-联合结算
settlementMode = if (deviceConfig.payType == 2) 0 else 1
SpTool.settlementMode = settlementMode
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
viewModel.activeEngine()
initLayoutEatDetailBinding(settlementMode)
}
}
viewModel.activeEngine()
initLayoutEatDetailBinding()
updateDateTime()
binding.tvTitleTime.setOnClickListener {
EnvSwitchDialog(context = this, onEnvChanged = {
@@ -166,6 +166,8 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
super.onDestroy()
}
var memberDiscount = 1.0
var memberInfo: MemberInfo? = null
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
this.memberInfo = memberInfo
@@ -191,9 +193,19 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
it.type = type
it.foodName = foodInfo?.foodName
//确认使用vipPrice 还是 specPrice
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
it.totalPrice = (onePrice ?: 0.0) * eatNum
presentation.initView()
//val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
if (isVip) {
userViewModel.getMemberDiscount(memberInfo?.faceUserId?:"") { discount ->
memberDiscount = if (discount == null) 1.0 else discount / 10
var onePrice = foodInfo?.specPrice ?: 0.0
onePrice = if (isVip) onePrice * memberDiscount else onePrice
it.totalPrice = onePrice * eatNum
presentation.initView()
}
} else {
it.totalPrice = (foodInfo?.specPrice ?: 0.0) * eatNum
presentation.initView()
}
}
}
@@ -23,7 +23,9 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
// }
payActivity.foodInfo?.let {
foodName = it.foodName
payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
// payAmount = (it.vipPrice ?: 0.0) * payActivity.eatNum
val price = it.specPrice ?: 0.0
payAmount = price * payActivity.memberDiscount * payActivity.eatNum
}
showSubScreen()
}
@@ -75,7 +75,9 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
}
if (memberInfo != null && memberInfo!!.member) {
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
// totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
val price = payActivity.foodInfo?.specPrice ?: 0.0
totalPrice = price * payActivity.memberDiscount * payActivity.eatNum
binding.layoutVip.visible()
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
@@ -321,4 +321,13 @@ interface ApiService {
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/settlement/terminal/app/getFoodInfoBySettlement",
@Query("userId") userId: String
): ApiResponse<FoodOrderModel?>
/**
* 查询会员折扣
*/
@GET
suspend fun getMemberDiscount(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getMemberDiscount",
@Query("userId") userId: String
): ApiResponse<Double?>
}
@@ -69,6 +69,7 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
class MainScreenPresentation(
val activity: MainActivity,
display: Display,
@@ -338,6 +339,7 @@ class MainScreenPresentation(
}
private val step3Debouncer = Debouncer(10*1000)
private var memberDiscount:Double = 1.0
/**
* 根据接口数据更新热量、营养数据
@@ -464,7 +466,6 @@ class MainScreenPresentation(
/**
* 餐品识别成功,不计费模式
*/
@SuppressLint("SetTextI18n")
fun loadUnbilledMode() {
if (currentUserId.isNullOrBlank()) {
return
@@ -685,7 +686,7 @@ class MainScreenPresentation(
private var lastAddWeight = 0
private fun addFoodToOrder(weight: Int, foodInfo: FoodInfo) {
Log.d(TAG, "addFoodToOrder: ${weight == lastAddWeight}")
Timber.tag(TAG) .d("addFoodToOrder: ${weight == lastAddWeight}")
// if (weight == lastAddWeight) return
lastAddWeight = weight
if (weight <= 10) {
@@ -701,9 +702,10 @@ class MainScreenPresentation(
}
return
}
val foodPrice = if (isMember) foodInfo.vipPrice else foodInfo.specPrice
//val foodPrice = if (isMember) foodInfo.vipPrice else foodInfo.specPrice
val foodPrice = (foodInfo.specPrice ?: 0.0) * memberDiscount
val eatNum = activity.getEatNum(weight.toDouble(), foodInfo.specWeight)
val price = eatNum * (foodPrice ?: 0.0)
val price = eatNum * foodPrice
if (foodOrderList.isNotEmpty() && mealPickupMode == 1) {
val last = foodOrderList.last()
if (last.isLocalData) {
@@ -732,8 +734,7 @@ class MainScreenPresentation(
)
// foodOrderAdapter.submitList(foodOrderList)
foodOrderAdapter.notifyDataSetChanged()
Log.d(
TAG,
Timber.tag(TAG) .d(
"addFoodToOrder: size=${foodOrderList.size},新增数据:${foodOrderList.last()}"
)
//foodOrderAdapter.notifyDataSetChanged()
@@ -742,7 +743,7 @@ class MainScreenPresentation(
eatWeightSum += it.num
}
binding.detailInclude.tvTotalWeight.text = "总重量:${eatWeightSum}"
Log.d(TAG, "addFoodToOrder: 更新总重量,新增数据------------------------------")
Timber.tag(TAG) .d("addFoodToOrder: 更新总重量,新增数据------------------------------")
}
private fun calculateNutrition(value: Int) {
@@ -1031,7 +1032,11 @@ class MainScreenPresentation(
}
}
// pauseCamera()
step3ShowRecognizeResult(userId)
//先查询会员折扣
userViewModel.getMemberDiscount(userId) { discount ->
memberDiscount = if (discount == null) 1.0 else discount / 10
step3ShowRecognizeResult(userId)
}
})
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
@@ -1471,7 +1476,6 @@ class MainScreenPresentation(
/**
* 加载订单列表
*/
@SuppressLint("NotifyDataSetChanged", "SetTextI18n")
private fun loadOrderList(userId: String, callback: () -> Unit) {
binding.llTopInfo.gone()
binding.flFoodDetail.visible()
@@ -1489,7 +1493,7 @@ class MainScreenPresentation(
//foodOrderAdapter.submitList(foodOrderList)
foodOrderAdapter.notifyDataSetChanged()
//binding.detailInclude.tvTotalWeight.text = "总重量:${model.eatWeightSum}克"
Log.d(TAG, "addFoodToOrder: 更新总重量,订单列表------------------------------")
Timber.tag(TAG).d("addFoodToOrder: 更新总重量,订单列表------------------------------")
// binding.detailInclude.tvTotalCalorie.text = "总热量:${model.calorie.roundedDecimalPlace(1)}千卡"
jointPaymentQueryFinish = true
@@ -359,4 +359,10 @@ class RemoteRepository constructor(
apiService.getFoodOrderList(userId = userId)
}
}
suspend fun getMemberDiscount(userId: String): ApiResponse<Double?> {
return safeApiCall {
apiService.getMemberDiscount(userId = userId)
}
}
}
@@ -651,4 +651,16 @@ class UserViewModel : BaseViewModel() {
}
}
}
fun getMemberDiscount(userId: String, block: (Double?) -> Unit) {
Timber.tag(TAG).d("getMemberDiscount")
launchWithLoading {
val response = repository.getMemberDiscount(userId)
if (parseResponse(response)) {
block(response.data)
} else {
block(null)
}
}
}
}
@@ -1,42 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="14dp"
android:layout_marginTop="42dp"
android:layout_weight="1"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="8"
tools:listitem="@layout/list_item_search_food" />
<TextView
android:id="@+id/tvToSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="38dp"
android:layout_gravity="center_horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:padding="10dp"
tools:text="以上都不是,手动搜索"
android:textColor="#FFFF3232"
android:textSize="31sp"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFoodList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/tvToSearch"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginHorizontal="14dp"
android:layout_marginTop="42dp"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="8"
tools:listitem="@layout/list_item_search_food" />
<include
android:id="@+id/detailInclude"
layout="@layout/layout_eat_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="14dp"
android:layout_height="0dp"
android:visibility="gone"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>