商品秒杀系统-支付模块的开发【https://github.com/XCXCXCXCX/KillSystem】
一、沙箱环境配置
我使用的是测试沙箱环境,使用沙箱测试版的支付宝进行测试,如果需要上线部署,要申请接入哦
APPID
阿里提供的,后面流程中需要把这个APPID复制粘贴到项目配置中
支付宝网关
直接使用图中测试环境的网关地址
RSA2/RSA密钥
这里我使用的是RSA2密钥,需要配置RSA2该栏目,先是生成RSA2的公钥密钥对(在阿里开发文档中有提供生成密钥对的工具,下载后按照文档生成密钥对并进行后续配置,这里我直接贴链接,就不照搬操作了)
用来验证开发者身份
应用网关
填入如上支付宝网关
授权回调地址
填入项目中对外暴露的alipayCallback.do接口
ps:这个接口地址一定要提供能在外网环境下能访问的地址
AES密钥
直接点生成就可以了,AES是对称加密算法,用于加密报文
二、引入所需jar包及alipaydemo的整合
pom.xml
<!-- alipay -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
</dependency>
另外还需要引入其他jar包,可以在我的git项目中找到,或者直接在开发文档中下载DEMO,DEMO中有这些包
整合时,先将demo中的java代码和zfbinfo.properties复制到自己开发项目中,然后将zfbinfo.properties修改成应用自己项目的配置:
# 支付宝网关名、partnerId和appId
# 前面沙箱配置中的支付宝网关
open_api_domain = https://openapi.alipaydev.com/gateway.do
mcloud_api_domain = http://mcloudmonitor.com/gateway.do
# 前面沙箱配置中的商户uid
pid = 2088102175224611
# 前面沙箱配置中的appid
appid = 2016091200490386
# RSA私钥、公钥和支付宝公钥
# 前面生成RSA2密钥对的私钥,我贴出来隐藏一下我的私钥0.0
private_key = *************************************************************************************************
# 前面生成RSA2密钥对的公钥
public_key = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkVHcHYP+6H+b4Md0desMNG0akCh1yMbLlR3OWjXG292gL4lfQLhjxs7tDw+6anLC+wBl/0TEV5F2McxgLxH5AGMkc3f4wA1/lNm85NxHEdtWlZ36cCSFb5eznBXwiFfWBXerT/YE9da6T34KS1cc5aEq0OJGRZ9o8rOr4+lwXbPETarWIqDT0kJqVmGeEZsa+FMn9ZBesGbenV2qEpvAE5Q8QP6GQTFeVEVlUbDc2CDocF+xFsjPFQq13zTNEm7MmVyrEWGvNp0bBEJsbkh9/FZfCbo1lzYSVGS+U6UPjccimNYYJOMcIgP6EtKpNb0HB/nSsgf8TZfCBgUaQ6U3ZwIDAQAB
#SHA1withRsa对应支付宝公钥
#alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB
#SHA256withRsa对应支付宝公钥
# 前面沙箱配置中的支付宝公钥,点击RSA2栏目的{查看支付宝公钥}查看,不要与前面的public_key弄混淆了
alipay_public_key =MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx4WxgxuWD7mHzv3fN2cVrXw6SOwyIsK2f6lSD4z1buO97HhFla7rQIIxfTmiR6B6ygVDRDN7T3HzAPH1s1wYp4tXUBdhcDoZlydNIrGDeJ8PeW96TcaLPjQgnZNFkUpNAlHULi0S3fBfuPBB/w9WM15O6r4CasyOyckJ3/v/fHFaTyxD52Ym6x94Obs2HrxJ6OW+WQp77192Dk9h+VCvNtbyVH00XDUiWveCv2yIa5ZbS0o+713O4irp7KTsAxXfGUQohYfO1QtF1e2KTCon/7gCFQCUGV++HM0UINJFfPAgNf6hu7mdFqk95u/SqkEDsmyHnnVdmakLHsxQy8yZcwIDAQAB
# 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa
sign_type = RSA2
# 当面付最大查询次数和查询间隔(毫秒)
max_query_retry = 5
query_duration = 5000
# 当面付最大撤销次数和撤销间隔(毫秒)
max_cancel_retry = 3
cancel_duration = 2000
# 交易保障线程第一次调度延迟和调度间隔(秒)
heartbeat_delay = 5
heartbeat_duration = 900
然后就可以按照api调用使用了,我只使用了二维码当面付的功能,后续慢慢介绍
三、支付模块的开发
1.前台web页面代码
(1)页面效果介绍
用户创建订单成功后,进入alipay.jsp页面,如图:
预支付成功后,页面如图:
支付成功后,直接返回“支付成功”页面,这个页面太丑了就不贴图了。。
(2)jsp文件代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
<script type="text/javascript">
$(function(){
$("#qrCodeDiv").hide();
window.setTimeout(toPay,3000);
});
function toPay(){
var order_id = $("#order_id").val();
var tel_num = $("#tel_num").val();
var address = $("#address").val();
var goods_id = $("#goods_id").val();
$.post("/KillSystem/pay.do", { order_id: order_id, tel_num: tel_num, address: address, goods_id: goods_id },
function (response) {
alert(response.msg);
if (response.status == "0") {
$("#qrCode").attr("src",response.data.qrUrl);
$("#qrCodeDiv").show();
window.setInterval(getPayState,5000);
window.setTimeout(back,36000);
}else{
alert('生成二维码失败!');
}
}, 'json');
}
function getPayState(){
var order_id = $("#order_id").val();
var tel_num = $("#tel_num").val();
var address = $("#address").val();
var goods_id = $("#goods_id").val();
$.post("/KillSystem/payIsSuccess.do", { order_id: order_id, tel_num: tel_num, address: address, goods_id: goods_id },
function (response) {
if (response.status == "0") {
if (response.data == true){
window.location.href="/paySuccess.html";
}
}
}, 'json');
}
</script>
<title>alipay</title>
</head>
<body>
5秒后自动跳转,支付宝正在努力的生成二维码...
<input id="order_id" type="hidden" name="order_id" value="${order.getOrder_id()}" />
<input id="tel_num" type="hidden" name="tel_num" value="${order.getTel_num()}" />
<input id="address" type="hidden" name="address" value="${order.getAddress()}" />
<input id="goods_id" type="hidden" name="goods_id" value="${order.getGoods_id()}" />
<div id="qrCodeDiv" class="align-center">
<img id="qrCode" class="top" src="" height="400" width="400" />
二维码5分钟后失效,支付成功后自动跳转...
</div>
</body>
</html>
(3)所需后台服务接口
pay.do
发起支付宝预支付,向支付宝发起支付请求,支付宝处理成功后,返回一个用于支付的二维码
alipayCallback.do
当二维码扫描后支付成功,支付宝会回调该接口,用于处理1.订单信息的验证2.支付信息的更新,结合项目需求实现代码
payIsSuccess.do
前台轮询调用该接口,当订单支付成功,跳转至“支付成功”页面。
2.后台开发
(1)controller层
OrderController.java
@RequestMapping("/pay.do")
@ResponseBody
public ServerResponse pay(Order order,HttpServletRequest request,HttpSession session) {
if (session.getAttribute("tel_num") == null || session.getAttribute("passwd") == null) {
return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户登录已过期");
}
//hasPay = true;
if(!orderService.orderIsExistInRedis(order)) {
//撤回库存减一的操作
goodsService.incrGoodsStock(order);
return ServerResponse.createByErrorMessage(order.getOrder_id() + "订单已失效,请重新创建订单");
}
//todo
//1.在redis中创建支付订单,若失败,返回“订单已存在”
if(orderService.createPayInRedis(order)==0) {
return ServerResponse.createByErrorMessage(order.getOrder_id() + "_pay订单已存在");
}
//2.收到支付宝回调后,在redis中更新支付订单,若失败,返回“订单支付失败,取消支付并删除订单和删除订单支付”
String path = request.getSession().getServletContext().getRealPath("upload");
return orderService.pay(order,path);
}
@RequestMapping("/alipayCallback.do")
@ResponseBody
public Object alipayCallback(HttpServletRequest request) {
//todo
//在request中取到支付宝回调的params
Map<String,String> params = Maps.newHashMap();
Map requestParams = request.getParameterMap();
for(Iterator 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]+",";
}
params.put(name,valueStr);
}
log.info("支付宝回调,sign:{},trade_status:{},参数:{}",params.get("sign"),params.get("trade_status"),params.toString());
//由于使用的是RSA2,看源码可以知道默认是使用RSA的,所以这里要进行处理,使用RSA2
params.remove("sign_type");
try {
boolean alipayRSACheckedV2 = AlipaySignature.rsaCheckV2(params, Configs.getAlipayPublicKey(),"utf-8",Configs.getSignType());
if(!alipayRSACheckedV2){
return ServerResponse.createByErrorMessage("非法请求,验证不通过,再恶意请求我就报警找网警了");
}
} catch (AlipayApiException e) {
log.error("支付宝验证回调异常",e);
}
ServerResponse serverResponse = orderService.aliCallback(params);
if(serverResponse.isSuccess()){
return "success";
}
return "failed";
}
@RequestMapping("/payIsSuccess.do")
@ResponseBody
public ServerResponse payIsSuccess(Order order,HttpServletRequest request,HttpSession session) {
if (session.getAttribute("tel_num") == null || session.getAttribute("passwd") == null) {
return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户登录已过期");
}
//查询该订单是否支付成功:在redis中查询key{订单号+pay}的value值是否为1,若为1,返回支付成功,若为0,返回未支付。
String flag = orderService.getPayState(order);
if(flag == null) {
return ServerResponse.createByErrorMessage("该支付订单不存在!");
}
boolean data = ("0".equals(flag) ? false : true);
return ServerResponse.createBySuccess("订单状态:data",data);
}
(2)service层
接口类
public interface OrderService extends BaseService<Order>{
int updateOrderState(Order order);
PageInfo<Map<String,Order>> select(Order order,int pageNum,int pageSize);
long createOrderInRedis(Order order);
long createPayInRedis(Order order);
String updateOrderPayInRedis(Order order);
boolean orderIsExist(Order order);
boolean orderIsExistInRedis(Order order);
String getPayState(Order order);
ServerResponse pay(Order order,String path);
ServerResponse aliCallback(Map<String, String> params);
}
实现类相关方法
//获取订单是否支付
@Override
public String getPayState(Order order) {
String flag = orderDao.getPayState(order);
return flag;
}
//在demo中的Main.java中直接复制粘贴过来的
//按照注释修改成自己的业务所适应的代码
@Override
public ServerResponse pay(Order order,String path) {
Map<String ,String> resultMap = Maps.newHashMap();
resultMap.put("orderNo",order.getOrder_id());
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
String outTradeNo = order.getOrder_id();
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店消费”
String subject = new StringBuilder().append("KillSystemx系统当面付消费,订单号:").append(outTradeNo).toString();
// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
Goods goods = new Goods();
goods.setGoods_id(order.getGoods_id());
goods = goodsDao.getGoodsById(goods);
String totalAmount = String.valueOf(goods.getGoods_price());
// (必填) 付款条码,用户支付宝钱包手机app点击“付款”产生的付款条码
String authCode = "用户自己的支付宝付款码"; // 条码示例,286648048691290423
// (可选,根据需要决定是否使用) 订单可打折金额,可以配合商家平台配置折扣活动,如果订单部分商品参与打折,可以将部分商品总价填写至此字段,默认全部商品可打折
// 如果该值未传入,但传入了【订单总金额】,【不可打折金额】 则该值默认为【订单总金额】- 【不可打折金额】
// String discountableAmount = "1.00"; //
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "0.0";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品3件共20.00元"
String body = new StringBuilder().append("购买商品1件共").append(totalAmount).append("元").toString();
// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId("2088100200300400500");
// 支付超时,线下扫码交易定义为5分钟
String timeoutExpress = "5m";
// 商品明细列表,需填写购买商品详细信息,
List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
// 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
GoodsDetail goods1 = GoodsDetail.newInstance(String.valueOf(goods.getGoods_id()), goods.getGoods_name(), Integer.valueOf(totalAmount), 1);
// 创建好一个商品后添加至商品明细列表
goodsDetailList.add(goods1);
// 继续创建并添加第一条商品信息,用户购买的产品为“黑人牙刷”,单价为5.00元,购买了两件
//GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "xxx牙刷", 500, 2);
//goodsDetailList.add(goods2);
//String appAuthToken = "应用授权令牌";//根据真实值填写
// 创建扫码支付请求builder,设置请求参数
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
// .setAppAuthToken(appAuthToken)
.setOutTradeNo(outTradeNo).setSubject(subject)
.setTotalAmount(totalAmount).setStoreId(storeId)
.setUndiscountableAmount(undiscountableAmount).setBody(body).setOperatorId(operatorId)
.setExtendParams(extendParams).setSellerId(sellerId)
.setGoodsDetailList(goodsDetailList).setTimeoutExpress(timeoutExpress)
.setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"));
// 调用tradePay方法获取当面付应答
//AlipayF2FPayResult result = service.tradePay(builder);
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
log.info("支付宝预支付成功: )");
AlipayTradePrecreateResponse response = result.getResponse();
dumpResponse(response);
File folder = new File(path);
if(!folder.exists()){
folder.setWritable(true);
folder.mkdirs();
}
// 需要修改为运行机器上的路径
//细节细节细节
String qrPath = String.format(path+"/qr-%s.png",response.getOutTradeNo());
String qrFileName = String.format("qr-%s.png",response.getOutTradeNo());
ZxingUtils.getQRCodeImge(response.getQrCode(), 256, qrPath);
File targetFile = new File(path,qrFileName);
try {
FTPUtil.uploadFile(Lists.newArrayList(targetFile));
} catch (IOException e) {
log.error("上传二维码异常",e);
}
log.info("qrPath:" + qrPath);
String qrUrl = PropertiesUtil.getProperty("ftp.server.http.prefix")+targetFile.getName();
resultMap.put("qrUrl",qrUrl);
//直接测试回调中的代码
//String flag = orderDao.updateOrderPayInRedis(order);
//InitFIFOListener.queue.offer(order);
return ServerResponse.createBySuccess("支付宝预下单成功!!!",resultMap);
case FAILED:
log.error("支付宝预下单失败!!!");
//库存加一
goodsDao.setBackGoodsStock(order);
//删除订单
//删除支付订单
return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");
case UNKNOWN:
log.error("系统异常,预下单状态未知!!!");
//库存加一
goodsDao.setBackGoodsStock(order);
//删除订单
//删除支付订单
return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");
default:
log.error("不支持的交易状态,交易返回异常!!!");
//库存加一
goodsDao.setBackGoodsStock(order);
//删除订单
//删除支付订单
return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
}
}
// 简单打印应答
//在demo中的Main.java中直接复制粘贴过来的
private void dumpResponse(AlipayResponse response) {
if (response != null) {
log.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
if (StringUtils.isNotEmpty(response.getSubCode())) {
log.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
response.getSubMsg()));
}
log.info("body:" + response.getBody());
}
}
@Override
public ServerResponse aliCallback(Map<String,String> params){
log.debug("支付宝回调啦!");
//pay.do中提交过去的订单号
String orderNo = params.get("out_trade_no");
//支付宝中的订单支付状态
String tradeStatus = params.get("trade_status");
log.debug(tradeStatus);
//判断订单是否存在在本服务器
Order order = new Order();
order.setOrder_id(orderNo);
order = orderDao.selectByorderIdInRedis(order);
//在redis中查找不到订单支付信息,返回支付订单失效 ps.支付订单存在在redis中是有过期时间的
if(orderDao.getPayState(order) == null){
goodsDao.setBackGoodsStock(order);
return ServerResponse.createByErrorMessage("该支付订单已失效,请重新下单!");
}
//回调时发现本机服务器上的订单支付状态已经是支付成功状态了,返回支付宝重复调用,但是是成功的回调
if("1".equals(orderDao.getPayState(order))){
return ServerResponse.createBySuccessMessage("支付宝重复调用");
}
//当回调订单状态是“TRADE_SUCCESS”时,返回订单支付成功
if("TRADE_SUCCESS".equals(tradeStatus)){
String flag = orderDao.updateOrderPayInRedis(order);
InitFIFOListener.queue.offer(order);
return ServerResponse.createBySuccessMessage("订单支付成功");
}else {
return ServerResponse.createByErrorMessage("订单支付失败");
}
(3)dao层相关代码
接口类
public interface OrderDao extends BaseDao<Order>{
int updateOrderState(Order order);
long createOrderInRedis(Order order);
long createPayInRedis(Order order);
String updateOrderPayInRedis(Order order);
boolean orderIsExist(Order order);
boolean orderIsExistInRedis(Order order);
String getPayState(Order order);
int createOrder(Order order);
Order selectByorderIdInRedis(Order order);
boolean createOrderAndupdateGoodsStock(Order order, Goods goods);
}
实现类相关方法
//redis操作:订单是否存在
@Override
public boolean orderIsExistInRedis(Order order) {
try {
jedis = JedisUtil.getConn();
return jedis.get(order.getOrder_id()) == null ? false : true;
}finally {
if(jedis == null) {
jedis.close();
}else {
JedisUtil.returnConn(jedis);
}
}
}
//redis操作:创建订单支付信息
@Override
public long createPayInRedis(Order order) {
try {
jedis = JedisUtil.getConn();
return jedis.setnx(order.getOrder_id() + "_pay", "0");
}finally {
if(jedis.expire(order.getOrder_id() + "_pay", 600) != 1) {
log.error("支付订单设置有效时间失败!");
}
if(jedis == null) {
jedis.close();
}else {
JedisUtil.returnConn(jedis);
}
}
}
//redis操作:更新订单支付信息
@Override
public String updateOrderPayInRedis(Order order) {
try {
jedis = JedisUtil.getConn();
String obj = jedis.getSet(order.getOrder_id() + "_pay", "1");
return "支付成功";
} finally {
if(jedis == null) {
jedis.close();
}else {
JedisUtil.returnConn(jedis);
}
}
}
//redis操作:查看订单支付信息
@Override
public String getPayState(Order order) {
try {
jedis = JedisUtil.getConn();
return jedis.get(order.getOrder_id() + "_pay");
}finally {
if(jedis == null) {
jedis.close();
}else {
JedisUtil.returnConn(jedis);
}
}
}
//redis操作:在redis中查询该订单号的订单信息
@Override
public Order selectByorderIdInRedis(Order order) {
// TODO Auto-generated method stub
try {
jedis = JedisUtil.getConn();
Order obj = new Order();
String result = jedis.get(order.getOrder_id());
String[] arrayResult = result.split(",");
obj.setOrder_id(order.getOrder_id());
obj.setTel_num(arrayResult[0]);
obj.setAddress(arrayResult[1]+","+arrayResult[2]+","+arrayResult[3]);
obj.setGoods_id(Integer.parseInt(arrayResult[4]));
return obj;
}finally {
if(jedis == null) {
jedis.close();
}else {
JedisUtil.returnConn(jedis);
}
}
}
项目详细代码请进入我的gitHub下载
有问题请指出,欢迎相互学习交流