package com.bgycc.smartcanteen.viewModel; import android.text.TextUtils; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.bgycc.smartcanteen.entity.PayAck; import com.bgycc.smartcanteen.entity.PayData; import com.bgycc.smartcanteen.entity.PayRequest; import com.bgycc.smartcanteen.entity.PayResponse; import com.bgycc.smartcanteen.executor.SCTaskExecutor; import com.bgycc.smartcanteen.repository.PayDataRepository; import com.bgycc.smartcanteen.repository.PayResponseRepository; import com.bgycc.smartcanteen.socket.SCWebSocketClient; import com.bgycc.smartcanteen.socket.SCWebSocketListener; import com.bgycc.smartcanteen.socket.SCWebSocketListenerAdapter; import com.bgycc.smartcanteen.state.PayOnlineState; import com.blankj.utilcode.util.LogUtils; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG; /** * 负责处理在线支付订单 <br/> * 将订单发送到后台,并接收后台返回的支付结果: <br/> * 1、"正在支付"状态 <br/> * 2、"支付成功",需检查是否为待处理订单,是否当前设备订单,符合后更新数据库状态 <br/> * 3、"支付失败",更新数据库状态 <br/> * 4、超时未处理订单将标记为需要"离线支付"订单 <br/> * 支付状态(空闲、发送订单信息、支付中、支付成功、支付失败)都会通过{@link PayOnlineState}发出通知 <br/><br/> */ public class PayOnlineViewModel extends ViewModel { private static final long TIMEOUT = 10 * 1000; // 在线支付延迟150ms执行,留出时间给扫码反馈 private static final long REQUEST_DELAY = 150; private static final long DEFAULT_DELAY = 3 * 1000; private Gson gson; private String deviceSN; private PayDataRepository payDataRepository; private PayResponseRepository payResponseRepository; private MutableLiveData<PayOnlineState> payOnlineState = new MutableLiveData<>(); public LiveData<PayOnlineState> getPayOnlineStateEvent() { return payOnlineState; } private PayRequest payRequest; private ScheduledFuture<?> timeoutFuture; public PayOnlineViewModel(PayDataRepository payDataRepository, PayResponseRepository payResponseRepository, Gson gson, String deviceSN) { this.payDataRepository = payDataRepository; this.payResponseRepository = payResponseRepository; this.gson = gson; this.deviceSN = deviceSN; } public void initialize() { SCWebSocketClient.getInstance().addListener(listener); } public void exec(PayData payData) { payRequest = new PayRequest(deviceSN, payData); cancelTimeout(); TimeoutRunnable timeoutRunnable = new TimeoutRunnable(); timeoutFuture = SCTaskExecutor.getInstance().schedule(timeoutRunnable, TIMEOUT, TimeUnit.MILLISECONDS); RequestRunnable requestRunnable = new RequestRunnable(); SCTaskExecutor.getInstance().schedule(requestRunnable, REQUEST_DELAY, TimeUnit.MILLISECONDS); } private void cancelTimeout() { if (timeoutFuture == null) return; timeoutFuture.cancel(true); timeoutFuture = null; } private SCWebSocketListener listener = new SCWebSocketListenerAdapter() { private static final String RESPONSE_MESSAGE = "message"; private static final String RESPONSE_PAY_WAIT = "正在支付......"; private static final String RESPONSE_PAY_RESULT = "PAY_RESULT"; @Override public void onMessage(String action, JsonObject obj, String original) { String message = ""; if (obj.has(RESPONSE_MESSAGE)) { message = obj.get(RESPONSE_MESSAGE).getAsString(); } if (TextUtils.isEmpty(action) && message.equals(RESPONSE_PAY_WAIT)) { LogUtils.d(TAG, "正在支付: " + original); payOnlineState.postValue(new PayOnlineState(PayOnlineState.WAIT, message)); return; } if (!action.equals(RESPONSE_PAY_RESULT)) return; // 在线支付结果需要匹配以下规则: // 1、action为"PAY_RESULT" if (payRequest == null || payRequest.getData().isEmpty()) { LogUtils.w(TAG, "后台返回在线支付结果,但没有待处理任务"); payOnlineState.postValue(new PayOnlineState(PayOnlineState.IDLE)); return; } LogUtils.d(TAG, "在线支付结果响应: " + original); ResponseRunnable runnable = new ResponseRunnable(original); SCTaskExecutor.getInstance().executeOnDiskIO(runnable); } }; private class RequestRunnable implements Runnable { @Override public void run() { String requestStr = gson.toJson(payRequest); payOnlineState.postValue(new PayOnlineState(PayOnlineState.SEND, requestStr)); SCWebSocketClient.getInstance().send(requestStr); LogUtils.d(TAG, "在线支付: " + payRequest.toString()); } } private class ResponseRunnable implements Runnable { private String response; ResponseRunnable(String response) { this.response = response; } @Override public void run() { PayResponse payResponse = gson.fromJson(response, PayResponse.class); PayData latestData = payRequest.getData().get(0); // 需要检查: // 1、返回的结果设备id是否和当前设备一致 // 2、返回的结果是否和在处理的订单一致 if (!payResponse.matchDevice(deviceSN) || !matchPayRequest(latestData, payResponse)) { LogUtils.w(TAG, "订单号或设备不匹配\n" + "结果: " + response + "当前: " + payRequest.toString()); return; } cancelTimeout(); long lastInsertId = payResponseRepository.insertPayResponse(payResponse); if (lastInsertId == -1) { LogUtils.w(TAG, "在线支付结果插入数据库失败: " + response); } if (payResponse.paySuccess()) { latestData.paySuccess(); payOnlineState.postValue(new PayOnlineState(PayOnlineState.SUCCESS, payResponse.getMessage(), response)); } else { latestData.payFailed(); payOnlineState.postValue(new PayOnlineState(PayOnlineState.FAILED, payResponse.getMessage(), response)); } payDataRepository.updatePayData(latestData); // 通知服务器,已成功接收该通知 PayAck ack = new PayAck(payResponse.getSerialNumber()); SCWebSocketClient.getInstance().send(gson.toJson(ack)); try { Thread.sleep(DEFAULT_DELAY); } catch (Exception ignored) { } finally { payOnlineState.postValue(new PayOnlineState(PayOnlineState.IDLE)); } } private boolean matchPayRequest(PayData latestData, PayResponse response) { String latestPayCode = latestData.getPayCode(); String responsePayCode = response.getPayCode(); if (responsePayCode.equals(latestPayCode)) { return true; } findAndUpdatePayData(responsePayCode); return false; } private void findAndUpdatePayData(String target) { // 若服务器响应订单与当前在处理订单不一致 // 则从数据库里获取对应订单并更新状态 PayData matchPayData = payDataRepository.queryPayDataByPayCode(target); if (matchPayData.success()) { // 该订单可能支付了两次 LogUtils.w(TAG, "订单号: " + matchPayData.toString() + " 支付了两次"); } else { matchPayData.paySuccess(); payDataRepository.updatePayData(matchPayData); } } } private class TimeoutRunnable implements Runnable { @Override public void run() { if (payRequest == null || payRequest.getData().isEmpty()) { LogUtils.w(TAG, "在线支付超时: 待处理任务为空"); payOnlineState.postValue(new PayOnlineState(PayOnlineState.FAILED, "支付超时")); return; } // 虽然当前订单支付超时,但已标记"离线支付"状态,因此可通知用户已支付成功(等待扣费即可) payOnlineState.postValue(new PayOnlineState(PayOnlineState.SUCCESS)); LogUtils.d(TAG, "订单支付超时,已标记离线支付: " + payRequest.toString()); List<PayData> payDataList = payRequest.getData(); for (PayData d : payDataList) { d.payOffline(); } payDataRepository.updatePayData(payDataList); try { Thread.sleep(DEFAULT_DELAY); } catch (Exception ignored) { } finally { payOnlineState.postValue(new PayOnlineState(PayOnlineState.IDLE)); } } } }