商户号:商户平台查看
appid:商户平台查看(需要有绑定关系对应的支付属性需要一一对应,比如app支付只能使用移动应用的appid)
apiv3密钥:https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html(一定要设置的!!!!)
证书序列号:商户平台查看(登陆商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号。商户API证书和微信支付平台证书均可以使用第三方的证书解析工具,查看证书内容。或者使用openssl命令行工具查看证书序列号。) 注意区分:这个是商户api证书的证书序列号
商户证书私钥:就是商户api证书的私钥,链接:https://kf.qq.com/faq/161222NneAJf161222U7fARv.html(apiclient_key.pem这个文件,文本方式打开或者用方法获取)
代码环境:jdk15(jdk1.8即可)
如果是按照下面的方法统一下单还报签名错误,一定是你的参数有问题!!!!你的参数有问题!!!!
基础支付的统一下单基本是一样的!
参考官方文档链接:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_3.shtml
详细代码:
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.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;
public class WxPayJsApi {
//新建一个可以关闭的请求
CloseableHttpClient httpClient;
/**
* 测试jsapi支付的统一下单
*/
//1.先加载两个证书
@Before
public void setup() throws IOException {
//获取商户api证书私钥的两种方法
//方法1直接把商户证书的里面的私钥以常量的形式带着代码中(不安全)
String privateKey1 = "你商户api证书的私钥";
//方法2利用获取私钥的类加载私钥(此类是官方写好的方法直接照搬过来即可)
PrivateKey privateKey = MyPrivateKey.getPrivateKey("商api证书路径");
//通过方法把私钥转换为字符串格式,这个方法是我自己找的,官方并未给示例,应该是方法不唯一和获取方式不唯一
byte[] encoded = privateKey.getEncoded();
System.out.println("encoded:"+encoded);
String prikeyStr = Base64.encodeBase64String(privateKey.getEncoded());//Base64:package org.apache.commons.codec.binary
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
//依旧是商户api证书的私钥
.loadPrivateKey(new ByteArrayInputStream(privateKey1.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
//商户号在商户平台能看到,序列号一样也是,这个apiv3秘钥跟商户证书api私钥一定要注意区分
//设置 apiv3秘钥参看文档:https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials("你的商户号", new PrivateKeySigner("你商户api证书的序列号", merchantPrivateKey)),"apiv3秘钥".getBytes("utf-8"));
//获取微信支付平台证书证书序列号
BigInteger serialNumber = verifier.getValidCertificate().getSerialNumber();
//转成16进制
String serialnumber = serialNumber.toString(16);
//获取微信支付平台证书
X509Certificate validCertificate = verifier.getValidCertificate();
//获取微信支付平台证书公钥
PublicKey publicKey1 = validCertificate.getPublicKey();
//转成字符串
String prikeyStr1 = Base64.encodeBase64String(publicKey.getEncoded());//Base64:package
System.out.println("prikeyStr:"+prikeyStr);
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
//merchantPrivateKey:平台证书就是上面的方法
.withMerchant("你的商号", "你商户api证书的序列号", merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
}
//2.获取api证书私钥,详情见MyPrivateKey
//3.构造签名,sdk自动构建签名了,无须商户自己构建签名
// 请求body参数
//这是照搬官方的参数,把商户号和appid修改为你自己使用的即可,需要和上面初始化httpClient的步骤中的参数是一一对应的
//注意:官方文档显示必填参数就是必须填写的,金额一定不能是小数,参数不能携带空格
//(到后期出现问题唤起支付的时候后期出现问题你就知道头疼)
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid","你的商户号")
.put("appid", "你的appid")
.put("description", "Image形象店-深圳腾大-QQ公仔")
.put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php")
.put("out_trade_no", "12177525012014070332333680183");
rootNode.putObject("amount")
.put("total", 1);
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);
}
//最后会自动执行,释放资源
@After
public void after() throws IOException {
httpClient.close();
}
}
生成签名的官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
我的代码:CreateSign
import okhttp3.HttpUrl;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;
public class CreateSign {
String getToken(String method, HttpUrl url, String body) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
//随机字符串
String nonceStr = "asdasdffafafgddfvc";//真!随机字符串
//时间戳
long timestamp = System.currentTimeMillis() / 1000;
//从下往上依次生成
String message = buildMessage(method, url, timestamp, nonceStr, body);
System.out.println("message:"+message);
//签名
String signature = sign(message.getBytes("utf-8"));
System.out.println("signature:"+signature);
System.out.println("nonceStr:"+nonceStr);
return "mchid=\"" + "你的商户号" + "\","
+ "nonce_str=\"" + nonceStr + "\","//真!随机自传承
+ "timestamp=\"" + timestamp + "\","//时间戳
+ "serial_no=\"" + "商户证书的证书序列号" + "\","
+ "signature=\"" + signature + "\"";
}
String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
//加密方式
Signature sign = Signature.getInstance("SHA256withRSA");
//私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名
sign.initSign(MyPrivateKey.getPrivateKey("商户证书的路径"));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) { //get请求自动做了校验,会把空字符串进行识别,注意是空字符串!!!!!
canonicalUrl += "?" + url.encodedQuery();
}
//官方的方法自动做了换行的所有动作,注意唤起支付的参数不一样需要更换(这里是统一下单所以直接照搬即可)
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
}
加载商户证书私钥的方法官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay7_1.shtml
我的代码:MyPrivatekey
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class MyPrivateKey {
/**
* 获取私钥。
* 这是个静态方法,可以直接用类名调用
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*
* 完全不需要修改,注意此方法也是去掉了头部和尾部,注意文件路径名
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
}
这个gettoken的方法在哪里用呢
楼主, okhttp3.HttpUrl; 这个类里面是啥代码呢?
前端发起jspi支付怎么弄,前端要时间戳,你这签名使用的时间戳如何返回给前端使用?