源码:https://gitee.com/wsc58888/wx-stock-coupon.git
- 由于微信商家券是新出来的接口,微信的文档一直都是各种坑.
- 每次请求需要签名.
- 注意: post的请求需要用Stream传输,不然一直会报400 Bed Requst
get的请求增加Header
patch的请求增加Header - 参考:
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));
}
}