支付宝当面付扫码支付功能详解
前言: 上篇呢主要是针对微信验证登录做了讲解,当然微信也是提供了很多的接口来供开发者进行调用,同样,微信也有支付,相信小伙伴们学习了上篇的登录之后,已经能够融汇贯通,做出微信的支付功能。那么本篇呢就讲解一下支付宝的支付功能,同样的,通过这一个例子,你就能使用支付宝其它的功能,还是那句老话,就当做是一个敲门砖吧,好了,下面就开始吧。
本篇为原创,转载请标出处:http://www.cnblogs.com/gudu1/p/8094197.html
微信有测试公众号测试,那么支付宝呢?他也有,不过名字是叫做支付宝沙箱环境,地址:https://sandbox.alipaydev.com/sms/receive.htm ,扫码登陆之后:
>> APPID,支付宝网关,以及应用网关 这些呢是固定不变的。
>> 我们已经知道微信使用的是 SHA-1 加密,那么支付宝使用的就是RSA 和 RSA2 加密,当然了,支付宝推荐使用RSA2加密,两个的区别就是RSA2 是2048 位,RSA 是1024位,所以RSA2加密更好,我们就使用它就好。
>> 授权回调地址就是用户扫码进行支付之后,支付宝服务器回调我们程序接口的地址,规则呢跟微信的是一样的,这里不多讲了,不明白的请看一下上篇微信验证登录的讲解 http://www.cnblogs.com/gudu1/p/8087130.html。
>> 接下来的就配置一下我们的密匙,使用哪个加密方式就配置哪个就好了,支付宝很周到,给我们提供了生成密匙的工具,下载位置:https://docs.open.alipay.com/291/105971 ,这个也是支付宝的官方文档,具体怎么使用,里面都很详细,这里就不占篇幅了,然后生成之后,就直接 copy 进去配置就好了,很简单的。
下面还是老样子,先贴出代码,然后讲解代码中的一些点,支付宝也提供了Java 、PHP、.NET 版本的支付代码,这点是非常到位的,所以我们只是站在巨人的肩膀上,前人栽树,后人乘凉,下载地址:https://docs.open.alipay.com/194/105201/ ,下载我们都会,下载之后把代码copy 出来,需要使用哪个功能就copy 哪个,因为我这里使用的支付宝生成二维码预下单的功能,然后:
Controller
@Controller
@RequestMapping("/order/")
public class OrderController {
@RequestMapping("pay.do")
@ResponseBody
private ServerResponse<Map<String, String>> pay(HttpSession session, Long orderNo, HttpServletRequest request) {
Integer userId = ((User) session.getAttribute(Const.CURRENT_USER)).getId();
String path = request.getSession().getServletContext().getRealPath(PropertiesUtil.getProperty("upload_image_path"));
if (orderNo == null || orderNo == 0L) {
return ServerResponse.createByErrorMessage("支付订单不能为空");
}
return orderService.pay(userId, orderNo, path);
}
}
Service 的pay 方法
@Service("iOrderService")
public class OrderServiceImpl implements IOrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
// 支付宝当面付2.0服务
private static AlipayTradeService tradeService;
static {
/** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
* Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
*/
Configs.init("zfbinfo.properties");
/** 使用Configs提供的默认参数
* AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
*/
tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();
}
/**
* 支付宝预创建支付订单,生成支付的二维码用户用户扫码支付
*
* @param userId 用户ID
* @param orderNo 订单号
* @param path 本地上传图片地址
* @return
*/
@Transactional
public ServerResponse pay(Integer userId, Long orderNo, String path) {
Map<String, String> mapResult = Maps.newHashMap();
Order order = orderMapper.selectOrderByOrderNo(orderNo);
if (order == null) {
return ServerResponse.createBySuccessMessage("该订单不存在");
}
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
String outTradeNo = order.getOrderNo().toString();
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
String subject = "Happy_mmall 扫码付款";
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = order.getPayment().toString();
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "0";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
String body = new StringBuffer().append("订单:").append(order.getOrderNo()).append(",共花费").append(order.getPayment()).append("元").toString();
// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId("2088100200300400500");
// 支付超时,定义为120分钟
String timeoutExpress = "120m";
// 商品明细列表,需填写购买商品详细信息,
List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
// 添加 用户 预支付的订单中的商品
List<OrderItem> orderItemList = orderItemMapper.selectListByUserIdAndOrderNo(orderNo, userId);
for (OrderItem orderItem : orderItemList) {
GoodsDetail goods1 = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(),
BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(), new Double(100)).longValue(),
orderItem.getQuantity());
goodsDetailList.add(goods1);
}
// 创建扫码支付请求builder,设置请求参数
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
.setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
.setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
.setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
.setTimeoutExpress(timeoutExpress)
.setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
.setGoodsDetailList(goodsDetailList);
// 创建预支付订单对象
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
logger.info("支付宝预下单成功: )");
// 获取响应 Response
AlipayTradePrecreateResponse response = result.getResponse();
// 简单打印一下日志
dumpResponse(response);
// 创建本地上传图片的文件夹,不存在则创建
File folder = new File(path);
if (!folder.exists()) {
folder.setWritable(true);
folder.mkdirs();
}
// 需要修改为运行机器上的路径,
String filePath = String.format(path + "/qr-%s.png",
response.getOutTradeNo());
// %s 是一种占位符,即后面的response.getOutTradeNo() ,只是生成额随机字符串,防止重名
String fileName = String.format("/qr-%s.png", response.getOutTradeNo());
logger.info("filePath:" + filePath);
// 上传到本地服务器
ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath);
File targetFile = new File(path, fileName);
try {
// 上传图片到FTP服务器,上传FTP完毕之后,删除本地存储的图片
FTPUtil.upload(Lists.newArrayList(targetFile));
} catch (IOException e) {
logger.error("上传二维码失败", e);
e.printStackTrace();
}
// 刚刚上传到FTP的图片地址URL
String qrPathUrl = PathUtil.getFTPImgPath(targetFile.getName());
mapResult.put("qrPath", qrPathUrl);
mapResult.put("orderNo", orderNo.toString());
return ServerResponse.createBySuccess(mapResult);
case FAILED:
logger.error("支付宝预下单失败!!!");
return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");
case UNKNOWN:
logger.error("系统异常,预下单状态未知!!!");
return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");
default:
logger.error("不支持的交易状态,交易返回异常!!!");
return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
}
}
}
>> 在此之前呢不要忘记添加支付宝的集成依赖jar包。
>> 为了方便理解,代码中的每一步都添加了注释,代码比较多,但是大多数的代码都是直接copy支付宝 提供的Demo,然后根据我们自己的业务需求修改。
>> 由于篇幅问题,请注意静态代码块中的代码,会加载一个配置文件,这个配置文件支付宝同样有提供,我们只要修改一下其中的参数值,APPID、PID(商户UID)、以及加密的公钥和私钥。
>> 我们程序支付整体的思路是这样:用户确认下单后,点击支付,然后会调用我们pay.do 这个接口,然后我们的程序在向支付宝服务器发送请求之前会添加一些参数,就是商品的订单号、收款平台信息、以及购买商品需要支付的总价格,需要修改的一处是 创建AlipayTradePrecreateRequestBuilder对象的时候,把call_back的URL修改成我们自己程序的接口,然后程序就向支付宝发送消息,因为这里支付宝集成做的特别好,只需要创建一个预支付对象,把需要的参数传进去,就可以发起预支付请求,返回二维码字节流,我们把二维码进行保存,然后展示给用户,进行扫码支付,用户扫码之后,不管是支付成功或者支付失败都会回调我们的call_back中配置的URL,进行处理。
接下来就是我们的 call_back :
@Controller
@RequestMapping("/order/")
public class OrderController {
@RequestMapping("alipay_callback.do")
@ResponseBody
private ServerResponse callBack(HttpServletRequest request) throws AlipayApiException {
// 取出支付宝回调携带的所有参数并进行转换,数组转换为字符串
Map<String, String[]> tempParams = request.getParameterMap();
// 参数存放 Map
Map<String, String> requestParams = Maps.newHashMap();
for (Iterator<String> iterator = tempParams.keySet().iterator(); iterator.hasNext(); ) {
String key = iterator.next();
String[] strs = tempParams.get(key);
String str = "";
// 这里如果数组的长度是1,说明只有一个,直接赋值就好,如果超过一个,后面加一个逗号来隔离
for (int i = 0; i < strs.length; i++) {
str = strs.length - 1 == i ? str + strs[i] : str + strs[i] + ",";
}
requestParams.put(key, str);
}
// 去除sign_type
requestParams.remove("sign_type");
try {
// 验证签名
boolean result = AlipaySignature.rsaCheckV2(requestParams, Configs.getPublicKey(), "utf-8", Configs.getSignType());
if (!result) {
return ServerResponse.createByErrorMessage("非法请求,再恶意请求我就报警找网警了");
}
} catch (AlipayApiException e) {
logger.error("支付宝回调验证异常", e);
e.printStackTrace();
throw e;
}
// 调用Service 方法进行处理
ServerResponse serverResponse = orderService.alipayCallBack(requestParams);
if (!serverResponse.isSuccess()) {
logger.error("OrderController.callBack()","数据操作失败");
return ServerResponse.createBySuccess(Const.AlipayCallback.RESPONSE_FAILED);
}
logger.info("支付宝支付回调完成,没有异常");
return ServerResponse.createBySuccess(Const.AlipayCallback.RESPONSE_SUCCESS);
}
}
>> 这个支付宝回调URL是这样:http://smyang.s1.natapp.cc/order/alipay_callback.do,对应我们的Controller 的RequestMapping中的路由,代码中都有添加注释,还是很清晰的。
>> 支付宝的验证签名的规则是怎样的呢?在通知返回的参数中除了sign_type和sign,其余的都是待验签的参数,详细请看 https://docs.open.alipay.com/194/103296/ ,但是这里只remove 掉了sign_type,通过查看源码发现,在支付宝集成代码中需要获取一下sign,然后它才remove 掉了sign,所以这里我们只需要remove掉sign_type就好,而且是必须的。
>> 在 rsaCheckV2() 方法中我们加入的参数sign_type,指定了使用哪种加密方式来验签,如果通过就确定这个支付过程是安全的,同时 Configs 这个类也是支付宝给我们提供的,很到位的吧!
>> 最后调用了Service方法进行我们的代码逻辑,比如:更新数据库中用户的支付状态,这个就是我们自己的业务需求了,在我们的业务代码中主要是通过支付宝回调参数中的 tradeStatus 这个字段来判断用户是否支付成功,具体的tradeStatus的状态,可以自行查看支付宝官方文档。
当然,支付宝不止这一种支付方式,另外还有好多,可以自行去查看,官方文档才是我们最好的老师。好了,到这里就先结束了,后续如果有不足的地方再另行修改。
The End。。。。。