1、微信文档
https://pay.weixin.qq.com/doc/v3/merchant/4012716434
2、接口说明
- 支持商户:
【普通商户】 - 请求方式:
【POST】/v3/fund-app/mch-transfer/transfer-bills - 请求域名:
【主域名】https://api.mch.weixin.qq.com
3、代码实现
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.http.*;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.HashMap;
@Slf4j
public class WxPayUtils {
/**
* 发起转账
*
* @param outBillNo 商户单号
* @param openId openId
* @param userName 用户实名
* @param amount 转账金额(单位:分)
*/
public void createTransferBills(String outBillNo, String openId, String userName, Long amount) {
OkHttpClient okHttpClient = new OkHttpClient();
HttpClient httpClient = new DefaultHttpClientBuilder()
.config(rsaAutoCertificateConfig())
.okHttpClient(okHttpClient)
.build();
HttpHeaders headers = new HttpHeaders();
headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
headers.addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue());
headers.addHeader("Wechatpay-Serial", "商户API证书序列号");
HashMap<Object, Object> map = new HashMap<>();
// 商户AppID
map.put("appid", "小程序APPID");
// 商户单号
map.put("out_bill_no", outBillNo);
// 转账场景ID
map.put("transfer_scene_id", "1001");
// 收款用户OpenID
map.put("openid", openId);
// 收款用户姓名(需要加密传入)
if (StringUtils.isNotBlank(userName)) {
map.put("user_name", rsaEncryptOAEP(userName));
}
// 转账金额(单位:分)
map.put("transfer_amount", amount);
// 转账备注
map.put("transfer_remark", "2020年4月报销");
// 回调通知地址
map.put("notify_url", "https://www.xxx.com/wechat/notify/transferNotify");
// 用户收款感知
map.put("user_recv_perception", "现金奖励");
// 转账场景报备信息
JSONArray jsonArray = new JSONArray();
jsonArray.add(new JSONObject().fluentPut("info_type", "活动名称").fluentPut("info_content", "新会员有礼"));
jsonArray.add(new JSONObject().fluentPut("info_type", "奖励说明").fluentPut("info_content", "注册会员抽奖一等奖"));
map.put("transfer_scene_report_infos", jsonArray);
JsonRequestBody build = new JsonRequestBody.Builder()
.body(JSONUtil.toJsonStr(map))
.build();
HttpRequest executeSendGetHttpRequest = new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url("https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills")
.headers(headers)
.body(build)
.build();
try {
HttpResponse<JSONObject> execute = httpClient.execute(executeSendGetHttpRequest, JSONObject.class);
JSONObject responseBody = execute.getServiceResponse();
log.info("发起转账返回:{}", responseBody.toJSONString());
} catch (ServiceException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* API安全加密配置
*/
private RSAAutoCertificateConfig rsaAutoCertificateConfig() {
return new RSAAutoCertificateConfig.Builder()
// 商户号
.merchantId("xxxxxxxxxxx")
// 商户API证书私钥的存放路径
.privateKeyFromPath("v3/apiclient_key.pem")
// 商户API证书序列号
.merchantSerialNumber("xxxxxxxxxxx")
// APIv3密钥
.apiV3Key("xxxxxxxxxxx")
.build();
}
/**
* 敏感信息加密
*/
private String rsaEncryptOAEP(String message) {
X509Certificate cert = getX509Certificate();
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, cert.getPublicKey());
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] cipherdata = cipher.doFinal(data);
return Base64.getEncoder().encodeToString(cipherdata);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取 X509Certificate
*/
private X509Certificate getX509Certificate() {
ClassLoader classLoader = this.getClass().getClassLoader();
try (InputStream in = classLoader.getResourceAsStream("v3/platform_cert.pem")) {
if (in == null) {
throw new IOException("Resource not found: v3/platform_cert.pem");
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(in);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
请求响应得到:
{
"body": {
"out_bill_no": "plfk2020042013",
"transfer_bill_no": "1330000071100999991182020050700019480001",
"create_time": "2015-05-20T13:29:35.120+08:00",
"state": "ACCEPTED",
"fail_reason": "PAYEE_ACCOUNT_ABNORMAL",
"package_info": "affffddafdfafddffda=="
}
}
- state,单据状态,商家转账订单状态
ACCEPTED: 转账已受理
PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。
WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
SUCCESS: 转账成功
FAIL: 转账失败
CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
CANCELLED: 转账撤销完成
返回小程序端需要的参数
{
"mchId ": "商户号",
"appId": "商户AppID",
"package": "跳转页面的package信息"
}
小程序端就可以拉起确认收款的界面了
如您在阅读中发现不足,欢迎留言!!!