标注:1、必须在微信内置浏览器进行支付;2、open id必须获取;3、获取code必须请求后台服务器,将code获取到返回前端,再请求获取openid,将openid存在自己服务器的session中
一、账户准备
1、在微信公众平台开通服务号并付费300元认证(只有企业才能开通)获取appid,在微信支付商户平台注册超级管理员并付费300认证,获取mch_id(商户id)。
2、在微信公众平台获取 appid,appsecret。在商户平台获取mch_id,api安全中获取API密钥(paternerKey)
二、平台配置
1、商户平台:产品中心开发配置,属于支付页面
2、公众平台:账户详情功能设置,配置网页授权域名,(将txt文件放在一级域名以及目录中,可通过域名/txt(文件全称)访问,测试是否可以获取txt内容。如果不行,则进行nginx配置域名访问目录)
3、公众平台:开发,基本设置,配置服务器地址(需要写接口并返回echostr,验证服务器),令牌随便写,消息密钥(随机生成),加密模式:兼容模式。
三、开发(下载javasdk。将微信中的工具类导入https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1)
1、支付前获取基本参数
/**
* 微信中请求打开页面
*/
@RequestMapping(value = "/toJob")
public String toJob(HttpServletResponse response){
String appId = WxConstants.APPID;
String tologin =redirectURL;
return "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appId+"&redirect_uri="+tologin+"&response_type=code&scope=snsapi_base#wechat_redirect";
}
/**
* 微信请求打开页面重定向到该接口,获取code
* 然后重定向到自己页面
*/
@RequestMapping(value = "/tologin")
public String tologin(String code ,HttpServletResponse response){
return "redirect:"+loginUrl+code;
}
/**
* 获取open id
*/
@RequestMapping("/getOpenId")
@ResponseBody
public String getOpenId( @RequestParam("code") String code,HttpServletRequest request){
logger.info("code code:" + code);
Map result = new HashMap<>();
//页面获取openId接口
String getopenid_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
Map param = new HashMap<>();
param.put("appid", WxConstants.APPID);
param.put("secret", WxConstants.SECRET);
param.put("code", code);
param.put("grant_type", "authorization_code");
//向微信服务器发送get请求获取openIdStr
String openIdStr = HttpClientUtil.doGet(getopenid_url, param);
JSONObject jsonObject = JSON.parseObject(openIdStr);
logger.info("opendID JSON:" + openIdStr);
String openid = jsonObject.getString("openid");
//将open id存入session。
//如果直接重定向到该接口,openid存不到session中,只会存在腾讯的session中
//微信中打开页面只能请求后台接口,后台获取到code拼接到前台页面后面
//前台获取到code,然后请求获取open id的接口,可以将open id存到我们自己服务器中
request.getSession().setAttribute("openid", openid);
//返回登录页
return openid;
}
/**
* 微信测试接口
* @param request
* @return
*/
@RequestMapping("/Token")
@ResponseBody
public String Token(HttpServletRequest request){
logger.info(JSON.toJSONString(request.getParameterMap()));
return request.getParameter("echostr");
}
2、支付、回调
/**
* 简历微信支付
* @param request
* @param isUpdate 是否是认证订单
*/
@RequestMapping(value = "/wxPayJob")
@ResponseBody
public Map wxpay(HttpServletRequest request,
@RequestParam("isUpdate") Integer isUpdate,
@RequestParam("jobUserId") Long jobUserId,
@RequestParam("totalAmount") String totalAmount) {
String openId = (String) request.getSession().getAttribute("openid");
logger.info("opendID pay:" + openId);
//获取openId
String ip = WxUtils.getIpAddress(request);
//将元转化为分
String totalFee = AmountUtils.changeY2F(totalAmount);
Map result = wxService.orderPayment(openId, ip, totalFee, jobUserId, isUpdate);
return result;
}
/**
* 微信支付回调
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/wxPayJobNotify")
@ResponseBody
public String wxPayJobNotify(HttpServletRequest request, HttpServletResponse response) {
InputStream is = null;
try {
is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
String xml = IOUtils.toString(is, "utf-8");
Map notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
if(notifyMap.get("return_code").equals("SUCCESS")){
if(notifyMap.get("result_code").equals("SUCCESS")){
String orderNum = notifyMap.get("out_trade_no");//商户订单号
String amountpaid = notifyMap.get("total_fee");//实际支付的订单金额:单位 分
String amount = AmountUtils.changeF2Y(amountpaid);
//String openid = notifyMap.get("openid"); //如果有需要可以获取
//String trade_type = notifyMap.get("trade_type");
//修改认证状态,订单状态
jobOrderDtoService.updateState(orderNum);
}
}
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
response.getWriter().write(" ");
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
3、支付统一下单主体
//微信支付----异步回调(接口:payForOrder)
@Value(value = "${WxConfig.wxJObNotifyUrl}")
private String wxJObNotifyUrl;
/**
* 调用微信支付
*/
@Override
public Map orderPayment(String openId, String ip, String totalAmount, Long jobUserId, Integer isUpdate) {
Map payMap = new HashMap();
try{
String OrderNum = OrderNumUtil.outtradeno("JOB");
String body = "认证";
Map mapBody = setSortedMap(openId,OrderNum,body,totalAmount,ip);
String xml = WXPayUtil.mapToXml(mapBody);//将所有参数(map)转xml格式
// 统一下单
String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//发送post请求"统一下单接口"返回预支付id:prepay_id
Map xmlMap = new HashMap<>();
xmlMap.put("xml",xml);
String xmlStr = HttpClientUtil.doPostJson(unifiedorder_url, xml);
//返回前端页面的json数据
payMap = resultMap(xmlStr);
//插入简历订单
} catch (Exception e) {
e.printStackTrace();
}
return payMap;
}
/**
* 下单主体信息
*/
public Map setSortedMap(String openId,String OrderNum,String body,String totalFee,String ip) throws Exception{
//拼接统一下单地址参数
Map paraMap = new HashMap();
paraMap.put("appid", WxConstants.APPID);
paraMap.put("body", body);
paraMap.put("mch_id", WxConstants.MCHID);
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());//生成uuID
paraMap.put("openid", openId);
paraMap.put("out_trade_no", OrderNum);//订单号
paraMap.put("spbill_create_ip", ip);
paraMap.put("total_fee",totalFee);//金额 单位 分
paraMap.put("trade_type", WxConstants.WX_TRADE_JSAPI);
paraMap.put("notify_url",wxJObNotifyUrl);// 此路径是微信服务器调用支付结果通知路径随意写
String sign = WXPayUtil.generateSignature(paraMap, WxConstants.KEY);
paraMap.put("sign", sign);
return paraMap;
}
/**
* 返回前端页面的json数据
*/
public Map resultMap(String xmlStr)throws Exception{
Map json = WXPayUtil.xmlToMap(xmlStr);
//JSONObject json = JSONObject.parseObject(xmlStr);//转成Json格式
LOGGER.info("prepay_id JSON:" + json);
//获取prepay_id
String prepay_id = json.get("prepay_id");//预支付id
if (xmlStr.indexOf("SUCCESS") != -1) {
Map map = WXPayUtil.xmlToMap(xmlStr);
prepay_id = (String) map.get("prepay_id");
}
Map payMap = new HashMap();
payMap.put("appId", WxConstants.APPID);
payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");
payMap.put("nonceStr", WXPayUtil.generateNonceStr());
payMap.put("signType", "MD5");
payMap.put("package", "prepay_id=" + prepay_id);
String paySign = WXPayUtil.generateSignature(payMap, WxConstants.KEY);
payMap.put("paySign", paySign);
return payMap;
}
4、工具类:
元转分,分转元
public class AmountUtils {
/**金额为分的格式 */
public static final String CURRENCY_FEN_REGEX = "\\-?[0-9]+";
/**
* 将分为单位的转换为元并返回金额格式的字符串 (除100)
*
* @param amount
* @return
* @throws Exception
*/
public static String changeF2Y(Long amount) throws Exception{
if(!amount.toString().matches(CURRENCY_FEN_REGEX)) {
throw new Exception("金额格式有误");
}
int flag = 0;
String amString = amount.toString();
if(amString.charAt(0)=='-'){
flag = 1;
amString = amString.substring(1);
}
StringBuffer result = new StringBuffer();
if(amString.length()==1){
result.append("0.0").append(amString);
}else if(amString.length() == 2){
result.append("0.").append(amString);
}else{
String intString = amString.substring(0,amString.length()-2);
for(int i=1; i<=intString.length();i++){
if( (i-1)%3 == 0 && i !=1){
result.append(",");
}
result.append(intString.substring(intString.length()-i,intString.length()-i+1));
}
result.reverse().append(".").append(amString.substring(amString.length()-2));
}
if(flag == 1){
return "-"+result.toString();
}else{
return result.toString();
}
}
/**
* 将分为单位的转换为元 (除100)
*
* @param amount
* @return
* @throws Exception
*/
public static String changeF2Y(String amount) throws Exception{
if(!amount.matches(CURRENCY_FEN_REGEX)) {
throw new Exception("金额格式有误");
}
return BigDecimal.valueOf(Long.valueOf(amount)).divide(new BigDecimal(100)).toString();
}
/**
* 将元为单位的转换为分 (乘100)
*
* @param amount
* @return
*/
public static String changeY2F(Long amount){
return BigDecimal.valueOf(amount).multiply(new BigDecimal(100)).toString();
}
/**
* 将元为单位的转换为分 替换小数点,支持以逗号区分的金额
*
* @param amount
* @return
*/
public static String changeY2F(String amount){
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); //处理包含, ¥ 或者$的金额
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(currency+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
}
return amLong.toString();
}
public static void main(String[] args) {
// try {
// System.out.println("结果:"+changeF2Y("-000a00"));
// } catch(Exception e){
// System.out.println("----------->>>"+e.getMessage());
return e.getErrorCode();
// }
// System.out.println("结果:"+changeY2F("1.00000000001E10"));
System.out.println(AmountUtils.changeY2F("1.33"));
try {
System.out.println(AmountUtils.changeF2Y("1322"));
} catch (Exception e) {
e.printStackTrace();
}
// System.out.println(Long.parseLong(AmountUtils.changeY2F("1000000000000000")));
// System.out.println(Integer.parseInt(AmountUtils.changeY2F("10000000")));
// System.out.println(Integer.MIN_VALUE);
// long a = 0;
// System.out.println(a);
}
}
http doPost doGet 请求
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
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;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpClientUtil {
public static String doGet(String url, Map param) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
StringBuilder sb = new StringBuilder(url+"?");
for (String key : param.keySet()) {
sb.append(key+"="+param.get(key)+"&");
}
sb.deleteCharAt(sb.length()-1);
URI uri = new URI(sb.toString());
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
}
微信工具类
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
public class WxUtils {
//请求xml组装
public static String getRequestXml(Map parameters){
StringBuffer sb = new StringBuffer();
sb.append("");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
sb.append("<"+entry.getKey()+">"+entry.getValue()+""+entry.getKey()+">");
}
sb.append(" ");
return sb.toString();
}
/**
* 随机字符串生成
*/
public static String getRandomString(int length) { //length表示生成字符串的长度
String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 将xml格式的字符串转换成Map对象
*
* @param xmlStr xml格式的字符串
* @return Map对象
* @throws Exception 异常
*/
public static Map xmlStrToMap(String xmlStr) throws Exception {
if(StringUtils.isEmpty(xmlStr)) {
return null;
}
Map map = new HashMap<>();
//将xml格式的字符串转换成Document对象
Document doc = DocumentHelper.parseText(xmlStr);
//获取根节点
Element root = doc.getRootElement();
//获取根节点下的所有元素
List children = root.elements();
//循环所有子元素
if(children != null && children.size() > 0) {
for(int i = 0; i < children.size(); i++) {
Element child = (Element)children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
return map;
}
public static T xmlStrToBean(String xmlStr, Class clazz) throws InstantiationException, IllegalAccessException {
T obj = clazz.newInstance();
try {
// 将xml格式的数据转换成Map对象
Map map = xmlStrToMap(xmlStr);
//将map对象的数据转换成Bean对象
obj = JSON.parseObject(JSON.toJSONString(map),clazz);
} catch(Exception e) {
e.printStackTrace();
}
return obj;
}
public static String getIpAddress(HttpServletRequest request) {
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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
微信常量类
public class WxConstants {
//微信统一下单请求xml参数
public static final String APPID="wx1111111"; //appid是微信公众账号或开放平台APP的唯一标识
public static final String MCHID="111111";//商户申请微信支付后,由微信支付分配的商户收款账号
public static final String SECRET="aaaaaaaa";//AppSecret是APPID对应的接口密码
public static final String KEY="1111111111";//交易过程生成签名的密钥 同API密钥
public static final String NONCE_STR="nonce_str";
public static final String SIGN="sign";//生成签名
public static final String ATTACH="attach";
public static final String TIMESTAMP = "timeStamp";
public static final String NONCESTR = "noncestr";
public static final String PREPAYID = "prepayid";
public static final String PARTNERID = "111111";//API密钥
public static final String PACKAGE = "package";
public static final String OPENID ="OPENID";//用户唯一标识
public static final int getHttpConnectTimeoutMs = 8000;//连接时间
public static final int getHttpReadTimeoutMs = 10000; //超时时间
/**
* 微信 JSAPI支付(或小程序支付)微信中调用网页支付
*/
public static final String WX_TRADE_JSAPI = "JSAPI";
/**
* 微信 Native支付
*/
public static final String WX_TRADE_NATIVE = "NATIVE";
/**
* 微信 H5支付
*/
public static final String WX_TRADE_MWEB = "MWEB";
/**
* 微信 app支付
*/
public static final String WX_TRADE_APP = "APP";
}