品优购17——微信扫码支付

1. 工程搭建与准备工作

1)建立支付服务接口模块pinyougou-pay-interface

2)建立支付服务实现模块pinyougou-pay-service (war) 依赖pinyougou-pay-interface 和pinyougou-common 、 spring  dubbox 相关依赖 、微信SDK

3)添加微信SDJ依赖

<dependency>
	<groupId>com.github.wxpay</groupId>
	<artifactId>wxpay-sdk</artifactId>
	<version>0.0.3</version>
</dependency>

4)在pinyougou-common工程中添加工具类HttpClient.java ,并添加依赖

<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>	  		
</dependency>

工具类

package util;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

/**
 * http请求客户端
 * 
 * @author Administrator
 * 
 */
public class HttpClient {
	private String url;
	private Map<String, String> param;
	private int statusCode;
	private String content;
	private String xmlParam;
	private boolean isHttps;

	public boolean isHttps() {
		return isHttps;
	}

	public void setHttps(boolean isHttps) {
		this.isHttps = isHttps;
	}

	public String getXmlParam() {
		return xmlParam;
	}

	public void setXmlParam(String xmlParam) {
		this.xmlParam = xmlParam;
	}

	public HttpClient(String url, Map<String, String> param) {
		this.url = url;
		this.param = param;
	}

	public HttpClient(String url) {
		this.url = url;
	}

	public void setParameter(Map<String, String> map) {
		param = map;
	}

	public void addParameter(String key, String value) {
		if (param == null)
			param = new HashMap<String, String>();
		param.put(key, value);
	}

	public void post() throws ClientProtocolException, IOException {
		HttpPost http = new HttpPost(url);
		setEntity(http);
		execute(http);
	}

	public void put() throws ClientProtocolException, IOException {
		HttpPut http = new HttpPut(url);
		setEntity(http);
		execute(http);
	}

	public void get() throws ClientProtocolException, IOException {
		if (param != null) {
			StringBuilder url = new StringBuilder(this.url);
			boolean isFirst = true;
			for (String key : param.keySet()) {
				if (isFirst)
					url.append("?");
				else
					url.append("&");
				url.append(key).append("=").append(param.get(key));
			}
			this.url = url.toString();
		}
		HttpGet http = new HttpGet(url);
		execute(http);
	}

	/**
	 * set http post,put param
	 */
	private void setEntity(HttpEntityEnclosingRequestBase http) {
		if (param != null) {
			List<NameValuePair> nvps = new LinkedList<NameValuePair>();
			for (String key : param.keySet())
				nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
			http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
		}
		if (xmlParam != null) {
			http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
		}
	}

	private void execute(HttpUriRequest http) throws ClientProtocolException,
			IOException {
		CloseableHttpClient httpClient = null;
		try {
			if (isHttps) {
				SSLContext sslContext = new SSLContextBuilder()
						.loadTrustMaterial(null, new TrustStrategy() {
							// 信任所有
							public boolean isTrusted(X509Certificate[] chain,
									String authType)
									throws CertificateException {
								return true;
							}
						}).build();
				SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
						sslContext);
				httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
						.build();
			} else {
				httpClient = HttpClients.createDefault();
			}
			CloseableHttpResponse response = httpClient.execute(http);
			try {
				if (response != null) {
					if (response.getStatusLine() != null)
						statusCode = response.getStatusLine().getStatusCode();
					HttpEntity entity = response.getEntity();
					// 响应内容
					content = EntityUtils.toString(entity, Consts.UTF_8);
				}
			} finally {
				response.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			httpClient.close();
		}
	}

	public int getStatusCode() {
		return statusCode;
	}

	public String getContent() throws ParseException, IOException {
		return content;
	}

}

添加配置文件weixinpay.properties,文件内容为

appid: 微信公众账号或开放平台APP的唯一标识
partner:财付通平台的商户账号
partnerkey:财付通平台的商户密钥
notifyurl:  回调地址

5)pinyougou-cart-web依赖工程pinyougou-pay-service 

6)将二维码插件QRious  拷贝到pinyougou-cart-web  的plugins目录中

2. 微信支付二维码生成

我们通过HttpClient工具类实现对远程支付接口的调用。
接口链接:https://api.mch.weixin.qq.com/pay/unifiedorder
具体参数参见“统一下单”API, 构建参数发送给统一下单的url ,返回的信息中有支付url,根据url生成二维码,显示的订单号和金额也在返回的信息中。

2.1 后端代码实现

2.1.1 服务层

@Service
public class WeixinPayServiceImpl implements WeixinPayService {

	@Value("${appid}")
	private String appid;
	
	@Value("${partner}")
	private String partner;
	
	@Value("${partnerkey}")
	private String partnerkey;
	
	/**
	 * 生成二维码
	 * @return
	 */
	public Map createNative(String out_trade_no,String total_fee){
		//1.创建参数
		Map<String,String> param=new HashMap();//创建参数
		param.put("appid", appid);//公众号
		param.put("mch_id", partner);//商户号
		param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串		
		param.put("body", "品优购");//商品描述
		param.put("out_trade_no", out_trade_no);//商户订单号
		param.put("total_fee",total_fee);//总金额(分)
		param.put("spbill_create_ip", "127.0.0.1");//IP
		param.put("notify_url", "http://test.itcast.cn");//回调地址(随便写)
		param.put("trade_type", "NATIVE");//交易类型
		try {
			//2.生成要发送的xml 
			String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
			System.out.println(xmlParam);	
			HttpClient client=new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
			client.setHttps(true);
			client.setXmlParam(xmlParam);
			client.post();		
			//3.获得结果 
			String result = client.getContent();
			System.out.println(result);
			Map<String, String> resultMap = WXPayUtil.xmlToMap(result);			
			Map<String, String> map=new HashMap<>();
			map.put("code_url", resultMap.get("code_url"));//支付地址
			map.put("total_fee", total_fee);//总金额
			map.put("out_trade_no",out_trade_no);//订单号
			return map;
		} catch (Exception e) {
			e.printStackTrace();
			return new HashMap<>();
		}			
	}
}

2.1.2 控制层

/**
 * 支付控制层
 * @author Administrator
 *
 */
@RestController
@RequestMapping("/pay")
public class PayController {
	@Reference
	private  WeixinPayService weixinPayService;
	
	/**
	 * 生成二维码
	 * @return
	 */
	@RequestMapping("/createNative")
	public Map createNative(){
		IdWorker idworker=new IdWorker();		
		return weixinPayService.createNative(idworker.nextId()+"","1");		
	}
}

注意:这里订单号通过分布式ID生成器生成,金额暂时写死,后续开发我们再对接业务系统得到订单号和金额

2.2 前端实现

2.2.1 服务层

在pinyougou-cart-web创建 payService.js

app.service('payService',function($http){
	//本地支付
	this.createNative=function(){
		return $http.get('pay/createNative.do');
	}	
});

2.2.2 控制层

在pinyougou-cart-web 创建payController.js

app.controller('payController' ,function($scope ,payService){	
	//本地生成二维码
	$scope.createNative=function(){
		payService.createNative().success(
			function(response){
				$scope.money=  (response.total_fee/100).toFixed(2) ;	//金额
				$scope.out_trade_no= response.out_trade_no;//订单号
				//二维码
		    	var qr = new QRious({
		 		   element:document.getElementById('qrious'),
		 		   size:250,
		 		   level:'H',
		 		   value:response.code_url
		 		});				
			}
		);		
	}		
});

2.2.3 页面

修改pay.html ,引入js

<script type="text/javascript" src="plugins/angularjs/angular.min.js">  </script>
<script type="text/javascript" src="js/base.js">  </script>
<script type="text/javascript" src="js/service/payService.js">  </script>
<script type="text/javascript" src="js/controller/payController.js">  </script>
<script type="text/javascript" src="plugins/qrious.min.js"></script>

添加指令

<body ng-app="pinyougou" ng-controller="payController" ng-init="createNative()">

设置二维码图片

<p class="red"></p>                      
         <div class="fl code">
              <img id="qrious">
              <div class="saosao">
                  <p>请使用微信扫一扫</p>
                  <p>扫描二维码支付</p>
         </div>
</div>

显示订单号

订单号:{{out_trade_no}}

显示金额

<em  class="orange money">¥{{money}}</em>元

3. 检测支付状态

我们通过HttpClient工具类实现对远程支付接口的调用。
接口链接:https://api.mch.weixin.qq.com/pay/orderquery
具体参数参见“查询订单”API, 我们在controller方法中轮询调用查询订单(间隔3秒),当返回状态为success时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。

3.1 后端代码

3.1.1 服务层

在pinyougou-pay-service的WeixinPayServiceImpl.java中实现方法 

@Override
	public Map queryPayStatus(String out_trade_no) {
		Map param=new HashMap();
		param.put("appid", appid);//公众账号ID
		param.put("mch_id", partner);//商户号
		param.put("out_trade_no", out_trade_no);//订单号
		param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
		String url="https://api.mch.weixin.qq.com/pay/orderquery";		
		try {
			String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);	
			HttpClient client=new HttpClient(url);
			client.setHttps(true);
			client.setXmlParam(xmlParam);
			client.post();
			String result = client.getContent();			
			Map<String, String> map = WXPayUtil.xmlToMap(result);
			System.out.println(map);
			return map;			
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}	
	}

3.1.2 控制层

在pinyougou-cart-web的PayController.java新增方法

/**
 * 查询支付状态
 * @param out_trade_no
 * @return
 */
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
	Result result=null;		
	while(true){
		//调用查询接口
		Map<String,String> map = weixinPayService.queryPayStatus(out_trade_no);
		if(map==null){//出错			
			result=new  Result(false, "支付出错");
			break;
		}			
		if(map.get("trade_state").equals("SUCCESS")){//如果成功				
			result=new  Result(true, "支付成功");
			break;
		}			
		try {
			Thread.sleep(3000);//间隔三秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}							
	}
	return result;
}

3.2 前端

3.2.1 服务层

在payService.js新增方法

//查询支付状态
this.queryPayStatus=function(out_trade_no){
	return $http.get('pay/queryPayStatus.do?out_trade_no='+out_trade_no);
}

3.2.2 控制层

在payController.js中新增方法 

//查询支付状态 
queryPayStatus=function(out_trade_no){
	payService.queryPayStatus(out_trade_no).success(
		function(response){
			if(response.success){
				location.href="paysuccess.html";
			}else{					
				location.href="payfail.html";								
			}				
		}
	);
}

在createNative方法的回调方法中调用此查询方法

	//本地生成二维码
	$scope.createNative=function(){
		payService.createNative().success(
			function(response){
				..........		    	
		     	queryPayStatus(response.out_trade_no);//查询支付状态				
			}
		);		
	}

4. 代码完善

4.1 查询时间限制

         如果用户到了二维码页面一直未支付,或是关掉了支付页面,我们的代码会一直循环调用微信接口,这样会对程序造成很大的压力。所以我们要加一个时间限制或是循环次数限制,当超过时间或次数时,跳出循环

1)修改pinyougou-cart-web工程PayController.java的queryPayStatus方法

@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
	Result result=null;		
	int x=0;		
	while(true){
		//调用查询接口
		.......		
		try {
			Thread.sleep(3000);//间隔三秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
		//为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
		x++;
		if(x>=100){
			result=new  Result(false, "二维码超时");
			break;
		}
	}
	return result;
}

2)修改payController.js

//查询支付状态 
queryPayStatus=function(out_trade_no){
	payService.queryPayStatus(out_trade_no).success(
		function(response){
			if(response.success){
				location.href="paysuccess.html";
			}else{
				if(response.message=='二维码超时'){
					$scope.createNative();//重新生成二维码					
				}else{
					location.href="payfail.html";
				}					
			}				
		}
	);
}

4.2 支付成功页面显示金额

1)修改payController.js  跳转页面传参

//查询支付状态 
queryPayStatus=function(out_trade_no){
	payService.queryPayStatus(out_trade_no).success(
		function(response){
			if(response.success){
				location.href="paysuccess.html#?money="+$scope.money;
			}else{
				if(response.message=='二维码超时'){
					$scope.createNative();//重新生成二维码					
				}else{
					location.href="payfail.html";
				}					
			}				
		}
	);
}

2)在payController.js中引入$location服务 ,新增方法

//获取金额
$scope.getMoney=function(){
	return $location.search()['money'];
}

3)修改页面paysuccess.html ,引入JS (与pay.html相同) ,body添加指令

ng-app="pinyougou" ng-controller="payController"

4)用表达式显示金额

<p>支付金额:¥{{getMoney()}}元</p>

5. 支付日志

实现思路:
(1)在用户下订单时,判断如果为微信支付,就想支付日志表添加一条记录,信息包括支付总金额、订单ID(多个)、用户ID 、下单时间等信息,支付状态为0(未支付)
(2)生成的支付日志对象放入redis中,以用户ID作为key,这样在生成支付二维码时就可以从redis中提取支付日志对象中的金额和订单号。
(3)当用户支付成功后,修改支付日志的支付状态为1(已支付),并记录微信传递给我们的交易流水号。根据订单ID(多个)修改订单的状态为2(已付款)。

5.1 表结构

tb_paylog  支付日志表

字段

类型

长度

含义

out_trade_no

varchar

30

支付订单号

create_time

datatime

 

创建时间

pay_time

datatime

 

支付完成时间

total_fee

bigint

 

支付金额(分)

transaction_id

varchar

30

交易流水号

trade_state

varchar

1

交易状态

pay_type

varchar

1

支付类型:

1:微信

2:支付宝

3:网银

order_list

varchar

200

订单表ID串,用逗号分隔

5.2 插入日志记录

修改pinyougou-order-service工程OrderServiceImpl.java 的add方法。
内容:判断如果支付方式为微信支付,向数据库插入支付日志记录,并放入redis存储
 

@Autowired
private TbPayLogMapper payLogMapper;
/**
 * 增加
 */
public void add(TbOrder order) {
	List<Cart> cartList = (List<Cart>) 
			redisTemplate.boundHashOps("cartList").get( order.getUserId() );
	List<String> orderIdList=new ArrayList();//订单ID列表
	double total_money=0;//总金额 (元)
	for(Cart cart:cartList){
		long orderId = idWorker.nextId();
      ......			
		orderIdList.add(orderId+"");//添加到订单列表	
		total_money+=money;//累加到总金额 
	}
	if("1".equals(order.getPaymentType())){//如果是微信支付		
		TbPayLog payLog=new TbPayLog();
		String outTradeNo=  idWorker.nextId()+"";//支付订单号
		payLog.setOutTradeNo(outTradeNo);//支付订单号
		payLog.setCreateTime(new Date());//创建时间
		//订单号列表,逗号分隔
		String ids=orderIdList.toString().replace("[", "").replace("]", "").replace(" ", "");
		payLog.setOrderList(ids);//订单号列表,逗号分隔
		payLog.setPayType("1");//支付类型
		payLog.setTotalFee( (long)(total_money*100 ) );//总金额(分)
		payLog.setTradeState("0");//支付状态
		payLog.setUserId(order.getUserId());//用户ID			
		payLogMapper.insert(payLog);//插入到支付日志表			
		redisTemplate.boundHashOps("payLog").put(order.getUserId(), payLog);//放入缓存			
	}		
	redisTemplate.boundHashOps("cartList").delete(order.getUserId());		
}

5.2 读取支付日志

5.2.1 服务层

pinyougou-order-service的OrderServiceImpl.java实现方法

@Override
public TbPayLog searchPayLogFromRedis(String userId) {
	return (TbPayLog) redisTemplate.boundHashOps("payLog").get(userId);		
}

5.2.2 控制层

修改pinyougou-cart-web工程PayController.java的createNative方法
实现思路:调用获取支付日志对象的方法,得到订单号和金额
 

@Reference
private OrderService orderService;
/**
 * 生成二维码
 * @return
 */
@RequestMapping("/createNative")
public Map createNative(){
	//获取当前用户		
	String userId=SecurityContextHolder.getContext().getAuthentication().getName();
	//到redis查询支付日志
	TbPayLog payLog = orderService.searchPayLogFromRedis(userId);
	//判断支付日志存在
	if(payLog!=null){
		return weixinPayService.createNative(payLog.getOutTradeNo(),payLog.getTotalFee()+"");
	}else{
		return new HashMap();
	}		
}

5.3 修改订单状态

5.3.1 服务层

在pinyougou-order-service工程OrderServiceImpl.java实现该方法.
这个方法主要做三件事:
1. 修改支付日志状态
2. 修改关联的订单的状态
3. 清除缓存中的支付日志对象
 

@Override
public void updateOrderStatus(String out_trade_no, String transaction_id) {
	//1.修改支付日志状态
	TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
	payLog.setPayTime(new Date());
	payLog.setTradeState("1");//已支付
	payLog.setTransactionId(transaction_id);//交易号
	payLogMapper.updateByPrimaryKey(payLog);		
	//2.修改订单状态
	String orderList = payLog.getOrderList();//获取订单号列表
	String[] orderIds = orderList.split(",");//获取订单号数组
	
	for(String orderId:orderIds){
		TbOrder order = orderMapper.selectByPrimaryKey( Long.parseLong(orderId) );
		if(order!=null){
			order.setStatus("2");//已付款
			orderMapper.updateByPrimaryKey(order);
		}			
	}
	//清除redis缓存数据		
	redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}

5.3.2 控制层

修改pinyougou-cart-web的PayController.java。在微信支付接口有成功返回状态时,调用修改状态的方法

/**
 * 查询支付状态
 * @param out_trade_no
 * @return
 */
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
	Result result=null;		
	int x=0;		
	while(true){
		//调用查询接口
		Map<String,String> map = weixinPayService.queryPayStatus(out_trade_no);
		if(map==null){//出错			
			result=new  Result(false, "支付出错");
			break;
		}			
		if(map.get("trade_state").equals("SUCCESS")){//如果成功				
			result=new  Result(true, "支付成功");
			//修改订单状态
			orderService.updateOrderStatus(out_trade_no, map.get("transaction_id"));
			break;
		}			
		try {
			Thread.sleep(3000);//间隔三秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
		//为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
	     ......			
	}
	return result;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值