项目框架: 项目框架
最近在封装传值方式 准备把json、fromData等传值方式封装成一个体系 可惜封装了半天还是接不到 可能是对HTTP的协议了解的好不够清楚 今天就先把APP支付宝支付的源码放出来吧
好的首先开始支付宝的文档阅读
https://docs.open.alipay.com/58/103584/
之后就是支付宝的工具包准备了
这个是我的整体项目架构 支付宝工具被我放到了一个包里面 接口和实现类 被我封装到了三层中 接下里上源码
com.bing.aliPay.pojo.Product.java(这个是支付VO类)
package com.bing.aliPay.pojo;
import java.io.Serializable;
/**
* @ClassName: Product
* @Description: TODO(支付宝支付 pojo)
* @date 2018-11-6
*/
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String productId;// 商品ID
private String subject;// 订单名称
private String body;// 商品描述
private String totalAmount;// 总金额(单位是元)
private String outTradeNo;// 订单号(唯一)
private String spbillCreateIp;// 发起人IP地址
private String attach;// 附件数据主要用于商户携带订单的自定义数据
private String notifyUrl;// 支付宝回调地址
private String return_url;// 页面同步通知地址
public Product() {
super();
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getSpbillCreateIp() {
return spbillCreateIp;
}
public void setSpbillCreateIp(String spbillCreateIp) {
this.spbillCreateIp = spbillCreateIp;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(String totalAmount) {
this.totalAmount = totalAmount;
}
public String getReturn_url() {
return return_url;
}
public void setReturn_url(String return_url) {
this.return_url = return_url;
}
}
com.bing.aliPay.util.Constants.java(这个是支付宝常量类)
package com.bing.aliPay.util;
import org.springframework.util.ClassUtils;
/**
* @ClassName: Constants
* @Description: TODO(常量)
* @date 2018-11-6
*/
public class Constants {
public static final String SF_FILE_SEPARATOR = System.getProperty("file.separator");//文件分隔符
public static final String SF_LINE_SEPARATOR = System.getProperty("line.separator");//行分隔符
public static final String SF_PATH_SEPARATOR = System.getProperty("path.separator");//路径分隔符
public static final String QRCODE_PATH = ClassUtils.getDefaultClassLoader().getResource("static").getPath()+SF_FILE_SEPARATOR+"qrcode";
//微信账单 相关字段 用于load文本到数据库
public static final String WEIXIN_BILL = "tradetime, ghid, mchid, submch, deviceid, wxorder, bzorder, openid, tradetype, tradestatus, bank, currency, totalmoney, redpacketmoney, wxrefund, bzrefund, refundmoney, redpacketrefund, refundtype, refundstatus, productname, bzdatapacket, fee, rate";
public static final String PATH_BASE_INFO_XML = SF_FILE_SEPARATOR+"WEB-INF"+SF_FILE_SEPARATOR+"xmlConfig"+SF_FILE_SEPARATOR;
public static final String CURRENT_USER = "UserInfo";
public static final String SUCCESS = "success";
public static final String FAIL = "fail";
}
com.bing.aliPay.util.AliPayConfig.java(支付宝公共变量)
package com.bing.aliPay.util;
import com.bing.model.PayModelVo;
import com.bing.util.PropertiesUtil;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
/**
* @ClassName: AliPayConfig
* @Description: TODO(配置公共参数)
* @date 2017-11-6
*/
public final class AliPayConfig {
/**
* 私有的默认构造子,保证外界无法直接实例化
*/
private AliPayConfig(){};
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "";
// 商户私钥,您的PKCS8格式RSA2私钥
public static String merchant_private_key = "";
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
public static String alipay_public_key = "";
//异步回调地址
public static final String NOTIFY_URL = PropertiesUtil.getPropertiesValue("sys","envPrefix")+"";//测试
//PCreturn地址
public static final String RETURN_URL ="www.baidu.com";//测试
public static final String PRODUCT_CODE = "QUICK_MSECURITY_PAY";
// 签名方式
public static String sign_type = "RSA2";
// 字符编码格式
public static String charset = "UTF-8";
// 字符编码格式
public static String time_out = "30m";
private static String method = "alipay.trade.refund";
// 支付宝网关
public static String gatewayUrl = "https://openapi.alipay.com/gateway.do";
/**
* 参数类型
*/
public static String PARAM_TYPE = "json";
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, app_id,
merchant_private_key, PARAM_TYPE, charset,
alipay_public_key,sign_type);
}
/**
* @Title: getAlipayClient
* @Description: TODO(支付宝APP请求客户端实例)
* @return
*/
public static AlipayClient getAlipayClient(){
return SingletonHolder.alipayClient;
}
public static String getResponseBody(PayModelVo payModelVo, String strBody) throws AlipayApiException {
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("支付宝支付");
model.setOutTradeNo(payModelVo.getOutTradeNo()); //商户订单id
model.setTotalAmount(payModelVo.getMoney());// 订单金额:元
model.setSubject(payModelVo.getSubject());// 订单标题
model.setTimeoutExpress("30m");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setEnablePayChannels("moneyFund,debitCardExpress");
request.setBizModel(model);
request.setNotifyUrl(AliPayConfig.NOTIFY_URL);
String orderString="";
try {
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = AliPayConfig.getAlipayClient().sdkExecute(request);
orderString=response.getBody();
System.err.println("orderString : "+orderString);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return orderString;
}
public static String aliPayPc(PayModelVo payModelVo) {
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(payModelVo.getReturnUrl());// 前台通知
alipayRequest.setNotifyUrl(payModelVo.getNotifyUrl());// 后台回调
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", payModelVo.getOutTradeNo());
bizContent.put("total_amount", payModelVo.getMoney());// 订单金额:元
bizContent.put("subject", payModelVo.getSubject());// 订单标题
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
bizContent.put("body", payModelVo.getBody());
bizContent.put("enable_pay_channels", "moneyFund,debitCardExpress");
String biz = bizContent.toString().replaceAll("\"", "'");
alipayRequest.setBizContent(biz);
String form = Constants.FAIL;
AlipayClient alipayClient = getAlipayClient();
try {
form = alipayClient.pageExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
}
return form;
}
}
在这里回调函数是被抽取出来了
PropertiesUtil.getPropertiesValue("sys","envPrefix")
这个方法是我的读取xml配置文件的方法 在后面的博客工具类篇中我会提到
好了 这些就是支付宝的配置文件 接下来就是最重要的 预下单与回调函数的编写了
预下单
/**
* @Title: trans
* @Description: (支付宝交易)
* @param request
* @return
*/
@RequestMapping(value = "trans", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
@ResponseBody
public String trans(HttpServletRequest request, final PayModelVo payModelVo, final String addressId, final String cartIds, String mergeId, final String isDispatch, final String isMerge, final String logisticsId, String ProductList) {
log.debug("开始支付宝调用");
ResultModel resultModel = new ResultModel();
try {
String token = request.getHeader("token");
String money = payModelVo.getMoney(); /// 变动金额
String type = payModelVo.getType(); // 消费类型 用户充值 用户提现 商品出售 会员购买会员升级 询价单购买 待支付订单的支付(此类支付不需要预订单了)
String out_trade_no = payModelVo.getOutTradeNo();
//回调地址
payModelVo.setToken(token);
payModelVo.setNotifyUrl(AliPayConfig.NOTIFY_URL);
payModelVo.setBody("支付用途");
payModelVo.setMoney(money);
//将payModelVo中所有信息存入redis
RedisTool.set("notify_"+out_trade_no,JSON.toJSONString(payModelVo));
//返回支付宝请求
Product product = new Product();
String subject = "支付宝支付";
product.setOutTradeNo(payModelVo.getOutTradeNo());
product.setSubject(subject);
product.setTotalAmount(payModelVo.getMoney());
product.setNotifyUrl(AliPayConfig.NOTIFY_URL);
String orderString = aliPayService.aliPayMobile(product);
resultModel = new ResultModel(200,"10000","执行成功");
Map<String,String> map = new HashMap<>();
map.put("orderString",orderString);
resultModel.setResult(map);
} catch (Exception e) {
log.error("支付宝支付异常", e);
e.printStackTrace();
resultModel.setError(500);
resultModel.setErrorCode("10008");
resultModel.setMsg("预下单异常");
}
log.info("支付宝支付app:" + JsonUtil.toJson(resultModel));
return JsonUtil.toJson(resultModel);
}
支付宝支付实现类
aliPayService.aliPayMobile
public String aliPayMobile(Product product) {
//实例化客户端
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("支付宝支付");
model.setOutTradeNo(product.getOutTradeNo()); //商户订单id
model.setTotalAmount(product.getTotalAmount());// 订单金额:元
model.setSubject(product.getSubject());// 订单标题
model.setTimeoutExpress("30m");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setEnablePayChannels("moneyFund,debitCardExpress");
request.setBizModel(model);
request.setNotifyUrl(product.getNotifyUrl());
String orderString="";
try {
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = AliPayConfig.getAlipayClient().sdkExecute(request);
orderString=response.getBody();
logger.info("orderString", orderString);//就是orderString 可以直接给客户端请求,无需再做处理。
} catch (AlipayApiException e) {
logger.error("支付宝构造表单失败", e);
}
return orderString;
}
APP调用预下单之后可以直接拿到字符串去进行支付,支付完成之后 支付宝会进行支付成功的回调 一般我们的业务都是在成功的回调中去进行编写的
支付回调 ,当然 如果要是想测试回调 在本机的情况下 你需要外网的映射 这些东西网上不少 但是一般都是需要花钱的 一般也不贵 我用的net123 ,30块钱用一年了都
支付回调
/**
* @Title: notify
* @Description: (充值支付宝支付回调)
* @param request
* @param response
* @throws Exception
*/
@Transactional(readOnly = false)
@RequestMapping(value = "alipay_notify", produces = "application/json;charset=UTF-8")
public void alipay_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("支付宝支付回调!");
String message = "failure";
String outtradeno = "";
//如果失败,应存入redis,便于出问题时候查看原因(默认保存两天)
try {
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
boolean signVerified = AlipaySignature.rsaCheckV1(params, AliPayConfig.alipay_public_key,
AliPayConfig.charset, AliPayConfig.sign_type); // 调用SDK验证签名
outtradeno = params.get("out_trade_no");
//将支付数据存入redis中 使用out_tran_no与支付前缀来进行区分
PayModelVo paymodel = (PayModelVo) new Gson().fromJson(RedisTool.get("notify_" + outtradeno), PayModelVo.class);
if (signVerified) {//支付宝验证签名成功
// 若参数中的appid和填入的appid不相同,则为异常通知
if (!AliPayConfig.app_id.equals(params.get("app_id"))) {
log.info("与付款时的appid不同,此为异常通知,应忽略!");
RedisTool.setex("notify_" + outtradeno, DateTools.getDatelinetoInt(),"与付款时的appid不同");
} else {
String receiptAmount = params.get("receipt_amount");
String status = params.get("trade_status");
if (status.equals("TRADE_SUCCESS") || status.equals("TRADE_FINISHED")) { // 如果状态是已经支付成功
/**
* 需要判断redis中是否有out_trade_no
* 如果没有,说明有两种情况:一种是数据出错(包括等待时间过长,redis自动清除),一种是已经回调过一次(默认成功完成后删除redis),需要去数据库查询单子状态,进一步确认
* 如果有,那就正常执行业务
*/
//redis中没有out_trade_no
if (!RedisTool.exists("notify_" + outtradeno)) {
}else{
RedisTool.del("notify_" + outtradeno);
// 校验金额
if(!receiptAmount.equals(paymodel.getMoney())){
//金额数目不对
return;
}
ResultModel resultModel = new ResultModel();
/**
* TODO 业务处理
*/
// payService.business(paymodel,resultModel);
if(resultModel.getError()==200){
message="success";
if(paymodel.getPaytype().equals("7")){//通联微信购买,需要向pc推送
try {
// WxPayController.goeasy(paymodel,"true");//GoEasy通知前端
} catch (Exception e) {
e.printStackTrace();
}
}
}else{
log.info("notify_" + outtradeno+":业务处理失败!");
RedisTool.setex("notify_" + outtradeno, DateTools.getDatelinetoInt(),"业务处理失败");
}
}
} else{
log.info("notify_" + outtradeno+":支付宝回调failure");
RedisTool.setex("notify_" + outtradeno, DateTools.getDatelinetoInt(),"支付宝处理出错");
}
}
}else { // 如果验证签名没有通过
log.info("notify_" + outtradeno+":验证签名失败!");
RedisTool.setex("notify_" + outtradeno,DateTools.getDatelinetoInt(),"验证签名失败");
}
} catch (Exception e) {
log.error("notify_" + outtradeno+"alipay_notify出现异常", e);
RedisTool.setex("notify_" + outtradeno, 1,"其他异常");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} finally {
writeMessageToResponse(response, message);
}
}
返回给支付宝是否成功的信息
/**
* @Title: writeMessageToResponse
* @Description: (向支付宝发起支付结果通知)
* @param response
* @param message
*/
protected void writeMessageToResponse(HttpServletResponse response, String message) {
PrintWriter writer = null;
try {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
writer = response.getWriter();
writer.print(message);
writer.flush();
} catch (IOException e) {
log.error("io出现异常", e);
} finally {
if (writer != null)
writer.close();
}
}
支付宝的回调 最关键的就是验证 其实你看那么多代码 几乎全部都是验证用的 首先取出参数 进行签名 验证支付宝的签名的合法性 这个是用来保证支付的安全性 之后就是验证 是否是二次回调 当你的业务没有处理成功的时候 你没有给支付宝返回正确的返回值 支付宝会进行多次的回调的 如果是二次回调 你需要直接略过 最后是验证你的支付金额和卖出产品的金额是否一致 这个很重要 比如前台直接将加个修改成0.01那直接支付你不去验证 那业务也是可以成立的 回调依然后进行
好的 支付宝APP支付的业务就只有这些了 下一篇是支付宝的
PC支付 :PC支付
有什么不对的地方请直接评论 谢谢