用户在食堂,超市进行限订金额的交易时,能够经过出示支付二维码,商家使用扫码器进行扫码,全部收款操做由商家端完成,进行免密码的支付。其中用户的手机能够是离线的,可是扫码器必须是联网的。 服务器
根据上述支付宝给出的流程图,咱们能够将步骤梳理以下:异步
根据以上步骤,咱们绘制以下时序图:加密
/** * 获取被动扫码支付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("支付码非法,请刷新二维码"); }