小程序微信支付
前言:最近项目中用到小程序的支付和退款,趁热打铁写个博客,请大家参考,有问题可以私信我源码。
写之前先看一遍官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
以下是我项目中的代码:
1、wx.properties配置文件:主要用于配置支付所需的参数
#微信的配置参数
APPID=微信公众平台中
#开发者密码
APPSECRET=微信公众平台中
#微信商户号
MCH_ID=客户开通的商户号
#交易类型
TRADE_TYPE=JSAPI(公众号和小程序都能用次类型)
#微信支付回调地址
Pay_NOTIFY_URL=支付成功回调地址
#微信退款回调地址
#REFUND_NOTIFY_URL = 退款成功回调地址
#签名方式
SIGN_TYPE=MD5
#微信支付商户密钥
KEY=商户平台中下载证书的时候设置的密钥
#证书路径
SSLCERT_PATH = 证书路径
#默认密码 默认为商户号
SSLCERT_PASSWORD = 商户号
1.1 查看appId和AppSecret
1.2 配置支付地址和回调地址(公众号支付格式:域名+项目名称;扫码支付格式:域名+项目名称+回调地址所在controller地址)
1.3 设置密钥(随机生成32位密钥:https://suijimimashengcheng.51240.com/)
2、pom.xml文件中增加包
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>${com.github.wxpay.version}</version>
</dependency>
3.、MyConfig方法
public class MyConfig implements WXPayConfig {
private byte[] certData;
// public MyConfig() throws Exception {
// String certPath = MyConfig.class.getClassLoader().getResource("MP_verify_9VjkBIHQhQfxQ534.txt").getPath();
URLDecoder decoder = new URLDecoder();
// String path = URLDecoder.decode(certPath,"utf-8");
File file = new File(certPath);
// File file = new File(path);
// InputStream certStream = new FileInputStream(file);
// this.certData = new byte[(int) file.length()];
// certStream.read(this.certData);
// certStream.close();
// }
// APPID
@Override
public String getAppID() {
return WXAuthUtil.APPID;
}
// 商户ID
@Override
public String getMchID() {
return WXAuthUtil.MCH_ID;
}
// 获取接口秘钥
@Override
public String getKey() {
return WXAuthUtil.KEY;
}
// 获取商户证书内容
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
}
4、WXAuthUtil微信工具类
@SuppressWarnings("deprecation")
public class WXAuthUtil {
static {
APPID = PropertyUtil.getProperty("APPID");
MCH_ID = PropertyUtil.getProperty("MCH_ID");
KEY = PropertyUtil.getProperty("KEY");
APPSECRET = PropertyUtil.getProperty("APPSECRET");
TRADE_TYPE=PropertyUtil.getProperty("TRADE_TYPE");
Pay_NOTIFY_URL=PropertyUtil.getProperty("Pay_NOTIFY_URL");
SIGN_TYPE=PropertyUtil.getProperty("SIGN_TYPE");
REFUND_NOTIFY_URL = PropertyUtil.getProperty("REFUND_NOTIFY_URL");
SSLCERT_PATH = PropertyUtil.getProperty("SSLCERT_PATH");
SSLCERT_PASSWORD = PropertyUtil.getProperty("SSLCERT_PASSWORD");
}
public static final String APPID;
public static final String MCH_ID;
public static final String KEY;
public static final String APPSECRET;
public static final String TRADE_TYPE;
public static final String Pay_NOTIFY_URL;
public static final String SIGN_TYPE;
public static final String REFUND_NOTIFY_URL;
public static final String SSLCERT_PATH;
public static final String SSLCERT_PASSWORD;
/**
* 通过url获取返回json对象
*
* @param url
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException {
JSONObject jsonObject = null;
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 把返回的结果转换为JSON对象
String result = EntityUtils.toString(entity, "UTF-8");
jsonObject = JSON.parseObject(result);
}
client.close();
return jsonObject;
}
public static JSONObject doPostJson(String url,String jsonString) throws ClientProtocolException, IOException {
JSONObject jsonObject = null;
DefaultHttpClient client = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(jsonString,"UTF-8");
httpPost.setEntity(stringEntity);
HttpResponse response = client.execute(httpPost);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 把返回的结果转换为JSON对象
String result = EntityUtils.toString(entity, "UTF-8");
jsonObject = JSON.parseObject(result);
}
client.close();
return jsonObject;
}
/**
* 获得微信 AccessToken
* access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。
* 开发者需要access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取
* 的access_token失效。
* (此处我是把token存在Redis里面了)
*/
public static AccessToken getWxToken() throws IOException {
JSONObject jsonObject =new JSONObject() ;
String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+ APPID+"&secret="+ APPSECRET;
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(tokenUrl);
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 把返回的结果转换为JSON对象
String result = EntityUtils.toString(entity, "UTF-8");
jsonObject = JSON.parseObject(result);
}
client.close();
AccessToken access_token = new AccessToken();
if (null != jsonObject) {
try {
access_token.setAccessToken(jsonObject.getString("access_token"));
access_token.setExpiresin(jsonObject.getInteger("expires_in"));
} catch (JSONException e) {
access_token = null;
// 获取token失败
e.printStackTrace();
}
}
return access_token;
}
public static void main(String[] args) throws IOException {
AccessToken wxToken = getWxToken();
System.out.println(wxToken);
}
/*
* 将SortedMap<Object,Object> 集合转化成 xml格式
*/
public static String getRequestXml(SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
5、PropertyUtil 用来读取properties中的配置参数
/**
* 读取wx.properties中的配置参数
* @author zhaobaolong
*
*/
public class PropertyUtil {
private static final Logger logger = LoggerFactory.getLogger(PropertyUtil.class);
private static Properties props;
static {
loadProps();
}
synchronized static private void loadProps() {
logger.info("开始加载properties文件内容.......");
props = new Properties();
InputStream in = null;
try {
// 第一种,通过类加载器进行获取properties文件流
in = PropertyUtil.class.getClassLoader().getResourceAsStream("wx.properties");
props.load(in);
} catch (FileNotFoundException e) {
logger.error("wx.properties文件未找到");
} catch (IOException e) {
logger.error("出现IOException");
} finally {
try {
if (null != in) {
in.close();
}
} catch (IOException e) {
logger.error("jdbc.properties文件流关闭出现异常");
}
}
logger.info("加载properties文件内容完成...........");
logger.info("properties文件内容:" + props);
}
public static String getProperty(String key) {
if (null == props) {
loadProps();
}
return props.getProperty(key);
}
public static String getProperty(String key, String defaultValue) {
if (null == props) {
loadProps();
}
return props.getProperty(key, defaultValue);
}
}
5.1 其他所需要的类都在weixin-sdk的包中
接下来是支付控制层的代码:
/**
* 微信支付
*
*/
@Controller
@RequestMapping("/wx/app")
public class WXPayController {
/**
* 统一下单
* @param total_fee 购买金额
* @param model
* @param openId 支付人的openId
* @return
* @throws Exception
*/
@RequestMapping("/orderPay")
@ResponseBody
public String unifiedOrder(Double total_fee,Model model,String openId) throws Exception {
String spbill_create_ip = IpUtil.getIp(request);
String orderNo = randomNo(orderInfoObjService.getOrderNum());//自动生成订单
OrderPayObj payList = new OrderPayObj();
// 统一下单
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<String, String>();
// 商品描述
data.put("body", "商品描述");
// 商户订单号
data.put("out_trade_no", orderNo);
int fee = (int) (total_fee * 100);
// 标价金额 付款金额
data.put("total_fee", String.valueOf(fee));
// 客户终端IP
data.put("spbill_create_ip", spbill_create_ip);
// 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
data.put("notify_url", WXAuthUtil.Pay_NOTIFY_URL);
// 交易类型 公众号支付
data.put("trade_type", "JSAPI");
// 用户标识
data.put("openid", openId);
Map<String, String> resp = null;
try {
resp = wxpay.unifiedOrder(data);
for (Entry<String, String> entry : resp.entrySet()) {
System.out.println("key:" + entry.getKey() + " Value:" + entry.getValue());
}
} catch (Exception e) {
e.printStackTrace();
}
if (resp.get("return_code").equals("SUCCESS")) {
if (resp.get("result_code").equals("SUCCESS")) {
Map<String, String> Param = new HashMap<String, String>();
Param.put("appId", WXAuthUtil.APPID);
Param.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
Param.put("nonceStr", WXPayUtil.generateNonceStr());
Param.put("package", "prepay_id=" + resp.get("prepay_id"));
Param.put("signType", WXAuthUtil.SIGN_TYPE);
String sign = WXPayUtil.generateSignature(Param, config.getKey(), WXPayConstants.SignType.MD5);
Param.put("paySign", sign);//验证签名
model.addAttribute("appId", Param.get("appId"));
model.addAttribute("timeStamp", Param.get("timeStamp"));
model.addAttribute("nonceStr", Param.get("nonceStr"));
model.addAttribute("pa", Param.get("package"));
model.addAttribute("signType", Param.get("signType"));
model.addAttribute("prepayId", resp.get("prepay_id"));
model.addAttribute("paySign", sign);
model.addAttribute("orderNo", orderNo);
//次数可写生成支付订单,支付状态为未支付
} else {
// if(resp.get("err_code")) //判断返回值的类型,打印出对应的错误返回到前端
payList.setCode("0");
payList.setCodeMsg(resp.get("err_code"));
}
} else {
// 通信失败,请检查网络
payList.setCode("-1");
payList.setCodeMsg(resp.get("return_msg"));
}
return JsonUtils.getJsonString(payList);
}
/**
* 自动生成订单
*/
public static String randomNo(int l) {
String str = new SimpleDateFormat("yyyyMMddhhmmss")
.format(new java.util.Date());
long m = Long.parseLong((str)) * 10000;
String ret = m +"_QL"+ l;
return ret;
}
/**
* @Description : 支付结果通知
* 异步通知
*/
@RequestMapping("/notify_url")
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
System.out.println("支付进入回调函数");
Map<String, String> map = new HashMap<String, String>();
String out_trade_no = null;
String return_code = null;
String return_msg = "";
BigDecimal total_fee = null;
try {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String resultStr = new String(outSteam.toByteArray(), "utf-8");
System.out.print("支付成功的回调:" + resultStr);
Map<String, String> resultMap = WXPayUtil.xmlToMap(resultStr);
out_trade_no = (String) resultMap.get("out_trade_no");
return_code = (String) resultMap.get("return_code");
total_fee = new BigDecimal(resultMap.get("total_fee"));
request.setAttribute("out_trade_no", out_trade_no);
response.setContentType("text/xml");
if (return_code.equals("SUCCESS")) {
// 通知微信.异步确认成功了.
return_code = "SUCCESS";
return_msg = "OK";
// 此处写支付成功的业务逻辑
} else {
// 此处写支付失败的业务逻辑
return_code = "FAIL";
return_msg = "error";
}
String mapToXml = WXPayUtil.mapToXml(map);
response.getWriter().write(mapToXml);
response.getWriter().flush();
} catch (Exception e) {
logger.debug("微信回调接口出现错误");
System.out.print("微信回调接口出现错误 :" + e.getMessage());
try {
map.put("return_code", return_code);
map.put("return_msg", return_msg);
response.getWriter().write(WXPayUtil.mapToXml(map));
} catch (Exception e1) {
logger.debug("支付回调解析错误");
}
}
}
/**
* @description: 订单查询
* @date: 13:38 2018/9/17
* @param: orderNo
* @retrun:
*/
@RequestMapping("/orderPayQuery.do")
@ResponseBody
public String orderQuery(String orderNo) throws Exception {
MyConfig config = new MyConfig();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<>();
data.put("out_trade_no", orderNo);
try {
Map<String, String> resp = wxpay.orderQuery(data);
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
}
return data.toString();
}
}
前端调用(将后台这些数据返回到前端,前端调用)
timeStamp String 是 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
nonceStr String 是 随机字符串,长度为32个字符以下。
package String 是 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
signType String 是 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致
paySign String 是 签名,具体签名方案参见微信公众号支付帮助文档;
大概就是这些,可能个别有些漏掉,有不明白的私信我