支付宝在移动支付领域的统治地位,使得我们有必要梳理支付宝移动开发流程。本文写作的目的就是梳理支付流程,从架构层面讲述如何在移动应用中嵌入支付宝支付功能,以及指出哪些地方存在开发陷阱。
public class PayStateMachine { /* all possible state of payment */ public enum PayState { PAY_INIT, PAY_GOT_CONTEXT, PAY_UPDATED_ORDER, PAY_APPLIED_ ID, PAY_ORDER_CREATED, PAY_SUCCEED, ERROR_OCCURRED} /* errors may occurred during payment */ public enum PayError { PAY_GET_CONTEXT_FAIL, PAY_UPDATE_ORDER_FAIL, PAY_APPLY_ID_FAIL, PAY_FAIL } private static PayStateMachine instance; private PayState state; private IOrder order; private IPayment payment; private PayStateMachine() { } public static PayStateMachine getInstance() { if (instance == null) { instance = new PayStateMachine(); } return instance; } public void initPayment(IOrder order, IPayment payment) { this.order = order; this.payment = payment; this.state = PayState.PAY_INIT; } public void startPay() { changeState(PayState.PAY_INIT); } public void changeState(PayState state) { onStateChanged(this.state, state); } public void reportError(PayError error, String detail) { LogUtil.printPayLog("the error id is:" + error + " " + detail); changeState(PayState.ERROR_OCCURRED); } private void onStateChanged(PayState oldState, PayState newState) { LogUtil.printPayLog("oid state:" + oldState + " new state:" + newState); this.state = newState; handlePayStateChange(); } private void handlePayStateChange() { if (this.order == null || this.payment == null) { LogUtil.printPayLog("Have not initiated payment"); return; } switch (this.state) { case PAY_INIT: order.getPayContext(); break; case PAY_GOT_CONTEXT: order.createOrder(); break; case PAY_UPDATED_ORDER: case PAY_APPLIED_ID: case PAY_ORDER_CREATED: payment.pay(order); break; case PAY_SUCCEED: case ERROR_OCCURRED: finishProcess(); break; default: LogUtil.printPayLog("state is not correct!"); finishProcess(); } } private void finishProcess() { this.order = null; this.payment = null; this.state = PayState.PAY_INIT; } }
操作2(调用支付接口)和操作7(接口返回支付结果):App与支付宝API的交互。
操作5(异步发送支付通知):支付宝服务器与App后台的交互。
从数据流角度看支付流程:
客户端实现
本文结合操作流程和数据流程,讲述主要的实现方案。
首先假设订单数据都已经存储在OrderPayModel中。
private void getOrderIdRequest() { JSONObject ob = new JSONObject(); ob.put("amount", orderPayModel.getOrderPriceTotal()); ob.put("productDescription", orderPayModel.getOrderName()); ob.put("userId", orderPayModel.getUserId()); ob.put("barCoupon", orderPayModel.getOrderId()); ob.put("barId", orderPayModel.getBarId()); ob.put("count", orderPayModel.getOrderNums()); LogUtil.printPayLog("get order id request data:" + orderPayModel.toString()); HttpRequestFactory.getInstance().doPostRequest(Urls.ALI_PAY_APPLY, ob, new AsyncHttpResponseHandler() { @Override public void onSuccess(String content) { super.onSuccess(content); LogUtil.printPayLog("get order id request is handled"); PayNewOrderModel rm = new PayNewOrderModel(); rm = JSON.parseObject(content, PayNewOrderModel.class); if (rm.getCode() != null && "200".equalsIgnoreCase(rm.getCode())) { tradeNo = rm.getResult().getTrade_no(); LogUtil.printPayLog("succeed to get order id:" + tradeNo); orderStr = generateOrder(); PayStateMachine.getInstance().changeState( PayState.PAY_APPLIED_ID); } else { PayStateMachine.getInstance().reportError( PayError.PAY_APPLY_ID_FAIL, "code is not right"); } } @Override public void onFailure(Throwable error, String content) { PayStateMachine.getInstance().reportError( PayError.PAY_APPLY_ID_FAIL, "failed to get order id"); }; @Override public void onFinish() { LogUtil.LogDebug("Payment", "on get order id finish", null); }; }); }
private String getOrderInfo(String partner, String seller) { String orderInfo; // 合作者身份ID orderInfo = "partner=" + "\"" + partner + "\""; // 卖家支付宝账号 orderInfo += "&seller_id=" + "\"" + seller + "\""; // 商户网站唯一订单号 orderInfo += "&out_trade_no=" + "\"" + tradeNo + "\""; // 商品名称 orderInfo += "&subject=" + "\"" + orderName + "\""; // 商品详情 orderInfo += "&body=" + "\"" + orderDetail + "\""; // 商品金额 orderInfo += "&total_fee=" + "\"" + totalPrice + "\""; // orderInfo += "&total_fee=" + "\"" + "0.01" + "\""; // 服务器异步通知页面路径 orderInfo += "¬ify_url=" + "\"" + Urls.ALI_PAY_NOTIFY + "\""; // 接口名称, 固定值 orderInfo += "&service=\"mobile.securitypay.pay\""; // 支付类型, 固定值 orderInfo += "&payment_type=\"1\""; // 参数编码, 固定值 orderInfo += "&_input_charset=\"utf-8\""; // 设置未付款交易的超时时间 // 默认30分钟,一旦超时,该笔交易就会自动被关闭。 // 取值范围:1m~15d。 // m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。 // 该参数数值不接受小数点,如1.5h,可转换为90m。 orderInfo += "&it_b_pay=\"30m\""; // 支付宝处理完请求后,当前页面跳转到商户指定页面的路径. // orderInfo += "&return_url=\"m.alipay.com\""; // Bill: this item must not be empty! though the api demo said it // can be. orderInfo += "&return_url=\"m.alipay.com\""; // 调用银行卡支付,需配置此参数,参与签名, 固定值 // orderInfo += "&paymethod=\"expressGateway\""; } return orderInfo; }
final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
PayTask alipay = new PayTask(PayDemoActivity.this); // 调用支付接口,获取支付结果 String result = alipay.pay(payInfo);
public class PayOrder { public String tradeNo; //随机编号 public String amount; //付款金额 public String status; //操作状态 public String statusCode; //操作状态代码,0-未支付,10-已支付,4000-退款中,5000-已退款,6000-付款失败,6001-取消付款,7000-已消费 public String orderNo; //支付宝流水号 public String productDescription; //商品名称 public String payNo; //消费码 public String isRefund; //是否申请退款 public String createTime; //创建时间 public String modifyTime; //修改时间 public String userId; //用户id public Integer id; //主键 public String pId; //商品id public int buyNumber; //存储购买数量 public int vendorId; //商家ID }
@RequestMapping(value = "/payorder") @ResponseBody public Map<String, Object> pay(HttpServletRequest request, HttpServletResponse response) { Map<String, Object> map = JsonPUtil.pToMap(request); Map<String, Object> msgMap = new HashMap<String, Object>(); if (!map.isEmpty()) { try { log.info("执行购买前确认操作:" + map); String now = String.valueOf(System.currentTimeMillis()); String trade_no = map.get("barId").toString() + "-" + map.get("barCoupon").toString() + "-" + map.get("count").toString() + "-" + now.substring(now.length() - 6); PayOrder alipay = new PayOrder(); alipay.setAmount(map.get("amount").toString()); alipay.setTradeNo(trade_no); alipay.setProductDescription(map.get("productDescription").toString()); alipay.setCreateTime(now); alipay.setStatus("待支付"); alipay.setStatusCode("0"); alipay.setExtInt1(Integer.parseInt(map.get("count").toString())); alipay.setUserId(map.get("userId").toString()); alipay.setpId(map.get("barCoupon").toString()); alipay.setExtInt2(Integer.parseInt(map.get("barId").toString())); int flag = alipayServiceImpl.pay(alipay); log.info("确认操作执行结果:" + flag); Map<String, Object> m = new HashMap<String, Object>(); m.put("trade_no", trade_no); m.put("seller", new String(Base64.encode(AlipayConfig.SELLER.getBytes()))); m.put("partner", new String(Base64.encode(AlipayConfig.partner.getBytes()))); m.put("privateKey", new String(Base64.encode(AlipayConfig.ios_private_key.getBytes()))); if (flag > 0) msgMap = ResponseMessageUtil.respMsg(Constance.BASE_SUCCESS_CODE, "success", m); else msgMap = ResponseMessageUtil.respMsg(Constance.BASE_SERVER_WRONG_CODE, "fail"); } catch (Exception e) { e.printStackTrace(); msgMap = ResponseMessageUtil.respMsg(Constance.BASE_SERVER_WRONG_CODE, "fail"); log.error("购买前确认失败", e); } } else { log.info("请求参数不完整"); msgMap = ResponseMessageUtil.respMsg(Constance.ILLEGAL_OPERATE, "fail"); } return msgMap; }支付宝服务器回调App服务器,通知支付结果。App服务器将相应的数据入库后,通知支付宝服务器"success" or "fail"。
@RequestMapping(value = "/payOver") @ResponseBody public String payOver(HttpServletRequest request, HttpServletResponse response) { Map<String, String> map = JsonPUtil.buildMap(request); String result_str = "fail"; if (!map.isEmpty()) { if (AlipayUtils.checkAlipay(map, false) > 0) {// 通过支付宝验证 try { log.info("执行付款的回调函数传递参数:" + map); String now = String.valueOf(System.currentTimeMillis()); String status = map.get("trade_status").toString(); PayOrder alipay = new PayOrder(); alipay.setTradeNo(String.valueOf(map.get("out_trade_no"))); log.info("支付状态:" + status); if (Constance.ALIPAY_SUCCESS_CODE.equals(status) || Constance.ALIPAY_FINISHED_CODE.equals(status)) {// 支付成功 List<Alipay> ali = alipayServiceImpl.search(alipay); if (ali.size() == 1 && (ali.get(0).getPayNo() == null || ali.get(0).getPayNo().equals(""))) {// 消息未处理 Alipay pay = new Alipay(); pay.setTradeNo(String.valueOf(map.get("out_trade_no"))); pay.setStatus("已支付"); pay.setStatusCode("10"); pay.setIsRefund("0"); pay.setModifyTime(String.valueOf(System.currentTimeMillis())); pay.setPayNo(new String(Base64.encode(now.substring(now.length() - 10).getBytes()))); pay.setOrderNo(String.valueOf(map.get("trade_no"))); int flag = alipayServiceImpl.payOver(pay); log.info("用户付款成功" + map); if (flag > 0) result_str = "success"; } } else { return result_str; } } catch (Exception e) { e.printStackTrace(); log.error("回调函数获取参数失败", e); return result_str; } } } return result_str; }