目录
支付主要开发流程:
第一步:准备数据(商户号、商户密钥、appid、小程序密钥、商户证书密钥等)。准备基于httpclient的http请求工具类HttpUtils。准备支付API对应的SDK。
第二步:编写controller,接收前端发送过来的openid、支付金额等信息。
第三步:编写service,将琐碎的参数拼接加密,为调用支付统一下单API做准备,也为获取签名paySign做准备。
第四步:将主备好的参数传给小程序,小程序调用requestPayment进行支付业务
一、准备数据、工具类和SDK
1.先编写WechatConstant
Tip:就是先将开发小程序大概率要用到的信息封装到一个类里。也不知道这个说法对不对,就是为了用着舒
public class WechatConstant {
public final static String MCH_ID = "****************"; //商户号
public final static String MCH_KEY = "***************"; //商户密钥
public final static String APPID = "*****************"; //appid
public final static String SECRET = "****************"; //小程序密钥
public final static String KEY = "*******************"; //商户证书密钥
}
2.编写请求工具类HttpUtils
Tip:这个百度有很多,比我的漂亮优雅的。。
public class HttpUtil {
private static final String Charset = "utf-8";
/**
* 发送请求,如果失败,会返回null
* @param url
* @param map
* @return
*/
public static String post(String url, Map<String, String> map) {
// 处理请求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
// 添加参数
List<NameValuePair> params = new ArrayList<NameValuePair>();
for (String str : map.keySet()) {
params.add(new BasicNameValuePair(str, map.get(str)));
}
post.setEntity(new UrlEncodedFormEntity(params, Charset));
// 执行请求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 处理请求结果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
if (in != null)
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e1) {
e1.printStackTrace();
}
return null;
}
/**
* 发送请求,如果失败会返回null
* @param url
* @param str
* @return
*/
public static String post(String url, String str) {
// 处理请求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str, Charset));
// 执行请求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 处理请求结果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8"));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
// 关闭流
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 发送GET方式的请求,并返回结果字符串。
* <br>
* 时间:2017年2月27日,作者:http://wallimn.iteye.com
* @param url
* @return 如果失败,返回为null
*/
public static String get(String url) {
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpGet get = new HttpGet(uri);
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String httpsRequest(String requestUrl, String requestMethod,String output){
try {
URL url = new URL(requestUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestMethod(requestMethod);
if (null != output){
OutputStream outputStream = connection.getOutputStream();
outputStream.write(output.getBytes("UTF-8"));
outputStream.close();
}
//从输出流返回内容
InputStream inputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null){
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
connection.disconnect();
return buffer.toString();
}catch (Exception e){
e.printStackTrace();
}
return "";
}
}
3.导入API对应的SDK
Tip:这个听说好像是可以导maven坐标,不知道是不是一个东西,我没有研究,那种应该比这个厉害-_-
下载地址:https://pay.weixin.qq.com/wiki/doc/api/micropay_sl.php?chapter=11_1。
二、编写controller
1.先获取openid给小程序
@ApiOperation("登录获取openid")
@RequestMapping(value = "/loginOpenid", method = {RequestMethod.GET})
public Map<String, Object> longin(@RequestParam("code") String code) {
String url = this.requestUrl + "?appid=" + WechatConstant.APPID + "&secret=" + WechatConstant.SECRET + "&js_code=" + code + "&grant_type=" + grantType;
// 发送请求
String data = HttpUtil.get(url);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> json = null;
try {
json = mapper.readValue(data, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
return json;
}
2.获取请求支付ip地址,传给service
@ApiOperation("支付")
@RequestMapping(value = "/pay", method = {RequestMethod.GET})
public Map<String, String> pay(HttpServletRequest request,
@RequestParam("openid") String openid,
@RequestParam("paymoney") String paymoney,//支付金额
@RequestParam("activityid") int activityid) throws Exception {
//获取请求ip地址
this.activity = activityid; //这里的activityid是我的业务需要,你们按需求写自己的参数就好了
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip.indexOf(",") != -1) {
String[] ips = ip.split(",");
ip = ips[0].trim();
}
return wxPayService.wxPay(openid, ip, paymoney);
}
三.编写server
Tip:tip都写在注释里了,认真看主要内容都在这里了。。
@Slf4j
@Service
public class WxPayServiceImpl extends ServiceImpl<WxPayNotifyVOMapper, Wxpaynotifyvo> implements WxPayService {
@Override
public Map<String, String> wxPay(String openid, String ip, String paymoney) {
System.out.println("openid"+openid);
try {
//1.拼接下单地址参数,生成商户订单
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("body", "混读罗的好友圈"); //这里写的是商家的名称,支付的时候会显示
paraMap.put("openid", openid); //openid,通过登录获取,在支付的controller里边有。
paraMap.put("out_trade_no", UUID.randomUUID().toString().replaceAll("-", ""));//订单号,每次都不同
paraMap.put("spbill_create_ip", ip); //请求的IP地址
paraMap.put("total_fee", paymoney); //支付金额,单位分
paraMap.put("trade_type", "JSAPI"); //交易类型,小程序支付用JSAPI,其他的可以看微信开发文档
log.info("paraMap为:" + paraMap); //打印日志
log.info("+++++++++++++++++++分割线++++++++++++++++++++");
//2.通过MyWXPayConfig创建WXPay对象,用于支付请求。调用支付统一下单API
final String SUCCESS_NOTIFY = "http://59.111.56.123:8090/hunduluo/success"; //支付成功的回调接口,需要自己写一个。(我写的是假的,就是举个例子),这个接口后边补充
boolean useSanbox = false; //是否用沙河环境支付API,是的话不会真正扣钱
MyWXPayConfig config = new MyWXPayConfig();
WXPay wxPay = new WXPay(config, SUCCESS_NOTIFY, false, useSanbox);
//3、fillRequestDate会将上述参数用key=value形式和mchkey一起加密为sign,验证地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi/php?chapter=20_1;
Map<String, String> map = wxPay.unifiedOrder(wxPay.fillRequestData(paraMap), 15000, 15000);
//4、发送post请求“统一下单接口”(https://api.mch.weixin.qq.com/pay/unifiedorder),返回预支付id:prepay-id
String prepayId = (String) map.get("prepay_id");
log.info("xmlStr为:" + map);
log.info("===================分割线=================");
Map<String, String> payMap = new HashMap<String, String>();
payMap.put("appId", WechatConstant.APPID); //小程序的appid
payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + ""); //时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
payMap.put("nonceStr", WXPayUtil.generateNonceStr()); //随机字符串,长度为32个字符以下。
//是否用沙河,我这里不用
if (useSanbox) {
payMap.put("signType", WXPayConstants.MD5);
} else {
payMap.put("signType", WXPayConstants.HMACSHA256);
}
payMap.put("package", "prepay_id=" + prepayId);
//5.通过appid,timeStamp,nonceStr,signType,package及圣湖密钥进行key=value形式拼接并加密
String paySign = null;
if (useSanbox) {
paySign = WXPayUtil.generateSignature(payMap, WechatConstant.MCH_KEY, WXPayConstants.SignType.MD5);
} else {
paySign = WXPayUtil.generateSignature(payMap, WechatConstant.MCH_KEY, WXPayConstants.SignType.HMACSHA256);
}
payMap.put("paySign", paySign);
//6、将着六个参数穿给前端,也就是小程序,用这些数据去调用微信官方wx.requestPayment支付请求
log.info("payMap:" + payMap);
return payMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
四.小程序端调用requestPayment
Tip:小程序接收后台传来的那几个参数,就是server里最后传回去那几个,进行wx.requestPayment
function(res){
wx.requestPayment({
nonceStr: res.data.nonceStr,
package: res.data.package,
paySign: res.data.paySign,
timeStamp: res.data.timeStamp,
signType:res.data.signType,
//支付成功
success (res) {
//业务逻辑
},
//支付失败
fail (res) {
//业务逻辑
},
complete(res){
}
})
}
补充
这里是支付成功的回调接口
Tip:为了给微信服务器一个return_code SUCCESS,否则它会每隔一段时间请求一次,并且用这个接口将支付信息Wxpaynotifyvo存起来,以后退款会用到。
@ApiOperation("支付成功后回调")
@RequestMapping(value = "/success", produces = MediaType.TEXT_PLAIN_VALUE)
public String success(HttpServletRequest request, @RequestBody Wxpaynotifyvo param) throws Exception {
System.out.println("param: "+param);
param.setActivityid(this.activity);
param.setTransactionid(param.getTransaction_id());
param.setTotalfee(param.getTotal_fee());
int insert = this.wxPayService.putWxPayNotifyVO(param);
HashMap<String, String> result = new HashMap<String, String>();
if ("SUCCESS".equals(param.getReturn_code())) {
result.put("return_code", "SUCCESS");
result.put("return_msg", "OK");
}
log.info(String.valueOf(param));
return WXPayUtil.mapToXml(result);
}
Tip:这个controller对应的server不写了(就不两行),把Wxpaynotifyvo内容写一下。
@Data
@XmlRootElement
@NoArgsConstructor
@AllArgsConstructor
public class Wxpaynotifyvo {
@TableId(value = "wxpaynotifyvo")
private int wxpaynotifyvo;
private String appid;
private String attach;
private String bank_type;
@TableField(value = "fee_type")
private String fee_type;
private String is_subscribe;
private String mch_id;
private String nonce_str;
private String openid;
private String out_trade_no;
private String result_code;
private String return_code;
private String sign;
private String time_end;
private String total_fee;
private String coupon_fee;
private String coupon_count;
private String coupon_type;
private String coupon_id;
private String trade_id;
private String transaction_id;
private int activityid;
private String transactionid;
private String totalfee;
}
Tip:讲道理,逻辑有点乱,可能看不懂,大二暑假做的东西,寒假才写博客记录头秃。真的是想不起来了,一边回忆一边质疑T_T。主要是记录一下,第一次写支付的时候也研究了好久,但最后我确实实现了支付和退款功能。(退款下篇写)希望对读者有帮助。欢迎纠错~