步骤:
登录(https://pay.weixin.qq.com)后,在“账户中心”->“API安全”。完成“申请API证书”和“设置APIv3密钥”配置。“设置API密钥”可不设置,v3支付用不到。
1、商户私钥。
在“申请API证书”,根据引导流程完成配置,你将下载获得(apiclient_cert.p12、apiclient_cert.pem、apiclient_key.pem)三个文件,其中apiclient_key.pem就是商户私钥,另外两个文件没用。
2、证书序列号。
在完成“申请API证书”配置后,右侧点击“管理证书”,可以看到证书序列号,复制保存下来。
3、设置APIv3密钥。
随机字符串,对于v3支付,该密钥很重要。
4、平台证书。
获取平台证书接口地址
签名验签:可以保证数据的完整性和真实性, 商户用自己的私钥签名,微信平台用商户公钥进行验签;微信平台用平台私钥签名,商户要用平台公钥进行验签。采用的是RSA非对称加密的方式。
证书和回调报文解密:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dechnic</groupId>
<artifactId>wcx_pay_service</artifactId>
<packaging>pom</packaging>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>dechnic-admin</module>
<module>dechnic-common</module>
<module>dechnic-pay</module>
</modules>
<name>wcx_pay_service</name>
<description>预付费小程序后台服务</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>1.4.1.RELEASE</spring.boot.version>
<!--<spring.boot.version>2.3.12.RELEASE</spring.boot.version>-->
<lombok.version>1.16.10</lombok.version>
<mysql.version>8.0.25</mysql.version>
<querydsl.version>4.1.4</querydsl.version>
<gson.version>2.8.9</gson.version>
<commons.lang.version>2.6</commons.lang.version>
<httClient.version>4.5.6</httClient.version>
<spring.data.commons.version>1.12.3.RELEASE</spring.data.commons.version>
</properties>
<!--依赖声明-->
<dependencyManagement>
<dependencies>
<!--springBoot 依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!-- 添加Spring-data-jpa依赖. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--bo dsl-->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.12.3.RELEASE</version>
</dependency>
<!--gson-->
<dependency>
<groupId>repository.com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!--commons-lang 工具包-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons.lang.version}</version>
</dependency>
<!--Http Client-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.15</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httClient.version}</version>
</dependency>
<!--dechnic-common-->
<dependency>
<groupId>com.dechnic</groupId>
<artifactId>dechnic-common</artifactId>
<version>${project.version}</version>
</dependency>
<!--dechnic-pay-->
<dependency>
<groupId>com.dechnic</groupId>
<artifactId>dechnic-pay</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.project.lombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<compilerArguments>
<verbose/>
<bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
dechnic-pay 配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wcx_pay_service</artifactId>
<groupId>com.dechnic</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>dechnic-pay</artifactId>
<dependencies>
<dependency>
<groupId>com.dechnic</groupId>
<artifactId>dechnic-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--微信支付API v3的Apache HttpClient扩展,实现了请求签名的生成和应答签名的验证-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
<!-- jackson 2.x 相关依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4.M1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.64</version>
</dependency>
</dependencies>
</project>
dechnic-admin 配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wcx_pay_service</artifactId>
<groupId>com.dechnic</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>dechnic_admin</artifactId>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 添加Spring-data-jpa依赖. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<!--querydsl 代码生成-->
<dependency>
<groupId>com.mysema.codegen</groupId>
<artifactId>codegen</artifactId>
<version>0.6.8</version>
</dependency>
<dependency>
<groupId>com.mysema.commons</groupId>
<artifactId>mysema-commons-lang</artifactId>
<version>0.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<!--Mongo驱动依赖包-->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.2</version>
</dependency>
<!--fastJson-->
<!-- <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>-->
<dependency>
<groupId>org.codehaus.xfire</groupId>
<artifactId>xfire-core</artifactId>
<version>1.2.6</version>
</dependency>
<!-- spring boot devtools 依赖包. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<!--支付依赖包-->
<dependency>
<groupId>com.dechnic</groupId>
<artifactId>dechnic-pay</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<!-- 构建节点. -->
<build>
<finalName>wxx_pay_service</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.project.lombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<compilerArguments>
<verbose />
<bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<!--该插件可以生成querysdl需要的查询对象,执行mvn compile即可-->
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
dechnic-common 配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>wcx_pay_service</artifactId>
<groupId>com.dechnic</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>dechnic-common</artifactId>
<dependencies>
<dependency>
<groupId>repository.com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<!-- 图片压缩 开源 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
</project>
package com.dechnic.pay.config;
import com.dechnic.pay.configprops.WeChatPayProperties;
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.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
/**
* @description:
* @author:houqd
* @time: 2022/7/6 16:22
*/
@Slf4j
@Configuration
public class WxPayV3Config {
@Autowired
WeChatPayProperties weChatPayProperties;
/**
* 获取商户的私钥文件
* @param filename
* @return
*/
private PrivateKey getPrivateKey(String filename){
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取签名验证器
* @return
*/
@Bean
public Verifier getVerifier() throws Exception {
log.info("获取签名验证器");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(weChatPayProperties.getPrivateKeyPath());
// 私钥签名对象
PrivateKeySigner keySigner = new PrivateKeySigner(weChatPayProperties.getMchSerialNo(), privateKey);
// 身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(weChatPayProperties.getMchId(), keySigner);
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(weChatPayProperties.getMchId(), wechatPay2Credentials,
weChatPayProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
Verifier verifier = certificatesManager.getVerifier(weChatPayProperties.getMchId());
return verifier;
}
/**
* 获取http请求对象
* @param verifier
* @return
*/
@Bean("wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier){
log.info("获取httpclient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(weChatPayProperties.getPrivateKeyPath());
// 从证书管理器中获取verifier
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(weChatPayProperties.getMchId(), weChatPayProperties.getMchSerialNo(), privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(weChatPayProperties.getPrivateKeyPath());
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(weChatPayProperties.getMchId(), weChatPayProperties.getMchSerialNo(), privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
}
}
spring:
profiles:
active: dev
application-dev.yml
server:
port: 443
servlet-path: /api/spro/*
spring:
application:
name: wcx_pay_service
datasource:
url: jdbc:mysql://XXXXXX:3306/jyservice_spro?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
username: XXXX
password: XXXXX
driver-class-name: com.mysql.cj.jdbc.Driver
tomcat:
max-active: 100
max-idle: 20
min-idle: 20
initial-size: 10
jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
#日志
logging:
config: classpath:logback-config.xml
file:
path: ./logs
level:
root: info
org.hibernate: info
com.dechnic: debug
#常量参数
pro-server:
pro:
name: cloud-jyservice-spro
flag: 1
#新版微信支付配置信息
wx:
#微信小程序配置
applet:
wcxVersion: 1.0.4
appId: XXXX
appSecret: XXX
#微信支付配置
pay:
mchId: XXXX #微信支付商户号
mchSerialNo: XXXX#证书序列号
apiV3Key: XXXXX #V3密钥
keyPass: XXXX #证书密码 默认微信商户号
privateKeyPath: D:/apiclient_key.pem
notifyUrl: http://localhost:443/api/spro/wxPay/pay-notify
#预付费小程序业务服务器配置
bizserver:
appId: XXXXX
secret: XXXXX
URL: http://localhost:8088
context-path: /openapi
sys-customer: iot-tfdchengo
WeChatAppletProperties
package com.dechnic.pay.configprops;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @description:
* @author:houqd
* @time: 2022/7/6 16:39
*/
@Component
@ConfigurationProperties(prefix = "wx.applet")
@Data
public class WeChatAppletProperties {
private String wcxVersion;//小程序版本号
private String appId;//小程序appId
private String appSecret;// 小程序密钥
}
WeChatPayProperties
package com.dechnic.pay.configprops;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @description:
* @author:houqd
* @time: 2022/7/5 13:58
*/
@Component
@ConfigurationProperties(prefix = "wx.pay")
@Data
public class WeChatPayProperties {
/**
* 商户id
*/
private String mchId;
/**
* 商户证书序列号
*/
private String mchSerialNo;
/**
* apiV3密钥
*/
private String apiV3Key;
/**
* p12证书文件位置
*/
private String p12Path;
/**
* 证书密码
*/
private String keyPass;
/**
* 商户私钥
*/
private String privateKeyPath;
/**
* 微信平台回调地址
*/
private String notifyUrl;
}
package com.dechnic.pay.service;
import com.dechnic.pay.po.QueryResultPO;
import java.util.Map;
/**
* @description: 小程序支付接口
* @author:houqd
* @time: 2022/7/8 14:33
*/
public interface IWChatPayService {
// 创建JSApiOrder 订单
Map<String, Object> createJSApiOrder(String orderNo, Integer amount, String openid, String goodsName) throws Exception;
// 查询订单状态
QueryResultPO getOrderState(String outTradeNo, String mChid) throws Exception;
}
WChatPayServiceImpl
package com.dechnic.pay.service.impl;
import com.dechnic.common.util.GsonUtll;
import com.dechnic.common.util.RandomUtil;
import com.dechnic.pay.configprops.WeChatAppletProperties;
import com.dechnic.pay.configprops.WeChatPayProperties;
import com.dechnic.pay.exception.BusinessException;
import com.dechnic.pay.po.QueryResultPO;
import com.dechnic.pay.service.IWChatPayService;
import com.dechnic.pay.util.PayUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class WChatPayServiceImpl implements IWChatPayService {
/**
* 微信小程序下单的请求地址
*/
private static final String V_3_PAY_TRANSACTIONS_JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
private static final String V_3_ORDER_SEARCH_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/";
// 测试开关
private static final boolean isTest = true;
@Autowired
WeChatPayProperties weChatPayProperties;
@Autowired
WeChatAppletProperties weChatAppletProperties;
@Resource
private CloseableHttpClient wxPayClient;
@Autowired
private PayUtils payUtils;
/**
* 创建微信小程序订单
*
* @param orderNo 订单号
* @param amount 单位 分
* @param openid 小程序的openid
* @param goodsName 订单名称
* @param
* @throws IOException
*/
@Override
public Map<String, Object> createJSApiOrder(String orderNo, Integer amount, String openid, String goodsName) throws IOException {// 请求body参数
log.info("============生成订单==========");
HttpPost httpPost = new HttpPost(V_3_PAY_TRANSACTIONS_JSAPI);
// 请求body参数
Map paramsMap = new HashMap();
paramsMap.put("appid", weChatAppletProperties.getAppId());// appid
paramsMap.put("mchid", weChatPayProperties.getMchId());//商户号
paramsMap.put("description", goodsName);// 商品描述
paramsMap.put("out_trade_no", orderNo);// 商户订单号
paramsMap.put("notify_url", weChatPayProperties.getNotifyUrl());// 通知地址
//订单金额
//TODO 测试为 1 分钱
Map amountMap = new HashMap();
amountMap.put("total", isTest ? 1 : amount);
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
//支付者
Map playerMap = new HashMap();
playerMap.put("openid", openid);
paramsMap.put("payer", playerMap);
//将参数转化未json字符串
String jsonParamsMap = GsonUtll.toJSON(paramsMap);
log.info("请求参数:" + jsonParamsMap);
StringEntity entity = new StringEntity(jsonParamsMap, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse resp = wxPayClient.execute(httpPost);
int statusCode = resp.getStatusLine().getStatusCode();
String bodyAsString = EntityUtils.toString(resp.getEntity());
if (statusCode == 200) { //处理成功
log.info("成功,返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功");
} else {
System.out.println("小程序下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
Map<String, Object> resMap = new HashMap<>();
try {
Map<String, Object> map = GsonUtll.jsonStr2Map(bodyAsString);
String prepayId = (String) map.get("prepay_id");
Assert.isTrue(StringUtils.isNotBlank(prepayId), "下单获取参数失败");
long timeStamp = System.currentTimeMillis() / 1000;
String nonceStr = RandomUtil.getUUID32().toUpperCase();
String packagep = "prepay_id=" + prepayId;
resMap.put("appId", weChatAppletProperties.getAppId());
resMap.put("timeStamp", timeStamp);
resMap.put("nonceStr", nonceStr);
resMap.put("package", packagep);
resMap.put("signType", "RSA");
//通过appid,timeStamp,nonceStr,signType,package以及商户密钥进行key=value形式进行拼接加密
String aPackage = payUtils.buildMessageForPay(weChatAppletProperties.getAppId(), timeStamp, nonceStr, (String) resMap.get("package"));
//获取对应的签名
String paySign = payUtils.sign(weChatPayProperties.getPrivateKeyPath(), aPackage.getBytes("utf-8"));
resMap.put("paySign", paySign);
log.info("[微信支付] 支付参数:" + GsonUtll.toJSON(resMap));
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
return resMap;
}
/**
* 查询订单状态
*
* @param outTradeNo 商户订单号
* @param mChid 直连商户号
* @return
*/
@Override
public QueryResultPO getOrderState(String outTradeNo, String mChid) {
CloseableHttpResponse response = null;
log.info("==================开始查询订单状态,订单号:[" + outTradeNo + "',商户id:[" + mChid + "]");
if (StringUtils.isEmpty(outTradeNo)) {
throw new BusinessException("查询订单状态,商户订单号不能为空");
}
if (StringUtils.isEmpty(mChid)) {
throw new BusinessException("查询订单状态,直连商户号不能为空");
}
String queryUrl = V_3_ORDER_SEARCH_URL + outTradeNo + "?mchid=" + mChid;
QueryResultPO queryResultPO = null;
try {
URIBuilder uriBuilder = new URIBuilder(queryUrl);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
try {
response = wxPayClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
String bodyAsString = EntityUtils.toString(response.getEntity());
if (statusCode == 200) { // 查询订单成功
log.info("查询订单成功,返回结果:" + bodyAsString);
} else if (statusCode == 204) { //处理成功,无返回Body
log.info("成功");
} else {
System.out.println("查询订单状态失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
}
Map<String, Object> resultMap = GsonUtll.jsonStr2Map(bodyAsString);
String trade_state = (String) resultMap.get("trade_state");// 支付状态
String trade_type = (String) resultMap.get("trade_type");// 交易类型
String transaction_id = (String) resultMap.get("transaction_id");//微信支付订单号
String trade_state_desc = (String) resultMap.get("trade_state_desc");//交易状态描述
String bank_type = (String) resultMap.get("bank_type");// 付款银行
String success_time = (String) resultMap.get("success_time");//支付完成时间
Map<String, Object> payer = (Map<String, Object>) resultMap.get("payer");
if (null != payer){
String openid = (String) payer.get("openid");
}
Map<String, Object> amout = (Map<String, Object>) resultMap.get("amount");
Long total = null;
if (null != amout){
total = (Long) amout.get("total");//订单总金额 单位分
}
queryResultPO = new QueryResultPO();
queryResultPO.setBank_type(bank_type);
if ("SUCCESS".equals(trade_state) || "CLOSED".equals(trade_state)) {
queryResultPO.setPaySuccess(true);
} else {
queryResultPO.setPaySuccess(false);
}
queryResultPO.setSuccess_time(success_time);
queryResultPO.setTotal(total);
queryResultPO.setTrade_state_desc(trade_state_desc);
queryResultPO.setTransaction_id(transaction_id);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
return queryResultPO;
}
}
package com.dechnic.pay.util;
import com.dechnic.pay.configprops.WeChatPayProperties;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Map;
/**
* @description:
* @author:houqd
* @time: 2022/7/8 13:46
*/
@Slf4j
@Component
public class PayUtils {
@Autowired
WeChatPayProperties weChatPayProperties;
/**
* 小程序调起支付 构造签名串
* @param appId
* @param timestamp
* @param nonceStr
* @param packag
* @return
*/
public String buildMessageForPay(String appId, long timestamp, String nonceStr, String packag) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ packag + "\n";
}
/**
* 用商户私钥证书进行签名
* @param wxCertPath 商户私钥证书路径
* @param message 待签名数据
* @return
* @throws NoSuchAlgorithmException
* @throws SignatureException
* @throws IOException
* @throws InvalidKeyException
* @throws java.security.InvalidKeyException
*/
public String sign(String wxCertPath,byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException, java.security.InvalidKeyException {
Signature sign = Signature.getInstance("SHA256withRSA"); //SHA256withRSA
sign.initSign(PemUtil.loadPrivateKey(new FileInputStream(wxCertPath)));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 微信后台返回密文解密
* @param bodyMap
* @return
* @throws GeneralSecurityException
*/
public String decryptFromResource(Map<String,Object> bodyMap) throws GeneralSecurityException {
log.info("===========秘文解密============");
//通知数据
Map<String,String > resourceMap =(Map<String, String>) bodyMap.get("resource");
//数据秘文
String ciphertext = resourceMap.get("ciphertext");
//获取随机串
String nonce = resourceMap.get("nonce");
String associated_data = resourceMap.get("associated_data");
log.info("秘文===》{}",ciphertext);
AesUtil aesUtil = new AesUtil(weChatPayProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//获取明文(解密后的数据)
String plainText = aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info("明文====》{}",plainText);
return plainText;
}
}
HttpUtils
package com.dechnic.pay.util.api;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
public class HttpUtils {
/**
* 将通知参数转化为字符串
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
WechatPay2ValidatorForRequest
package com.dechnic.pay.util.api;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
/**
* 从微信官方发给我的加密信息,进行验签解密
*/
public class WechatPay2ValidatorForRequest {
protected static final Logger log = LoggerFactory.getLogger(WechatPay2Validator.class);
/**
* 应答超时时间,单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId,String body) {
this.verifier = verifier;
this.requestId=requestId;
this.body=body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
//从请求头中拿到验签名序列号
String serial = request.getHeader(WECHAT_PAY_SERIAL);
//从请求头中拿到携带的签名
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签处理
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//获取时间戳,判断请求是否过期
String timestampStr = header;
try {
//通过时间戳,创建一个基于时间戳的时间对象
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期的请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}
OrderController
package com.dechnic.admin.web.controller.order;
import com.dechnic.admin.model.bean.order.OrderInfo;
import com.dechnic.admin.model.bean.user.UserInfo;
import com.dechnic.admin.model.to.order.GoodsTO;
import com.dechnic.admin.service.order.OrderService;
import com.dechnic.admin.service.user.UserSearchService;
import com.dechnic.common.dto.AjaxResult;
import com.dechnic.common.util.GsonUtll;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* @author Jahnke【[email protected]】
* @ClassName OrderController
* @Description:
* @date 2020/7/17 11:34
* @Version 1.0
*/
@Slf4j
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private UserSearchService userSearchService;
@Autowired
private OrderService orderService;
// 创建订单
@RequestMapping("get")
public String add(String sysCustomer, String token, String goodsDatas, String orderDatas, String desc) {
UserInfo userInfo = userSearchService.getByToken(sysCustomer, token);
List<GoodsTO> goodsList = GsonUtll.json2ListBean(goodsDatas, GoodsTO.class);
if (goodsList == null || goodsList.isEmpty()) {
return AjaxResult.errorResult("购买的物品不能为空!");
}
OrderInfo info = orderService.add(sysCustomer, goodsList, desc, orderDatas, userInfo);
return AjaxResult.successResult(info);
}
// 获取支付签名
@RequestMapping("get-sign")
public Map<String,Object> getSign(String sysCustomer, String token, String orderNo, String payType, String wxOpenId) {
UserInfo userInfo = userSearchService.getByToken(sysCustomer, token);
Assert.notNull(userInfo,"当前帐号失效,请重新登录");
Map<String,Object> sign = null;
try {
sign = orderService.getSign(sysCustomer, orderNo, payType, wxOpenId);
} catch (IOException e) {
e.printStackTrace();
}
return AjaxResult.ok(sign);
}
// 自己系统支付回调
@RequestMapping("pay-notify")
public String payNotify(String sysCustomer, String orderNo) {
orderService.payNotify(sysCustomer, orderNo);
return AjaxResult.successResult();
}
}
OrderService
package com.dechnic.admin.service.order;
import com.dechnic.admin.model.bean.order.OrderInfo;
import com.dechnic.admin.model.bean.order.QOrderInfo;
import com.dechnic.admin.model.bean.user.UserInfo;
import com.dechnic.admin.model.to.order.GoodsTO;
import com.dechnic.admin.model.vo.order.OrderRepository;
import com.dechnic.admin.web.advice.JyResultException;
import com.dechnic.common.dto.AjaxResult;
import com.dechnic.common.util.GsonUtll;
import com.dechnic.pay.configprops.WeChatPayProperties;
import com.dechnic.pay.constants.OrderStateConstant;
import com.dechnic.pay.po.QueryResultPO;
import com.dechnic.pay.service.IOrderSerivce;
import com.dechnic.pay.service.impl.WChatPayServiceImpl;
import com.querydsl.core.BooleanBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 订单方法
*/
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private IOrderSerivce IOrderSerivce;
@Autowired
private WChatPayServiceImpl wChatPayServiceImpl;
@Autowired
private WeChatPayProperties weChatPayProperties;
// 创建订单
public OrderInfo add(String sysCustomer, List<GoodsTO> goodsTOList, String desc, String orderDatas, UserInfo userInfo) {
// 计算金额
Double money = goodsTOList.stream().collect(Collectors.summarizingDouble(GoodsTO::getMoney)).getSum();
OrderInfo info = new OrderInfo();
// 商户订单号生成
String orderNo = IOrderSerivce.generaterOrderNo("power");
info.setSysCustomer(sysCustomer);
info.setOrderNo(orderNo);
info.setUser_name(userInfo.getName());
info.setUsername(userInfo.getUsername());
info.setOrderDatas(orderDatas);
info.setUser_phone(userInfo.getPhone());
info.setCreateTim(new Date());
info.setDelFlag(0);
info.setfVersion(1);
info.setOrderType("power");
info.setNoticeState(0);
info.setOrderState(OrderStateConstant.ORDER_STATE_NEW);
info.setPaymentMode("online");
info.setOrderMoney(money);
info.setGoodsDatas(GsonUtll.toJSON(goodsTOList));
info.setOrderDesc(desc);
orderRepository.save(info);
return info;
}
// 获取支付签名
public Map<String, Object> getSign(String sysCustomer, String orderNo, String payType, String userwx) throws IOException {
OrderInfo dbInfo = orderRepository.findBySysCustomerAndOrderNo(sysCustomer, orderNo);
if (dbInfo != null) {
if (dbInfo.getOrderState() <= 0) {
return AjaxResult.error("订单已取消,无法支付");
}
if (dbInfo.getOrderState() == OrderStateConstant.ORDER_STATE_FINISH) {
return AjaxResult.error("订单已完成,无法重复支付!");
}
if (dbInfo.getOrderState() == OrderStateConstant.ORDER_STATE_SIGN) {
// 判断支付是否完成
QueryResultPO queryResultPO = wChatPayServiceImpl.getOrderState(orderNo, weChatPayProperties.getMchId());
if (queryResultPO.isPaySuccess()) {
this.finishOrder(sysCustomer, orderNo);
throw new JyResultException("订单已支付,无法再次付款!");
}
}
// 获取支付签名
BigDecimal fenMoney = BigDecimal.valueOf(dbInfo.getOrderMoney()).setScale(2, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100));
Map<String, Object> sign = wChatPayServiceImpl.createJSApiOrder(orderNo, Integer.valueOf(fenMoney.intValue()), userwx, dbInfo.getOrderDesc());
dbInfo.setOrderState(OrderStateConstant.ORDER_STATE_SIGN);
dbInfo.setSignTime(new Date());
dbInfo.setPayType(payType);
dbInfo.setPayMoney(dbInfo.getOrderMoney());
dbInfo.setSignEffectTime(15);
orderRepository.save(dbInfo);
return sign;
}
return null;
}
// 支付回调
public void payNotify(String sysCustomer, String orderNo) {
// 支付完成,回调
OrderInfo dbInfo = orderRepository.findBySysCustomerAndOrderNo(sysCustomer, orderNo);
if (dbInfo != null) {
// 判断支付是否完成
QueryResultPO queryResultPO = wChatPayServiceImpl.getOrderState(orderNo, weChatPayProperties.getMchId());
if (queryResultPO.isPaySuccess()) {
this.finishOrder(sysCustomer, orderNo);
}
}
}
// 订单完成
public void finishOrder(String sysCustomer, String orderNo) {
OrderInfo dbInfo = orderRepository.findBySysCustomerAndOrderNo(sysCustomer, orderNo);
if (dbInfo != null) {
if (OrderStateConstant.ORDER_STATE_FINISH != dbInfo.getOrderState()) {
dbInfo.setOrderState(OrderStateConstant.ORDER_STATE_FINISH);
orderRepository.save(dbInfo);
}
}
}
// 跟踪订单支付完成状态
@Scheduled(cron = "0/5 * * * * ?")
public void autoFinish() {
// 查询当前服务器中所有存在获取支付签名,但是尚未完成支付的订单,判断是否完成支付
QOrderInfo qm = QOrderInfo.orderInfo;
BooleanBuilder qBuilder = new BooleanBuilder();
qBuilder.and(qm.orderState.eq(OrderStateConstant.ORDER_STATE_SIGN));
Pageable pageable = new PageRequest(0, 500, new Sort(Sort.Direction.DESC, "id"));
List<OrderInfo> list = orderRepository.findAll(qBuilder, pageable).getContent();
if (list != null) {
for (int i = 0; i < list.size(); i++) {
QueryResultPO queryResultPO = wChatPayServiceImpl.getOrderState(list.get(i).getOrderNo(), weChatPayProperties.getMchId());
if (queryResultPO != null && queryResultPO.isPaySuccess()) {
this.finishOrder(list.get(i).getSysCustomer(), list.get(i).getOrderNo());
continue;
}
// 支付超时,设置订单过期
if ((list.get(i).getSignTime().getTime() + list.get(i).getSignEffectTime() * 60 * 1000) < System.currentTimeMillis()) {
list.get(i).setOrderState(OrderStateConstant.ORDER_STATE_OUTTIME);
orderRepository.save(list.get(i));
}
}
}
}
}
NotifyController
package com.dechnic.admin.web.controller.order;
import com.dechnic.admin.service.order.OrderService;
import com.dechnic.pay.config.WxPayV3Config;
import com.dechnic.pay.configprops.BizServerConfigProps;
import com.dechnic.pay.util.PayUtils;
import com.dechnic.pay.util.api.HttpUtils;
import com.dechnic.pay.util.api.WechatPay2ValidatorForRequest;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* @description:
* @author:houqd
* @time: 2022/7/8 8:57
*/
@Slf4j
@RestController
@RequestMapping("/wxPay")
public class NotifyController {
@Resource
private PayUtils payUtils;
@Resource
private Verifier verifier;
@Autowired
private OrderService orderService;
@Autowired
private BizServerConfigProps bizServerConfigProps;
/**
* 微信通知回调地址
*
* @param request
* @param response
* @return
*/
@PostMapping("/notify")
public String notify(HttpServletRequest request, HttpServletResponse response) {
Gson gson = new Gson();
//创建一个应答对象
HashMap<String, String> map = new HashMap<>();
try {
//处理通知参数
String body = HttpUtils.readData(request);
HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
log.info("支付通知的id=====》》》{}", bodyMap.get("id"));
log.info("支付通知的完整数据=====》》》{}",body);
//TODO : 签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);
if (!wechatPay2ValidatorForRequest.validate(request)) {
log.error("通知验签失败");
//通知失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
log.info("通知验签成功");
//TODO : 处理订单
//解密密文,获取明文
String plaintText = payUtils.decryptFromResource(bodyMap);
//将明文转为map
HashMap plaintTextMap = gson.fromJson(plaintText, HashMap.class);
//获取支付下单的时候,传入的商户订单号,可以根据这个订单号去获取我们的一个订单记录,从而更新订单状态
String orderNo = (String) plaintTextMap.get("out_trade_no");
//业务编号
String transactionId = (String) plaintTextMap.get("transaction_id");
//trade_type,支付类型,如果有需要的话, 你可以存储在数据库中,这里我们的数据,基本上都是JSapi支付类型
String tradeType = (String) plaintTextMap.get("trade_type");
//交易状态
String tradeState = (String) plaintTextMap.get("trade_state");
//还有很多,为这里就不一一去写了
/**
* 在更新你订单状态之前,可以先根据orderNo,查询数据库中是否有这个订单
* 然后查询这个订单是否已经被处理过,也就是状态是否是已经支付的状态
* 如果这个订单已经被处理了,那么我们可以直接return,没有被处理过我们在处理
* 这样可以避免数据库被反复的操作
*
* 微信官方的解释是:
* 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
* 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,
* 并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,
* 则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,
* 以避免函数重入造成的数据混乱。
*/
//更新订单状态
/*
* 你的数据库操作
* 一定要存储解密出来的transaction_id字段在数据库中
* 如果需要跟微信官方对账的话,是需要提供这个字段进行一个查账的
* */
orderService.payNotify(bizServerConfigProps.getSysCustomer(), orderNo);
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
}
recharge.wxml
<!--pages/recharge/recharge.wxml-->
<wxs src="../../utils/fn.wxs" module="tool" />
<view class="m-page">
<view class="wp">
<view class="m-balance">
<view class="record">
<navigator class="" target="" url="../rechargeRecord/rechargeRecord?meterCode={{meterRealInfo.meterCode}}&tenantCode={{tenantsInfo.code}}&contact={{tenantsInfo.contact}}&name={{tenantsInfo.name}}&remainderValue={{meterRealInfo.remainderValue}}" hover-class="navigator-hover" open-type="navigate">
充值记录
</navigator>
</view>
<view class="balance">
<view class="pic">
<image class="" src="{{meterRealInfo.meterType==='电表'?'/images/z-i5.png':'/images/z-i6.png'}}" mode="widthFix" lazy-load="false" binderror="" bindload=""></image>
</view>
<view class="tit">余量:{{tool.toFixed(meterRealInfo.remainderValue)}}</view>
</view>
</view>
<view class="ul-list1">
<view class="li">
<view class="con">{{meterRealInfo.meterCode}}</view>
<view class="tit">电表号</view>
</view>
<view class="li">
<view class="con">{{tenantsInfo.name}}</view>
<view class="tit">缴费单位</view>
</view>
<view class="li">
<view class="con">{{tenantsInfo.code}}</view>
<view class="tit">缴费户号</view>
</view>
<view class="li">
<view class="con">{{tenantsInfo.contact}}</view>
<view class="tit">户名</view>
</view>
</view>
<view class="m-recharge">
<view class="g-t1">本次充值</view>
<view class="ul-list2" >
<view wx:for="{{goodsList}}" class="li {{formData.select1==index?'on':''}}" bindtap="radioChange" data-name="select1" data-index="{{index}}">
{{item.goods}}{{goodsUnit}} - ¥{{item.money}}
</view>
</view>
</view>
<view class="m-recharge">
<view class="g-t1">支付方式</view>
<view class="ul-list3">
<view class="li {{formData.select2==0?'on':''}}" bindtap="radioChange" data-name="select2" data-index="0">
<view class="circular">
<image class="" src="{{config.imageUrl}}/images/z-i3.png" mode="widthFix" lazy-load="false" binderror="" bindload=""></image>
</view>
<view class="left">
<view class="pic">
<image class="" src="{{config.imageUrl}}/images/z-i2.png" mode="widthFix" lazy-load="false" binderror="" bindload=""></image>
</view>
<view class="txt">
<view class="h3">微信支付</view>
<view class="em">微信安全支付</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="wp">
<button bindtap="sumit" class="m-btn">
确认支付
</button>
</view>
recharge.js
const util = require('../../utils/util.js')
const sysConfig = require('../../config/sysConfig.js')
var app = getApp();
Page({
/**
* 页面的初始数据
*/
data: {
formData: {
select1: 0,
select2: 0
},
tenantsInfo: {}, // 商户信息
meterRealInfo: {}, // 表具信息
goodsList: [], // 商品列表
goodsUnit: 'kwh'
},
radioChange: function(e){
let formData = this.data.formData;
let index = e.currentTarget.dataset.index;
let name = e.currentTarget.dataset.name;
formData[name] = index;
this.setData({
formData: formData
});
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this
var tenantsInfo = {
code: options.tenantCode,
contact:options.contact,
name: options.name,
meterPrise:options.meterPrise
}
that.setData({
meterCode:options.meterCode,
tenantCode:options.tenantCode,
tenantsInfo:tenantsInfo,
meterPrise: util.isNotNull(options.meterPrise)?Number(options.meterPrise):0 // 单价
})
// 通过商户号/表具号获取信息
that.queryMeterRealData()
},
// 通过商户号/表具号获取信息
queryMeterRealData () {
var that = this;
var params = {
meterCode: that.data.meterCode, // 表具编号
tenantCode: that.data.tenantCode // 商户号
}
app.func.otherReq("/openapi/queryMeterRealData", params, function (result) {
var successs = result.code == 1 ? true : false;
if (successs) {
var info = result.obj
// if (util.isNull(info.remainderValue)|| util.isNull(that.data.meterPrise)) {
// info.balance = '0.00'
// }else {
// info.balance = (info.remainderValue/that.data.meterPrise).toFixed(2)
// }
that.setData({
meterRealInfo:info
})
// 获取商品列表
that.getGoodsList()
}
})
},
// 获取充值列表
getGoodsList () {
var that = this;
var params = {
sysCustomer: sysConfig.app.sysCustomer
}
app.func.iotReq("/spro/goods/list", params, function (result) {
var successs = result.status == 1 ? true : false;
if (successs) {
var data = result.data
console.log("========获取充值goods列表成功:"+result.data);
console.log("=========meterRealInfo.meterType:"+that.data.meterRealInfo.meterType);
console.log("======电价:"+that.data.meterPrise)
var goodsList = []
var goodsList2 = []
var goodsUnit = 'kwh'
if (util.isNotNull(data) && data.length > 0) {
for (var i=0;i<data.length;i++) {
console.log("====data["+i+"]:"+data[i])
if (that.data.meterRealInfo.meterType === '电表' && data[i].type==='fee-ele') {
goodsList = data[i].data
} else if (that.data.meterRealInfo.meterType === '水表' && data[i].type==='fee-water') {
goodsList = data[i].data
goodsUnit = 'm³'
}
}
if (goodsList.length> 0) {
for (let index = 0; index < goodsList.length; index++) {
const e = goodsList[index];
e.goods = (util.isNotNull(that.data.meterRealInfo.meterCT) ?that.data.meterRealInfo.meterCT:1) * e.goods
e.money = e.goods * that.data.meterPrise
goodsList2.push(e)
}
}
}
console.log(goodsList2)
that.setData({
goodsList:goodsList2,
goodsUnit: goodsUnit
})
}
})
},
// 支付
sumit() {
console.log(1)
var that = this
// 创建订单
that.orderGet()
},
// 创建订单
orderGet () {
console.log("=========开始执行创建订单=========")
var that = this;
var token = wx.getStorageSync('token')
if(util.isNull(token)) {
util.showToast('请先登录!')
setTimeout(function(){
wx.navigateTo({
url: '../login/login'
})
},500)
return
}
var select1 = that.data.formData.select1
var goodsDatas = [{
"goods": that.data.goodsList[select1].goods, // 商品
"money": that.data.goodsList[select1].money // 花费金额
}]
var orderDatas = {
"tenantCode":that.data.tenantCode, // 商户号 必填
"meterCode": that.data.meterCode, // 表具编号 必填
}
var desc = that.data.meterRealInfo.meterType === '电表'?'电费充值':(that.data.meterRealInfo.meterType === '水表'?'水费充值':that.data.meterRealInfo.meterType + '充值')
var params = {
sysCustomer: sysConfig.app.sysCustomer,
"token":token, // 登录标识 必填
"goodsDatas": JSON.stringify(goodsDatas), // json数组格式的电费信息(数组视为了适配购买多个商品的情况)
"orderDatas": JSON.stringify(orderDatas), // 订单的其他信息(每个客户不一样)
"desc": desc // 订单描述 电费充值 固定文字
}
app.func.iotReq("/spro/order/get", params, function (result) {
var successs = result.status == 1 ? true : false;
if (successs) {
var info = result.info
console.log("=========创建订单号成功,订单号为:"+info.orderNo)
//获取支付签名
that.orderGetSign(info.orderNo)
}
})
},
// 获取支付签名
orderGetSign (orderNo) {
console.log("==========准备获取支付签名============")
var that = this;
var token = wx.getStorageSync('token')
var openId = wx.getStorageSync('openId')
that.setData({
"orderNo": orderNo,
})
var params = {
"sysCustomer": sysConfig.app.sysCustomer,
"token":token, // 登录标识 必填
"orderNo": orderNo, // 订单号
"payType": 'wx_xcx', // 支付方式 wx_xcx
"wxOpenId": openId // 用户的openId
}
app.func.iotReq("/spro/order/get-sign", params, function (result) {
var successs = result.status == 1 ? true : false;
console.log("=====result.state:"+result.status)
console.log("=====result.info:"+result.info)
if (successs) {
var sign = result.info
console.log("==============获取支付签名成功,支付签名为:"+sign)
// 调起支付
that.requestPayment(sign)
}
})
},
// 调起支付
requestPayment (sign) {
console.log("===========开始调起支付==============")
var that= this
let info;
if (util.isNotNull(sign)) {
if(typeof sign == String){
info = JSON.parse(sign);
}else{
info = sign;
}
wx.requestPayment({
'timeStamp': info.timeStamp + '',
'nonceStr': info.nonceStr,
'package': info.package,
'signType': info.signType,
'paySign': info.paySign,
'success': function (res) {
// 支付成功后回调
that.orderPayNotify()
},
'fail':function (res) {
util.showToast('支付失败!')
setTimeout(function(){
// 返回上一页
wx.navigateBack({
delta: 1
})
},500)
}
})
}
},
// 支付成功后回调
orderPayNotify () {
var that = this;
var params = {
"sysCustomer":sysConfig.app.sysCustomer,
"orderNo": that.data.orderNo, // 订单号
}
app.func.iotReq("/spro/order/pay-notify", params, function (result) {
var successs = result.status == 1 ? true : false;
if (successs) {
util.showToast('支付成功!')
// 通过商户号/表具号获取信息
that.queryMeterRealData()
}
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})
1.通过 java httClient 工具包 得到的HttpClient在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。简化开发步骤,构造专属微信支付接口的httpClient 。
2. 微信小程序的支付流程: (1)先创建业务订单号 (2)请求微信小程序JSAPI 接口生成预支付订单号 prepay_id (3) 从小程序客户端 发起请求调起支付 (4)微信平台根据商户后台预留的notify_url 回调地址,商户后台处理支付完成后的业务逻辑(修改订单状态为已完成,给微信后台返回支付状态等)(5)商户后台定时任务轮询订单支付状态,并修改业务数据