微信支付接口V3结合微信小程序步骤

1.首先可以下载官方SDK查看相关工具类

微信支付官网SDK下载
其中WXPayConstants和WXPayUtil会在后边使用到

import java.security.SecureRandom;
import java.util.*;

public class WXPayUtil {

	private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

	private static final Random RANDOM = new SecureRandom();

	/**
	 * 获取随机字符串 32位  Nonce Str
	 *
	 * @return String 随机字符串
	 */
	public static String generateNonceStr() {
		char[] nonceChars = new char[32];
		for (int index = 0; index < nonceChars.length; ++index) {
			nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
		}
		return new String(nonceChars);
	}
	
	/**
	 * 获取当前时间戳,单位秒
	 * 
	 * @return
	 */
	public static long getCurrentTimestamp() {
		return System.currentTimeMillis() / 1000;
	}
	/**
	 * 获取当前时间戳,单位毫秒
	 * 
	 * @return
	 */
	public static long getCurrentTimestampMs() {
		return System.currentTimeMillis();
	}
}
import org.apache.http.client.HttpClient;

/**
 * 常量
 */
public class WXPayConstants {

    public enum SignType {
        MD5, HMACSHA256
    }

    public static final String DOMAIN_API = "api.mch.weixin.qq.com";
    public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
    public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
    public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
    public static final String FAIL     = "FAIL";
    public static final String SUCCESS  = "SUCCESS";
    public static final String HMACSHA256 = "HMAC-SHA256";
    public static final String MD5 = "MD5";
    public static final String FIELD_SIGN = "sign";
    public static final String FIELD_SIGN_TYPE = "sign_type";
    public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
    public static final String USER_AGENT = WXPAYSDK_VERSION +
            " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
            ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
    public static final String MICROPAY_URL_SUFFIX     = "/pay/micropay";
    public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
    public static final String ORDERQUERY_URL_SUFFIX   = "/pay/orderquery";
    public static final String REVERSE_URL_SUFFIX      = "/secapi/pay/reverse";
    public static final String CLOSEORDER_URL_SUFFIX   = "/pay/closeorder";
    public static final String REFUND_URL_SUFFIX       = "/secapi/pay/refund";
    public static final String REFUNDQUERY_URL_SUFFIX  = "/pay/refundquery";
    public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
    public static final String REPORT_URL_SUFFIX       = "/payitil/report";
    public static final String SHORTURL_URL_SUFFIX     = "/tools/shorturl";
    public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
    // sandbox
    public static final String SANDBOX_MICROPAY_URL_SUFFIX     = "/sandboxnew/pay/micropay";
    public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
    public static final String SANDBOX_ORDERQUERY_URL_SUFFIX   = "/sandboxnew/pay/orderquery";
    public static final String SANDBOX_REVERSE_URL_SUFFIX      = "/sandboxnew/secapi/pay/reverse";
    public static final String SANDBOX_CLOSEORDER_URL_SUFFIX   = "/sandboxnew/pay/closeorder";
    public static final String SANDBOX_REFUND_URL_SUFFIX       = "/sandboxnew/secapi/pay/refund";
    public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX  = "/sandboxnew/pay/refundquery";
    public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
    public static final String SANDBOX_REPORT_URL_SUFFIX       = "/sandboxnew/payitil/report";
    public static final String SANDBOX_SHORTURL_URL_SUFFIX     = "/sandboxnew/tools/shorturl";
    public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
}

2.创建商户号

创建商户号,登录商户平台配置小程序appid以及获取证书序列号、微信支付商户号、APIV3密钥,这部分比较简单不做详述

3.微信支付的三个核心步骤 预支付、验签、回调方法

微信支付接口调用方法有预支付、验签、回调方法、关闭订单以及查询订单和退款,其中的难点在验签和回调,废话不多说直接上代码。
导入依赖

<dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.4</version>
</dependency>

将需要的密钥导入项目,也可以添加到properties文件中方便后期修改

    //小程序appid
	public static final String appid = "***************";
	//商户号
	public static final String mchId = "*****************";
	//证书序列号
	public static final String mchSerialNo = "***********";
	//V3密钥
	public static final String apiV3Key = "**************";
	//支付通知回调
	public static final String notifyUrl = "http://******";
	//小程序密钥
	public static final String secret="******************";
	//证书密钥
	public static final String privateKey = "************";
	//http客户端
	public static CloseableHttpClient httpClient;
	//退款通知回调
	public static final String refundNotifyUrl="http://**";
	public static AutoUpdateCertificatesVerifier verifier;

1.预支付

public static void setup() throws IOException {
		// 加载商户私钥(privateKey:私钥字符串)
		PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));

		// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
		 verifier = new AutoUpdateCertificatesVerifier(
				new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
				apiV3Key.getBytes("utf-8"));

		// 初始化httpClient
		httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey)
				.withValidator(new WechatPay2Validator(verifier)).build();
}

/**
	 * 获取小程序请求用户openId
	 * @param jsCode
	 * @return
	 * @throws URISyntaxException
	 * @throws IOException
	 */
	public static String getOpenId(String jsCode) throws URISyntaxException, IOException {
		String url = "https://api.weixin.qq.com/sns/jscode2session?appid="
				+appid+"&secret="+secret+"&js_code="+jsCode+"&grant_type=authorization_code";
		URIBuilder uriBuilder = new URIBuilder(url);
		HttpGet httpGet = new HttpGet(uriBuilder.build());
		httpGet.addHeader("Accept", "application/json");
		CloseableHttpResponse response = httpClient.execute(httpGet);
		String openIdParse = EntityUtils.toString(response.getEntity());
		Map<String,String> parse = (Map) JSON.parse(openIdParse);
		String openId = parse.get("openid");
		return openId;
}

//预支付方法,返回prepay_id(预支付id)PayBean是用于接收前端数据的实体类
public static String order(PayBean payBean,String outTradeNo) throws IOException, URISyntaxException {
        setup();
		//url: 哪一种支付
		HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
		httpPost.addHeader("Accept", "application/json");
		httpPost.addHeader("Content-type", "application/json; charset=utf-8");
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectMapper objectMapper = new ObjectMapper();
		String openId = getOpenId(payBean.getJsCode());
		ObjectNode rootNode = objectMapper.createObjectNode();
		rootNode.put("mchid", mchId)
		        .put("appid", appid)
		        .put("description", "")
				.put("notify_url",notifyUrl )//异步通知 yrl
				.put("out_trade_no", outTradeNo);//自己平台的商户号
		rootNode.putObject("amount").put("total", 1).put("currency", "CNY");
		rootNode.putObject("payer").put("openid", openId);
		objectMapper.writeValue(bos, rootNode);
		httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
		CloseableHttpResponse response = httpClient.execute(httpPost);
		String bodyAsString = EntityUtils.toString(response.getEntity());
		System.out.println(bodyAsString);
		return bodyAsString;
}

2.验签(V3加密方式只有RSA非对称性加密)

	public AjaxResult pay(PayBean payBean) throws Exception {
	//后端生成
	String outTradeNo = "LY" + System.currentTimeMillis();// 平台商品号码
		String order = "";
		try {
			order = order(payBean, outTradeNo);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//生成时间戳
		Long timeStamp = WXPayUtil.getCurrentTimestamp();
		生成随机数订单号
		String generateNonceStr = WXPayUtil.generateNonceStr();
		Map<String, String> parse = (Map) JSON.parse(order);
		// 需要签名的
		StringBuilder builder = new StringBuilder();
		builder.append(appid + "\n");
		builder.append(timeStamp + "\n");
		builder.append(generateNonceStr + "\n");
		builder.append("prepay_id=" + parse.get("prepay_id") + "\n");// 加签 商户号一致
		byte[] signBytes = builder.toString().getBytes("UTF-8");
		//加密方法
		String paySign = sign(signBytes);
		HashMap<String, String> signData = new HashMap<String, String>(8);
		signData.put("mchId", WeChatPay.mchId);
		signData.put("appId", WeChatPay.appid);
		signData.put("package", "prepay_id=" + parse.get("prepay_id"));
		signData.put("nonceStr", generateNonceStr);// 随机字符串32位
		signData.put("timeStamp", timeStamp.toString());// 时间戳
		signData.put("sign", paySign);
		return AjaxResult.success(signData);
	}

/**
	    StringBuilder builder = new StringBuilder();
		builder.append(WeChatPay.appid + "\n");
		builder.append(timeStamp + "\n");
		builder.append(generateNonceStr + "\n");
		builder.append("prepay_id=" + parse.get("prepay_id") + "\n");// 加签 商户号一致
		byte[] message = builder.toString().getBytes("UTF-8");
	 * @param message  需要签名的字符传byte数组
	 * @return
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 * @throws SignatureException
	 */
	public String sign(byte[] message) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		// 加载商户私钥(privateKey:私钥字符串)
		PrivateKey merchantPrivateKey = WeChatPay.getPrivateKey();

		Signature sign = Signature.getInstance("SHA256withRSA");
		sign.initSign(merchantPrivateKey);
		sign.update(message);
		return Base64.getEncoder().encodeToString(sign.sign());
	}

3.回调方法

public Map callBack(HttpServletRequest request, HttpServletResponse response)
			throws IOException, GeneralSecurityException {
		Map<String, String> result = new HashMap<String, String>();
		// 响应接口
		result.put("code", "FAIL");
		/** 从请求头获取验签字段 */
		// 时间戳
		String Timestamp = request.getHeader("Wechatpay-Timestamp");
		String Nonce = request.getHeader("Wechatpay-Nonce");// 字符数按
		String Signature = request.getHeader("Wechatpay-Signature");
		String Serial = request.getHeader("Wechatpay-Serial");
		System.out.println("开始读取请求头的信息");
		// 请求头
		System.out.println("Wechatpay-Timestamp=" + Timestamp);
		System.out.println("Wechatpay-Nonce=" + Nonce);
		System.out.println("Wechatpay-Signature=" + Signature);
		System.out.println("Wechatpay-Serial=" + Serial);
		// 加载商户私钥(privateKey:私钥字符串)
		PrivateKey merchantPrivateKey = PemUtil
				.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));

		// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
		AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
				new WechatPay2Credentials(mchId,
				new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
				apiV3Key.getBytes("utf-8"));
				// 读取请求体的信息
		System.out.println("开始读取请求体的信息");
		ServletInputStream inputStream = request.getInputStream();
		StringBuffer stringBuffer = new StringBuffer();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
		String s;
		// 读取回调请求体
		while ((s = bufferedReader.readLine()) != null) {
			stringBuffer.append(s);
		}
		String s1 = stringBuffer.toString();
		System.out.println("请求体" + s1);
		Map<String, String> requestMap = (Map) JSON.parse(s1);
		// 开始按照验签进行拼接
		String id = requestMap.get("id");
		System.out.println("id=" + id);
		String resource = String.valueOf(requestMap.get("resource"));
		System.out.println("resource=" + resource);
		Map<String, String> requestMap2 = (Map) JSON.parse(resource);
		String associated_data = requestMap2.get("associated_data");
		String nonce = requestMap2.get("nonce");
		String ciphertext = requestMap2.get("ciphertext");// 数据密文
		// 按照文档要求拼接验签串
		String VerifySignature = Timestamp + "\n" + Nonce + "\n" + s1 + "\n";
		System.out.println("拼接后的验签串=" + VerifySignature);
		// 使用官方验签工具进行验签
		boolean verify = verifier.verify(Serial, VerifySignature.getBytes(), Signature);
		System.out.println("官方工具验签=" + verify);
		// 判断验签的结果
		System.out.println("=======判断验签结果=======");
		if (verify == false) {
			result.put("message", "sign error");
			return result;
		}
		System.out.println("验签成功后,开始进行解密");
		// 解密,如果这里报错,就一定是APIv3密钥错误
		AesUtil aesUtil = new AesUtil(apiV3Key.getBytes("utf-8"));
		String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
		System.out.println("解密后=" + aes);
		Map<String, String> callBackData = getCallBackData(aes);
		String tradeState = callBackData.get("trade_state");
		if ("SUCCESS".equals(tradeState)) {
			result.put("code", "SUCCESS");
			result.put("message", "成功");
		} else {
			result.put("message", "trade_state error");
		}
		return result;	
}

public static Map<String,String> getCallBackData(String body){
		Map<String,String> resMap=new HashMap<>();
		Map<String,Object> strToMap = (Map) JSON.parse(body);
		resMap.put("out_trade_no",strToMap.get("out_trade_no").toString());
		resMap.put("transaction_id",strToMap.get("transaction_id").toString());
		resMap.put("trade_state",strToMap.get("trade_state").toString());
		Map<String,Object> payerMap = (Map) JSON.parse(strToMap.get("payer").toString());
		Map<String,Object> amountMap = (Map) JSON.parse(strToMap.get("amount").toString());
		resMap.put("openid",payerMap.get("openid").toString());
		resMap.put("total",amountMap.get("total").toString());
		resMap.put("payer_total",amountMap.get("payer_total").toString());
		return resMap;
}

4.查询订单

/**
	 * 查询订单 
	 * @param outTradeNo 商户订单号(本地平台的订单号)
	 * @return
	 * @throws URISyntaxException
	 * @throws IOException
	 * 
	 */
	public static String findOrder(String outTradeNo) throws URISyntaxException, IOException {
		String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "?mchid=" + mchId;
		URIBuilder uriBuilder = new URIBuilder(url);
		HttpGet httpGet = new HttpGet(uriBuilder.build());
		httpGet.addHeader("Accept", "application/json");
		CloseableHttpResponse response = httpClient.execute(httpGet);
		String bodyAsString = EntityUtils.toString(response.getEntity());
		return bodyAsString;
	}

5.退款与退款通知
退款是前端用户退款时请求的方法,退款通知是微信平台收到退款请求的回调

    /**
	 * 退款(api)
	 */
	public Map refund(String outTradeNo){
		Map<String, Object> result = new HashMap<String, Object>();
		String out_refund_no = "TB" + System.currentTimeMillis();// 平台商品号码
		ShaChargeRecord recode = shaChargeRecordService.selectShaChargeRecordByoutTradeNo(outTradeNo);
		long price = recode.getRealTotalPrice().longValueExact();
		try {
			JSONObject order = new JSONObject();
			order.put("out_trade_no", outTradeNo);//商户订单号
			order.put("out_refund_no", out_refund_no);//商户退款单号
//			order.put("reason", reason);//退款原因
			order.put("notify_url", refundNotifyUrl);//退款通知
			JSONObject amount = new JSONObject();
			amount.put("refund", price * 100);//退款金额 price * 100
			amount.put("currency", "CNY");
			amount.put("total", price * 100);//原订单金额
			order.put("amount", amount);
			// 加载商户私钥(privateKey:私钥字符串)
		PrivateKey merchantPrivateKey = PemUtil
				.loadPrivateKey(new ByteArrayInputStream(WeChatPay.privateKey.getBytes("utf-8")));
				// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
		AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
					new WechatPay2Credentials(mchId,
							new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
					apiV3Key.getBytes("utf-8"));
		// 初始化httpClient
		CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey)
					.withValidator(new WechatPay2Validator(verifier)).build();
		HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
		httpPost.addHeader("Accept", "application/json");
		httpPost.addHeader("Content-type","application/json; charset=utf-8");
		httpPost.setEntity(new StringEntity(order.toJSONString(), "UTF-8"));
		HttpResponse response = httpClient.execute(httpPost);
		//获取返回数据
		String bodyAsString = EntityUtils.toString(response.getEntity());
		JSONObject bodyAsJSON = JSONObject.parseObject(bodyAsString);
		logger.info(bodyAsJSON.toJSONString());
		final String status = bodyAsJSON.getString("status");
		result.put("status", status);
		if("SUCCESS".equals(status)){
				logger.info("退款成功");
				result.put("message", "退款成功");
			}else if("CLOSED".equals(status)){
				logger.info("退款关闭");
				result.put("message", "退款关闭");
			}else if("PROCESSING".equals(status)){
				logger.info("退款处理中");
				result.put("message", "退款处理中");
				result.put("code", 200);
			}else if("ABNORMAL".equals(status)){
				result.put("message", "退款异常");
				logger.info("退款异常");
			}
		} catch (Exception e) {
			logger.info(e.toString());
			e.printStackTrace();
		}
		return result;
	}	

    /**
	 * 退款通知(api)
	 */
	public Map refundNotice(HttpServletRequest request, HttpServletResponse response) {
		Map<String, String> result = new HashMap<String, String>();
		result.put("code", "FAIL");
		try {
			String reqParams = WeChatPay.read(request.getInputStream());
			logger.info("-------支付结果:" + reqParams);
			JSONObject json = JSONObject.parseObject(reqParams);
			String ciphertext = json.getJSONObject("resource").getString("ciphertext");
			final String associated_data = json.getJSONObject("resource").getString("associated_data");
			final String nonce = json.getJSONObject("resource").getString("nonce");
			AesUtil aesUtil = new AesUtil(apiV3Key.getBytes("utf-8"));
			//解密数据
			ciphertext = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
			Map<String, Object> resMap = new HashMap<>();
			Map<String, Object> strToMap = (Map) JSON.parse(ciphertext);
			String outTradeNo = (String) strToMap.get("out_trade_no");
			final String eventType = json.getString("event_type");
			if ("REFUND.SUCCESS".equals(eventType)) {
				result.put("code", "SUCCESS");
				result.put("message", "退款成功");
			} else if ("REFUND.ABNORMAL".equals(eventType)) {
				result.put("message", "退款异常");
			} else if ("REFUND.CLOSED".equals(eventType)) {
				result.put("message", "退款关闭");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	/**
	 * 查询单笔退款订单
	 * 
	 * @param outTradeNo
	 * @return
	 * @throws IOException
	 * @throws URISyntaxException
	 * 
	 * 微信开发文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_10.shtml
	 */
	 public AjaxResult getRefundsOrderByOutTradeNo(String outTradeNo) throws IOException, URISyntaxException {
		String order = getRefundsOrderByOutTradeNo(outTradeNo);
		Map<String,Object> resMap=new HashMap<>();
		Map<String,Object> strToMap = (Map) JSON.parse(order);
		resMap.put("status", strToMap.get("status"));
		if ("SUCCESS".equals(strToMap.get("status"))) {
		}
		return AjaxResult.success(resMap);
	}	
	
	/**
	 * 查询订单 
	 * @param outTradeNo 商户订单号(力云平台的订单号)
	 * @return
	 * @throws URISyntaxException
	 * @throws IOException
	 * 
	 */
	public static String getRefundsOrderByOutTradeNo(String outTradeNo) throws URISyntaxException, IOException {
		String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + outTradeNo ;
		URIBuilder uriBuilder = new URIBuilder(url);
		HttpGet httpGet = new HttpGet(uriBuilder.build());
		httpGet.addHeader("Accept", "application/json");
		CloseableHttpResponse response = httpClient.execute(httpGet);
		String bodyAsString = EntityUtils.toString(response.getEntity());
		return bodyAsString;
	}
	
	public static String read(InputStream is){
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			int len = 0;
			byte[] buffer = new byte[512];
			while((len = is.read(buffer)) != -1){
				baos.write(buffer, 0, len);
			}
			return new String(baos.toByteArray(), 0, baos.size(), "utf-8");
		}catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}

你可能感兴趣的:(java,微信支付接口,微信小程序,微信,后端,java)