说句走心的话,这篇文章是给自己看的,以后再做第三方支付的时候可以少踩坑。
目标读者:Java 服务器后端开发者;需要实现第三方支付接口调用功能的开发者;需要 APP 支付功能实现者;
故事发生在几天前的一个午后,我们终于申请到了微信和支付宝的支付功能,或得了如下信息:
微信:
wxpay.appid=wx0c341ac12e3a8ce1
wxpay.mchid=1439182322
wxpay.key=192006250b4c09247ec02edce69f6a2d
wxpay.notify_url=http://api.woliegequ.com/api/payment/wxpay/notify
支付宝:
alipay.appId=2018011503899539
alipay.publicKey=MIIBIjANBgkqhkiG92133EFAAOCAQ8AMIIBCgKCAQEAlDGp9uLXtdaxXeH0jAQ3KuWzAK9wtvURoHNRUz41m9IbVm+mqx0v/9YRvXTZtRAPwB46K4cnJj3vXm5ugPO8VyShw30cJHtZiL1H1pW297Rqm+n7owgkjL8V1ylIKR9GxTe3hhQ4oPOk8JWYdUEMHItd3RUUQ4bnTxAAjqo8R1LyKnhTNQ4BwCoe3tXWntnEXSK32323iINA4yyTXNuk5Qv515fpjjfNUyslnMKW/q329guF8TzBKRe/i/OvPDeYIe7wxdbSNqAFYxd4hNyIjoPnq12cVpeYT1215Bjtj+UBuwsQV//322IDAQAB
alipay.privateKey=MIIE212BADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCUMan24te11rFd4fSMBDcq5bMAr3C29RGgc1FTPjWb0htWb6arHS//1hG9dNm1EA/AHjorhycmPe9ebm6A87xXJKHDfRwke1mIvUfWlbb3tGqb6fujCCSMvxXXKUgpH0bFN7eGFDig86TwlZh1QQwci13dFRRDhudPEACOqjxHUvIqeFM1DgHAKh7e1dae2cRdIqlgtCdDvJ435456TlC/nXl+mON81TKyWcwpb+q2eFZoWGoD2C4XxPMEpF7+L8688N5gh7vD3245hPjfOPy/H8rkGO2P5QG7CxBX/8Np0fAgMBAAECggEAE6E0e1B29E449k+c3gMc76C3gkq66nEx4YgE6LrfzQEav+tQL3BRUFkhxm+4+sPi4jbey68+X1Fq6J5GIuymMQDYMJXc6XQxWux/nIv+TXdne7mVrHXC43678D6Y0+CtJ+ML1Prb8iCKWvGYF95Oj7LZBpmm6fMR2PJhghIhROkrIFJngewerwfIeaJFRxW/rmzfg1V5N2F3+WRnStu7nD0KOz7S7PZjieymA/Blgrewgerg78ENVMXp2BmZuu3mPOyiiGMBz5pgwree9s9y9Y1/7aZ5SXIQgrewregyNeAP6KXohsPFyulqNN+bpxrVxSjFq2EXYq1tyhr/LVZ13ab4vzFTq4z4x87QfNlar34fw/Fo9acUL41ivSPwl6r4xoXgbQ462TIxCRCXhS7OJYtCx1CKcQKBgQCzXCWH0mc/sD1TQHNKDCvNfZGgSQnBferfrCK10klFx6W98uqKv3+j6DLzlL+iyyx4Fnpsd22PE0U0NNTI0l2lYIOdTCoKeZ3/7w7KJqP3sbHsZnr+vRzR+eOreKjZuaHIjwKBgQCfOQCRv7Qg
alipay.notifyUrl=http://api.woliegequ.com/api/payment/alipay/notify
当然啦,这些信息是假的,怎么可能公开嘛。。我们切入正题!
先贴代码,思路好讲:
微信:
/**** @param oid 订单号* @param price 价格* @param body 理解为:备注* @param ipAddress* @param deviceInfo 默认为:"WEB"* @return* @throws ActionParameterException 参数异常* @throws IOException IO 异常(网络请求可能会抛出)*/publicStringcreateOrder(Stringoid,BigDecimalprice,Stringbody,StringipAddress,StringdeviceInfo)throwsActionParameterException,IOException{StringplaceUrl="https://api.mch.weixin.qq.com/pay/unifiedorder";SortedMap<String,Object>parameters=newTreeMap<String,Object>();parameters.put("appid",appid);parameters.put("mch_id",mch_id);parameters.put("device_info","WEB");parameters.put("body",body);parameters.put("nonce_str",wxUtils.gen32RandomString());// 32 位随机字符串,26 个字母的大小写组成parameters.put("notify_url",notifyUrl);parameters.put("out_trade_no",oid);// parameters.put("total_fee", price.multiply(BigDecimal.valueOf(100)).intValue());parameters.put("total_fee",1);// 测试环境,设定为 1 分钱parameters.put("spbill_create_ip",ipAddress);parameters.put("trade_type","APP");parameters.put("sign",wxUtils.createSign(parameters));// 必须在最后returnwxUtils.executePost(placeUrl,parameters);// 网络请求}
支付宝:
/*** 返回支付宝订单ID** @param oid 订单号* @param price* @param body 备注信息* @param subject 备注信息* @return* @throws ActionParameterException 参数异常*/publicStringcreateOrder(Stringoid,BigDecimalprice,Stringsubject,Stringbody)throwsActionParameterException{//实例化客户端AlipayClientalipayClient=newDefaultAlipayClient("https://openapi.alipay.com/gateway.do",APP_ID,APP_PRIVATE_KEY,"json","utf-8",ALIPAY_PUBLIC_KEY,"RSA2");//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.payAlipayTradeAppPayRequestrequest=newAlipayTradeAppPayRequest();//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。AlipayTradeAppPayModelmodel=newAlipayTradeAppPayModel();model.setBody(body);model.setSubject(subject);model.setOutTradeNo(oid);model.setTimeoutExpress(TIMEOUT_EXPRESS);// model.setTotalAmount("" + price.doubleValue());model.setTotalAmount("0.01");// 测试环境,设定为 1 分钱model.setProductCode("QUICK_MSECURITY_PAY");request.setBizModel(model);request.setNotifyUrl(NOTIFY_URL);try{//这里和普通的接口调用不同,使用的是sdkExecuteAlipayTradeAppPayResponseresponse=alipayClient.sdkExecute(request);System.out.println(response.getBody());//就是orderString 可以直接给客户端请求,无需再做处理。returnresponse.getBody();}catch(AlipayApiExceptione){e.printStackTrace();}returnnull;}
对比可发现一个小细节,微信的价格单位是「分」,支付宝是「元」;微信是自己写的 HTTP 客户端进行请求,支付宝是用的 SDK 进行的请求,虽然本质上没差。
其中支付宝用到的第三方 sdk 是这个人的,贴一下 maven:
com.github.1991wangliang
alipay-sdk
1.0.0
安全性暂且不考虑了,不相信会有什么恶意代码在里面。实在不行也建议自己重新实现一下也是不错的。阿里也在文档里面给了 .jar 的 sdk 可以使用,但一直没有找到对应的 maven 源算是个小遗憾的消息。
翻篇,我们讲一下官方文档的流程。
适用场景为普通的手机 APP 消费,使用微信支付操作。可参考:
先贴微信给出的 API 列表:
微信开放平台官方文档 API 列表
根据这个列表我们可以隐约知道流程大概是这样子的:
Server 端下单 --> APP 调用支付操作(会跳转到微信 APP 进行支付流程然后支付完成后跳转回自己的 APP)--> APP 查询支付结果
之后的查询订单和关闭订单都是后话了,就不展开讨论。上述第一步可以细细拆开一下变成:
APP 调用下单接口(可能包含很多业务,比如:购买衣服的种类等) --> Server 得到下单消息,访问微信平台进行下单(只包含支付业务)--> 微信平台返回成功信息 --> Server 将该信息直接返回给客户端 APP 即可
这是微信官方的流程图,参考一下:
微信开放平台「微信支付」流程图
所以,Server 服务器端需要有三个接口:
APP 下单接口(可能不止一个,但支付的 Service 走的一条线)微信平台将数据发回 Server 的接口(一般称为回调接口)APP 查询订单接口(查看订单状态、交易额等)
APP 下单接口就不展开了,其中业务服务层的代码在文章开篇已经示例出,这里就贴上接口回调的 Controller 作为补充:
@RequestMapping(value="/wxpay/callback",method={RequestMethod.POST,RequestMethod.GET})publicStringwx(WxPayTradeModelwxPayTradeModel){returnwxPayService.callBack(wxPayTradeModel);}
其中 Service 对应方法如下:
publicStringcallBack(WxPayTradeModelmodel){Stringsign=model.getSign();booleansignSucess=false;// 验证签名if(signSucess){if(model.getReturn_code().equals("SUCCESS")){if(model.getResult_code().equals("SUCCESS")){wxPayTradeService.save(model);orderService.finishOrder(model.getOut_trade_no());}else{wxPayTradeService.save(model);orderService.failOrder(model.getOut_trade_no());}}return"\n"+" \n"+" \n"+"";}return"\n"+" \n"+" \n"+"";}
这里只是简单地持久化了传回地数据,并未做过多处理,正常情况应该要根据传回的 model 信息选择性地更新自己的业务订单(不只是支付订单)信息。
便于浏览,我们再切一条分割线出来,此处记叙一下支付宝的操作流程。
支付宝开放平台文档中心「APP支付」接口列表
相较于「微信支付」,支付宝多了网页支付的能力,所以这里有一条「手机网站支付转APP支付」的栏目。其中 Android 和 iOS 集成同微信文档里的「调起支付接口」一致;「请求参数说明」同微信的「统一下单」一致;「通知参数说明」同微信的「支付结果通知」一致。
这是支付宝的适用场景:
自然的,原理和「微信支付」是一致的,所以此处就不啰嗦太多,先贴出接口回调的代码:
@RequestMapping(value="/alipay/callback",method={RequestMethod.POST,RequestMethod.GET})publicStringali(AliPayTradeModelaliPayTradeModel){try{returnaliPayService.callBack(aliPayTradeModel);}catch(AlipayApiExceptione){return"failure";}}
具体的 Service 类里的方法:
publicStringcallBack(AliPayTradeModelaliPayTradeModel)throwsAlipayApiException{//将异步通知中收到的所有参数都存放到map中JSONObjectjson=JSONObject.fromObject(aliPayTradeModel);booleansignVerified=AlipaySignature.rsaCheckV1(json,ALIPAY_PUBLIC_KEY,"UTF-8","RSA2");//调用SDK验证签名if(signVerified){//验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failureStringout_trade_no=aliPayTradeModel.getOut_trade_no();// 查询该订单是否已经完成交易,若否,则继续TradeModel.TRADE_STATUStrade_status=aliPayTradeModel.getTrade_status();// logger.info(out_trade_no1 + ":" + trade_status1);// 修改订单状态,判断是否完成交易switch(trade_status){caseWAIT_BUYER_PAY:// 交易创建,等待买家付款(该通知不可能拿到,支付宝默认不开启该通知)aliPayTradeService.save(aliPayTradeModel);// 更新支付状态break;caseTRADE_CLOSED:// 未付款交易超时关闭,或支付完成后全额退款aliPayTradeService.save(aliPayTradeModel);orderService.failOrder(out_trade_no);// 关闭订单,订单失败break;caseTRADE_SUCCESS:// 交易支付成功caseTRADE_FINISHED:// 交易结束,不可退款// 成功!aliPayTradeService.save(aliPayTradeModel);orderService.finishOrder(out_trade_no);// 关闭订单,订单成功break;}return"success";}else{//验签失败则记录异常日志,并在response中返回failure.return"failure";}}
此处的业务代码相对于微信那块较详细一些,可以参考一下。我们仍然贴出支付宝的流程图作为补充:
支付宝开放平台文档中心快速接入「支付流程」配图支付宝开放平台文档中心快速接入「支付流程」配图
支付宝和微信的流程可以对比着来看,其实都是一样的。最后附上一篇申请支付功能的流程介绍。
微信支付功能申请:
参考文档
第一步是需要申请一个微信商户平台的账号,账号里面会提供「商户ID」和「密钥 Key」值进行支付功能的实现。一般来说是先申请公众号/企业号/微信开发平台账号然后再进行绑定商户平台账号来操作支付功能的。开放平台链接如下:
开发 APP 必然会有 APP ID,在开放平台申请移动应用创建就可以拿到「APP ID」了。配合商户平台的「商户 ID」和「密钥 Key」就可以参照此文进行微信支付功能的服务器端实现了。
再来看支付宝的申请流程:
这是官方指导页面
第一步:创建应用并获取APPID
第二步:配置应用
第三步:集成和开发
第四步:调用接口
第五步:调试应用
第六步:上线应用
上述六步直接来自于「支付宝开放平台文档中心」,其中第二步配置应用需要自己手动添加公钥信息,自己找个网站或者开源的包生成一下就好了,私钥自己存在服务器本地,公钥交给支付宝进行签名加密。
以上,踩坑的人若见此文便可少挖一坟,少废一时心机,少掉一根头发。
emmmmm,就这样吧。