微信商家券对接wechatpay-apiv3

源码:https://gitee.com/wsc58888/wx-stock-coupon.git

  1. 由于微信商家券是新出来的接口,微信的文档一直都是各种坑.
  2. 每次请求需要签名.
  3. 注意: post的请求需要用Stream传输,不然一直会报400 Bed Requst
    get的请求增加Header
    patch的请求增加Header
  4. 参考:
    https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/marketing/busifavor/chapter3_1.shtml
    https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

工具类:

package com.wx.utils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

import org.apache.http.client.config.RequestConfig;
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.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;

import com.gdhz.wx.card.config.WechatPayConfig;
import com.java.st.lib.tools.io.FileUtil;
import com.java.st.lib.tools.io.URLResourceUtil;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by wd on 2016/6/28.
 */
@Slf4j
public class WechatPayHttpClientUtil {

	private static CloseableHttpClient client;

	// 使用商户私钥解密
	public String decryptOAEP(String ciphertext, PrivateKey privateKey) {

		try {
			String decryptCiphertext = RsaCryptoUtil.decryptOAEP(ciphertext, privateKey);
			return decryptCiphertext;
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		return null;
	}

	// 进行公钥加密
	public String decryptOAEP(String text, Verifier verifier) {

		// 建议从Verifier中获得微信支付平台证书,或使用预先下载到本地的平台证书文件中
		X509Certificate wechatpayCertificate = verifier.getValidCertificate();
		try {
			String ciphertext = RsaCryptoUtil.encryptOAEP(text, wechatpayCertificate);
			return ciphertext;
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * @return
	 */
	private static WechatPayHttpClientBuilder initBuilder() {
		try {
			String fileContent = FileUtil.toString(URLResourceUtil.asFile("classpath:apiclient_key.pem"));
			String privKeyPEM = fileContent.replace("-----BEGIN PRIVATE KEY-----\n", "")
					.replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");
			byte[] keyBytes = org.apache.commons.codec.binary.Base64.decodeBase64(privKeyPEM);
			// X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
			// PublicKey privateKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
			PrivateKey privateKey = java.security.KeyFactory.getInstance("RSA").generatePrivate(keySpec);
			String apiV3Key = WechatPayConfig.privateKey;
			AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
					new WechatPay2Credentials(WechatPayConfig.mchId,
							new PrivateKeySigner(WechatPayConfig.mchSerialNo, privateKey)),
					apiV3Key.getBytes("utf-8"));
			WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
					.withMerchant(WechatPayConfig.mchId, WechatPayConfig.mchSerialNo, privateKey)
					.withValidator(new WechatPay2Validator(verifier));
			return builder;
		} catch (Exception e) {
			log.error("初始化微信支付密钥失败{}", e.getMessage());
		}
		return null;
	}

	//
	/**
	 * 获取http连接
	 * 
	 * @return
	 */
	public static CloseableHttpClient getCloseableHttpClient() {
		if (client != null) {
			return client;
		}
		try {
			String fileContent = FileUtil.toString(URLResourceUtil.asFile("classpath:apiclient_key.pem"));
			String privKeyPEM = fileContent.replace("-----BEGIN PRIVATE KEY-----\n", "")
					.replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");
			byte[] keyBytes = org.apache.commons.codec.binary.Base64.decodeBase64(privKeyPEM);
			// X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
			// PublicKey privateKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
			PrivateKey privateKey = java.security.KeyFactory.getInstance("RSA").generatePrivate(keySpec);
			String apiV3Key = WechatPayConfig.privateKey;
			AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
					new WechatPay2Credentials(WechatPayConfig.mchId,
							new PrivateKeySigner(WechatPayConfig.mchSerialNo, privateKey)),
					apiV3Key.getBytes("utf-8"));
			WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
					.withMerchant(WechatPayConfig.mchId, WechatPayConfig.mchSerialNo, privateKey)
					.withValidator(new WechatPay2Validator(verifier));
			client = builder.build();
		} catch (Exception e) {
			log.error("初始化WecvhatPayHttlClientBulider失败:{}", e.getMessage());
		}
		return client;
	}

	/**
	 * 获取请求超时等配置
	 *
	 * @return
	 */
	public static RequestConfig getRequestConfig() {
		RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000) // 连接服务器端超时
//                .setConnectionRequestTimeout(5000) // 从连接池获取连接超时
				.setSocketTimeout(30 * 1000) // 从服务器端获取数据超时
				.build();
		return requestConfig;
	}

	/**
	 * 提交json数据
	 *
	 * @param url
	 * @param requestJson
	 * @return
	 * @throws IOException
	 */
	public static String postJson(String url, String requestJson) throws IOException {
		HttpPost httpPost = new HttpPost(url);
		StringEntity se = new StringEntity(requestJson, "UTF-8");
		se.setContentType("application/json");
		httpPost.setEntity(se);// new StringEntity(requestJson, ContentType.APPLICATION_JSON)
		return getCloseableHttpClient().execute(httpPost, new HttpStringResponse());
	}
	/**
	 * 提交postStream数据
	 *流转输
	 * @param url
	 * @param requestJson
	 * @return
	 * @throws IOException
	 */
	public static String postStream (String url, String requestJson) throws IOException {
		HttpPost httpPost = new HttpPost(url);
		InputStream stream = new ByteArrayInputStream(requestJson.getBytes("utf-8"));
		InputStreamEntity reqEntity = new InputStreamEntity(stream);
		reqEntity.setContentType("application/json");
		httpPost.setEntity(reqEntity);
		httpPost.addHeader("Accept", "application/json");
		
		return getCloseableHttpClient().execute(httpPost, new HttpStringResponse());
	}
	/**Get请求*/
	public static String doGet(String url) throws IOException {
		HttpGet httpGet = new HttpGet(url);
		//重点,需要增加这个header,不然会返回400 - Bad Request
		httpGet.addHeader("Accept", "application/json");
		return getCloseableHttpClient().execute(httpGet, new HttpStringResponse());
	}

}
public static void patchStream(String url, String jsonString) throws ClientProtocolException, IOException {
			HttpPatch httpPatch = new HttpPatch(url);
			httpPatch.setHeader("Content-type", "application/json");
			httpPatch.setHeader("Charset", "utf-8");
			httpPatch.setHeader("Accept", "application/json");
			httpPatch.setHeader("Accept-Charset", "utf-8");
			StringEntity data = new StringEntity(jsonString, "utf-8");
	        httpPatch.setEntity(data);
	        getCloseableHttpClient().execute(httpPatch);
		
	}

创建商家券

package com.wx.utils;
@Slf4j
public class WxStocksInterfaceInvoker {

	/**
	 * 创建商家券
	 */
	public static StockCreateResponse createStocks(StockCreateRequest stockCreateRequest) {
		String responseStr = null;
		try {
			**//重点:平时我们都使用Json传输,微信使用的是Stream传输**
			responseStr = WechatPayHttpClientUtil.postStream(WechatPayConfig.createStocksURL,
					JSON.toJSONString(stockCreateRequest));
		} catch (IOException e) {
			log.error("请求时IO异常,地址:{},参数:{},error:{}",  WechatPayConfig.createStocksURL,JSON.toJSONString(stockCreateRequest), e);
		}
		StockCreateResponse responseDTO = JSON.parseObject(responseStr, new TypeReference<StockCreateResponse>() {
		});
		return responseDTO;
	}
}

HttpReponse返回值统一处理.

package com.wx.utils;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.util.EntityUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by wd on 2016/6/28.
 */
@Slf4j
public class HttpStringResponse implements ResponseHandler<String> {

	@Override
	public String handleResponse(HttpResponse response) throws IOException {

		StatusLine statusLine = response.getStatusLine();


        HttpEntity entity = response.getEntity();
        if (statusLine.getStatusCode() >= 300) {
            log.info("请求被重定向,无法接受任何数据!");
            return null;
        }
        if (entity == null) {
            throw new ClientProtocolException("Response contains no content");
        }
        return EntityUtils.toString(entity);
	}
}

Junit4Test


@Slf4j
public class WxStockInterfaceInvokerTest {

	@Test
	public void createStock() {
		String createStockJson = "{\n" + 
				"    \"stock_name\": \"满减券20200122001\",\n" + 
				"    \"belong_merchant\": \"123141414\",\n" + 
				"    \n" + 
				"    \"goods_name\": \"满减券可适用的商品或服务\",\n" + 
				"    \"stock_type\": \"NORMAL\",\n" + 
				"    \"coupon_use_rule\": {\n" + 
				"        \"coupon_available_time\": {\n" + 
				"            \"available_begin_time\": \"2021-01-22T13:29:35+08:00\",\n" + 
				"            \"available_end_time\": \"2021-02-20T13:29:35+08:00\"\n" + 
				"             \n" + 
				"           \n" + 
				"        },\n" + 
				"        \"fixed_normal_coupon\": {\n" + 
				"            \"discount_amount\": 5,\n" + 
				"            \"transaction_minimum\": 100\n" + 
				"        },\n" + 
				"        \n" + 
				"        \"use_method\": \"OFF_LINE\"\n" + 
				"        \n" + 
				"    },\n" + 
				"    \"stock_send_rule\": {\n" + 
				"        \"max_coupons\": 100,\n" + 
				"        \"max_coupons_per_user\": 1\n" + 
				"         \n" + 
				"    },\n" + 
				"    \"out_request_no\": \"adsfddddddddddd2112009N001\",\n" + 
				"    \"coupon_code_mode\": \"WECHATPAY_MODE\"\n" + 
				"}";

		StockCreateRequest stockCreateRequest = JSON.parseObject(createStockJson,
				new TypeReference<StockCreateRequest>() {
				});
		StockCreateResponse stockCreateResponse = WxStocksInterfaceInvoker.createStocks(stockCreateRequest);
		log.info("创建商家券Requst: {} \r\n Response: {}",JSON.toJSONString(stockCreateRequest) ,
				JSON.toJSONString(stockCreateResponse));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wscwsc58888

只为正式环境创作

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值