用戶在食堂,超市進行限訂金額的交易時,能夠經過出示支付二維碼,商家使用掃碼器進行掃碼,全部收款操做由商家端完成,進行免密碼的支付。其中用戶的手機能夠是離線的,可是掃碼器必須是聯網的。 服務器
根據上述支付寶給出的流程圖,咱們能夠將步驟梳理以下:異步
根據以上步驟,咱們繪製以下時序圖:
加密
/** * 獲取被動掃碼支付token * @param accountId 帳戶id * @return 僅以一次有效的token */ String getPayScanPassivityToken(String accountId); /** * 被動掃碼消費 * @param payCode 支付碼 * @param consumeAmount 支付金額 * @param businessId 業務id(食堂訂單id,洗衣訂單id) * @param businessIdType 業務類型 * @param tradeRemark 交易備註 * @return 支付帳戶id */ String consumeByScanPassivity(String payCode, int consumeAmount, String businessId, WalletConsumeType businessIdType,String tradeRemark) throws InvalidOperationException; /** * 根據支付payCode查詢支付狀態 * @param payCode 支付碼 * @return 支付狀態 */ WalletOrderStatus getWalletOrderStatusByToken(String payCode) throws InvalidOperationException;
這裏須要注意的是,APP每次得到的支付令牌token,二維碼是在token的基礎上進行加密繪製的,二維碼本質上是一個支付碼payCode,掃碼器每次得到是payCode,用於交易的是payCode,須要插敘支付狀態的也是payCode。若是用戶一開始就沒有token,那麼APP是沒法進行二維碼繪製的。咱們使用payCode而不是token進行支付的目的就是離線能夠屢次支付。spa
APP生成payCode的時間與服務器校驗payCode的時間是有偏差的,咱們限定的是先後15分鐘有效。若是payCode在60 * 15 個備選數據中有一個符合,咱們都認爲校驗成功。下面是關鍵代碼:code
/** * 檢驗payCode * @param payCode 支付碼 * @return 返回帳戶id */ private String checkPayCode(String payCode) throws InvalidOperationException { log.info("===" + payCode); final int payCodeLessLength = 24; //容許15分鐘有效 final long payCodeTimeStep = 60 * 15; final String payCodePrivateKey = "5d4*********************************"; if(StringUtils.isEmpty(payCode) || payCode.length() <= payCodeLessLength){ throw new InvalidOperationException("支付碼格式異常,請從新掃碼"); } String payTokenStr = payCode.substring(payCode.length() - payCodeLessLength); Optional<WalletPayTokenEntity> payTokenOptional = walletPayTokenJpaRepository.findByToken(payTokenStr); if(!payTokenOptional.isPresent()){ log.error("支付碼不存在:" + payCode); throw new InvalidOperationException("支付碼不存在,請刷新二維碼"); } WalletPayTokenEntity payToken = payTokenOptional.get(); if(!EntityStatusEnum.VALID.getValue().equals(payToken.getEntityStatus())){ throw new InvalidOperationException("支付碼已經消費,請刷新二維碼"); } long timestamp = LocalDateTimeUtils.getSecondsByTime(LocalDateTime.now()); String accountId = payToken.getAccountId(); for (long i = timestamp - payCodeTimeStep; i <= timestamp + payCodeTimeStep; i++) { String raw = accountId + i + payCodePrivateKey; String signCode = DigestUtils.sha1Hex(raw.getBytes()) + payTokenStr; if(payCode.equals(signCode)){ payToken.consume(payCode); walletPayTokenJpaRepository.save(payToken); return accountId; } } log.error("支付碼非法:" + payCode); throw new InvalidOperationException("支付碼非法,請刷新二維碼"); }