相较于之前的微信支付API,主要区别是:
- 遵循统一的REST的设计风格
- 使用JSON作为数据交互的格式,不再使用XML
- 使用基于非对称密钥的SHA256-RSA的数字签名算法,不再使用MD5或HMAC-SHA256
- 不再要求携带HTTPS客户端证书(仅需携带证书序列号)
- 使用AES-256-GCM,对回调中的关键信息进行加密保护
微信支付API v3官方SDK(目前包含Java、PHP、GO三种语言版本)。此外,微信支付也提供API v3的Postman调试工具、微信支付平台证书下载工具,可以通过微信支付的GitHub获取。
1.在【API安全】里需要申请API证书、设置APIv3密钥。
2.在【开发配置】里需要配置合法域名
3.在【AppID账号管理】里需要绑定小程序的AppID
timeStamp:时间戳(只需要到秒,如果获取的是毫秒级需要除以1000)
nonceStr:随机字符串(不长于32位)
package:订单详情扩展字符串(prepay_id=……)
signType:签名方式(仅支持RSA)
paySign:签名(使用字段appId、timeStamp、nonceStr、package计算得出的签名值)
具体详解请查看微信支付api
wx.requestPayment({
timeStamp: '',
nonceStr: '',
package: '',
signType: '',
paySign: '',
success: (result) => {},
fail: () => {},
complete: () => {}
});
<!-- 导入微信支付v3工具包 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<!--Hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.4</version>
</dependency>
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
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.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
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.apache.xmlbeans.impl.xb.xsdschema.Public;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.PushBuilder;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class V3WXPayUtil {
public static Map<String, Object> createOrder(String buildCode, String openId, String description, Integer total) throws Exception {
// PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
// new ByteArrayInputStream(WXPayConstants.PRIVATE_KEY.getBytes("utf-8")));
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(WXPayConstants.PRIVATE_KEY);
writeText("C:/log.txt", "获取私钥:=========>" + merchantPrivateKey.toString());
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
writeText("C:/log.txt", "获取证书管理器实例:=========>" + certificatesManager.toString() + "\n");
// 向证书管理器增加需要自动更新平台证书的商户信息
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(WXPayConstants.MCH_ID,
new PrivateKeySigner(WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey));
writeText("C:/log.txt", "获取管理器增加需要自动更新平台证书的商户信息前:=========>" + wechatPay2Credentials.toString() + "\n");
byte[] api_v3KEYBytes = WXPayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8);
writeText("C:/log.txt", "获取api_v3KEYBytes:=========>" + api_v3KEYBytes + "\n");
certificatesManager.putMerchant(WXPayConstants.MCH_ID, wechatPay2Credentials, api_v3KEYBytes);
writeText("C:/log.txt", "证书管理器实例:=========>" + certificatesManager.toString());
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(WXPayConstants.MCH_ID);
writeText("C:/log.txt", "从证书管理器中获取verifier:=========>" + verifier);
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(WXPayConstants.MCH_ID, WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
writeText("C:/log.txt", "从证书管理器中获取verifier后:=========>" + "\n");
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();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", WXPayConstants.MCH_ID)
.put("appid", WXPayConstants.APP_ID)
.put("notify_url", WXPayConstants.NOTIFY_URL)
.put("description", description)
.put("out_trade_no", buildCode);
rootNode.putObject("amount")
.put("total", total) // 支付总金额,分为单位 需前端传入
.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());
log.info("成功获取微信预支付订单号{}", bodyAsString);
System.out.println("成功获取微信预支付订单号==========>" + bodyAsString);
// 时间戳
String currentTimeMillis = System.currentTimeMillis() / 1000 + "";
// 随机字符串 hutool工具类
String randomString = RandomUtil.randomString(32);
StringBuilder builder = new StringBuilder();
// 应用ID
builder.append(WXPayConstants.APP_ID).append("\n");
// 时间戳
builder.append(currentTimeMillis).append("\n");
// 随机字符串
builder.append(randomString).append("\n");
JsonNode node = objectMapper.readTree(bodyAsString);
writeText("C:/log.txt", "从获取node后:=========>" + node + "\n");
// 订单详情扩展字符串
builder.append("prepay_id=").append(node.get("prepay_id").toString().replace("\"", "")).append("\n");
String paySign = sign(builder.toString().getBytes());
Map<String, Object> map = new HashMap();
map.put("package", "prepay_id=" + node.get("prepay_id").toString().replace("\"", ""));
map.put("prepayId", node.get("prepay_id").toString().replace("\"", ""));
map.put("nonceStr", randomString);
map.put("signType", "RSA");
map.put("paySign", paySign);
map.put("timeStamp", currentTimeMillis);
map.put("appId", WXPayConstants.APP_ID);
return map;
}catch (Exception e){
writeText("C:/log.txt", "异常:" + getStackTraceMessage(e));
throw e;
}
}
public static String getStackTraceMessage(Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
e.printStackTrace(pw);
pw.flush();
sw.flush();
return sw.toString();
}
public static String sign(byte[] message) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey());
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 获取私钥。
* @return 私钥对象
* @throws IOException
*/
public static PrivateKey getPrivateKey() throws IOException {
String content = WXPayConstants.PRIVATE_KEY;
// String content = new String(Files.readAllBytes(new ClassPathResource(filename).getFile().toPath()), "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("无效的密钥格式");
}
}
public static boolean signVerify(String serial, String message, String signature) {
// 从证书管理器中获取verifier
Verifier verifier = null;
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(WXPayConstants.PRIVATE_KEY);
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(WXPayConstants.MCH_ID, new WechatPay2Credentials(WXPayConstants.MCH_ID,
new PrivateKeySigner(WXPayConstants.MCH_SERIAL_NO, merchantPrivateKey)), WXPayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
verifier = certificatesManager.getVerifier(WXPayConstants.MCH_ID);
return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
} catch (IOException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (HttpCodeException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
return false;
}
public static String decryptOrder(String body) {
AesUtil util = new AesUtil(WXPayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = null;
try {
node = objectMapper.readTree(body);
JsonNode resource = node.get("resource");
String ciphertext = resource.get("ciphertext").textValue();
String associatedData = resource.get("associated_data").textValue();
String nonce = resource.get("nonce").textValue();
return util.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static void writeText(String fileName, String text) {
FileOutputStream fop = null;
File file;
try {
file = new File(fileName);
fop = new FileOutputStream(file,true);
if (!file.exists()) {
file.createNewFile();
}
fop.write(text.getBytes());
fop.flush();
fop.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fop != null) {
fop.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class WXPayConstants {
/*appid*/
public static final String APP_ID = "";
/*小程序唯一凭证密钥*/
public static final String SECRET = "";
/*商户号*/
public static final String MCH_ID = "";
/* 商户证书序列号 */
public static final String MCH_SERIAL_NO = "";
/*APIv3密钥*/
public static final String API_V3KEY = "";
/*支付成功回调地址*/
public static final String NOTIFY_URL = "https://*.com/*/wXPay/wxPayNotify";
/*退款回调接口*/
public static final String PACKAGE = "Sign=WXPay";
/*序列号*/
public static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
"证书内容" +
"-----END PRIVATE KEY-----\n";
}
前端付款成功后微信会主动访问你的服务器WXPayConstants给的NOTIFY_URL回调地址给你一个通知
@ApiOperation(value = "微信回调", notes = "微信回调")
@RequestMapping(value = "/wxPayNotify", method = RequestMethod.POST)
public Map<String, Object> wxPayNotify(HttpServletRequest request) {
// JSONObject params = (JSONObject) this.requestBody();
// return busOrderService.productOrder(params);
System.out.println("Wechatpay-Timestamp:" + request.getHeader("Wechatpay-Timestamp"));
this.writeText("C:/log.txt", "Wechatpay-Timestamp: {}:=========>" + request.getHeader("Wechatpay-Timestamp") + "\n");
System.out.println("Wechatpay-Nonce:" + request.getHeader("Wechatpay-Nonce"));
this.writeText("C:/log.txt", "Wechatpay-Nonce: {}:=========>" + request.getHeader("Wechatpay-Nonce") + "\n");
System.out.println("Wechatpay-Signature:" + request.getHeader("Wechatpay-Signature"));
this.writeText("C:/log.txt", "Wechatpay-Signature: {}:=========>" + request.getHeader("Wechatpay-Signature") + "\n");
System.out.println("Wechatpay-Serial:" + request.getHeader("Wechatpay-Serial"));
this.writeText("C:/log.txt", "Wechatpay-Serial: {}:=========>" + request.getHeader("Wechatpay-Serial") + "\n");
Map result = new HashMap();
result.put("code", "FAIL");
BufferedReader reader = null;
try {
StringBuilder signStr = new StringBuilder();
// 请求时间戳\n
signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
// 请求随机串\n
signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");
// 请求报文主体\n
reader = request.getReader();
this.writeText("C:/log.txt", "reader:=========>" + reader.toString() + "\n");
String str = null;
StringBuilder builder = new StringBuilder();
while ((str = reader.readLine()) != null) {
builder.append(str);
}
System.out.println(builder);
log.info("请求报文主体: {}", builder);
this.writeText("C:/log.txt", "请求报文主体:=========>" + builder + "\n");
signStr.append(builder.toString()).append("\n");
// 1.验签
if (!V3WXPayUtil.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))) {
result.put("message", "sign error");
return result;
}
// 2.解密
String decryptOrder = V3WXPayUtil.decryptOrder(builder.toString());
log.info("验签解密: {}", decryptOrder);
this.writeText("C:/log.txt", "验签解密:=========>" + decryptOrder + "\n");
System.out.println(decryptOrder);
String payResult = busOrderService.verifySuccessfulPayment(decryptOrder);
if (StringUtils.isEmpty(payResult)) {
result.put("message", "verification error");
return result;
} else {
result.put("code", payResult);
}
return result;
} catch (IOException e) {
e.printStackTrace();
result.put("code", "FAIL");
result.put("message", e.getMessage());
return result;
}
}
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R