Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add H5 Pay #18

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions wepay-core/src/main/java/me/hao0/wepay/core/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ protected void buildConfigParams(final Map<String, String> params){
params.put(WepayField.MCH_ID, wepay.getMchId());
}

/**
* 构建企业配置参数
* @param params 参数
*/
protected void buildPartnerConfigParams(final Map<String, String> params) {
params.put(WepayField.MCH_ID, wepay.getMchId());
}

/**
* 构建签名参数
* @param params 支付参数
Expand Down
243 changes: 243 additions & 0 deletions wepay-core/src/main/java/me/hao0/wepay/core/Partners.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package me.hao0.wepay.core;

import me.hao0.common.date.Dates;
import me.hao0.common.security.MD5;
import me.hao0.wepay.model.Partner.*;
import me.hao0.wepay.model.enums.WepayField;
import me.hao0.wepay.util.RSA;
import me.hao0.wepay.util.RandomStrs;

import javax.annotation.PostConstruct;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
import java.util.TreeMap;

import static me.hao0.common.util.Preconditions.*;

/**
* 企业相关业务组件
* <p>
* 用于企业向微信用户银行卡付款
* 目前支持接口API的方式向指定微信用户的银行卡付款。
*
* @author [email protected]
* @since 2018/8/7
*/
public final class Partners extends Component {

/**
* 企业付款到银行卡url
*/
private static final String PAY_BANK = "https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank";

/**
* 企业付款到银行卡查询url
*/
private static final String QUERY_BANK = "https://api.mch.weixin.qq.com/mmpaysptrans/query_bank";

/**
* 获取RSA公钥url
*/
private static final String PUB_KEY = "https://fraud.mch.weixin.qq.com/risk/getpublickey";

// TODO 随便配啦
private static final String KEY_FILE_PATH = "";

private static RSAPublicKey PUBLIC_KEY;

Partners(Wepay wepay) {
super(wepay);
}

/**
* 公钥只加载一次,防止每次请求都加载
*
* @throws Exception
*/
@PostConstruct
private void loadPublicKey() throws Exception {
RSAPublicKey publicKey;
try {
String publicKeyStr = RSA.loadPublicKeyByFile(KEY_FILE_PATH);
publicKey = RSA.loadPublicKeyByStr(publicKeyStr);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
PUBLIC_KEY = publicKey;
}


/**
* 企业付款到银行卡API
*
* @param request
* @return
*/
public PayBankResponse payBank(PayBankRequest request) throws Exception {
checkPayBankParams(request);
Map<String, Object> response = doPayBank(request);
return buildPayBankResp(response);
}

/**
* 企业付款到银行卡查询API
*
* @param request
* @return
*/
public QueryBankResponse queryBank(QueryBankRequest request) {
checkNotNullAndEmpty(request.getPartnerTradeNo(), "partnerTradeNo");
Map<String, Object> response = doQueryBank(request);
return buildQueryBankResp(response);
}

/**
* 获取RSA公钥API
* <p>
* 1、 调用获取RSA公钥API获取RSA公钥,落地成本地文件,假设为public.pem
* 2、 确定public.pem文件的存放路径,同时修改代码中文件的输入路径,加载RSA公钥
* 3、 用标准的RSA加密库对敏感信息进行加密,选择RSA_PKCS1_OAEP_PADDING填充模式
* (eg:Java的填充方式要选 " RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING")
* 4、 得到进行rsa加密并转base64之后的密文
* 5、 将密文传给微信侧相应字段,如付款接口(enc_bank_no/enc_true_name)
*
* @return
*/
public Map<String, Object> getPublicKey() {
Map<String, String> queryBankParams = buildGetPublicKeyParams();
buildSignParams(queryBankParams);
return doPost(PUB_KEY, queryBankParams);
}


private Map<String, Object> doPayBank(PayBankRequest request) throws Exception {
Map<String, String> payBankParams = buildPayBankParams(request);
buildSignParams(payBankParams);
return doPost(PAY_BANK, payBankParams);
}

private Map<String, Object> doQueryBank(QueryBankRequest request) {
Map<String, String> queryBankParams = buildQueryBankParams(request);
buildSignParams(queryBankParams);
return doPost(QUERY_BANK, queryBankParams);
}


private PayBankResponse buildPayBankResp(Map<String, Object> data) {
String partnerId = wepay.getMchId();
String nonceStr = RandomStrs.generate(16);
String timeStamp = String.valueOf(Dates.now().getTime() / 1000);
String partnerTardeNo = String.valueOf(data.get(WepayField.PARTNER_TRADE_NO));
String amount = String.valueOf(data.get(WepayField.AMOUNT));
String paymentNo = String.valueOf(data.get(WepayField.PAYMENT_NO));
Integer cmmsAmt = Integer.parseInt(String.valueOf(data.get(WepayField.CMMS_AMT)));

// 加签
String signing =
WepayField.MCH_ID + "=" + partnerId +
"&" + WepayField.NONCESTR + "=" + nonceStr +
"&" + WepayField.PKG + "=Sign=WXPay" +
"&" + WepayField.PARTNERID + "=" + partnerId +
"&" + WepayField.TIMESTAMP + "=" + timeStamp +

"&" + WepayField.PARTNER_TRADE_NO + "=" + partnerTardeNo +
"&" + WepayField.AMOUNT + "=" + amount +
"&" + WepayField.PAYMENT_NO + "=" + paymentNo +
"&" + WepayField.CMMS_AMT + "=" + cmmsAmt +

"&" + WepayField.KEY + "=" + wepay.getAppKey();

String signed = MD5.generate(signing, false).toUpperCase();

return new PayBankResponse(paymentNo, cmmsAmt, partnerId, partnerTardeNo, amount, nonceStr, signed);
}


private QueryBankResponse buildQueryBankResp(Map<String, Object> data) {
QueryBankResponse response = new QueryBankResponse();
response.setMchId(wepay.getMchId());
response.setPartnerTradeNo(String.valueOf(data.get(WepayField.PARTNER_TRADE_NO)));
response.setPaymentNo(String.valueOf(data.get(WepayField.PAYMENT_NO)));
response.setBankNoMd5(String.valueOf(data.get(WepayField.BANK_NO_MD5))); // TODO MD5解密
response.setTrueNameMd5(String.valueOf(data.get(WepayField.TRUE_NAME_MD5))); // TODO MD5解密
response.setAmount(String.valueOf(data.get(WepayField.AMOUNT)));
response.setStatus(String.valueOf(data.get(WepayField.STATUS)));
response.setCmmsAmt(String.valueOf(data.get(WepayField.CMMS_AMT)));
response.setCreateTime(String.valueOf(data.get(WepayField.CREATE_TIME)));
response.setPaySuccTime(String.valueOf(data.get(WepayField.PAY_SUCC_TIME)));
response.setReason(String.valueOf(data.get(WepayField.REASON)));
return response;
}


private Map<String, String> buildPayBankParams(PayBankRequest request) throws Exception {
Map<String, String> payBankParams = new TreeMap<>();

// 配置参数
buildPartnerConfigParams(payBankParams);

/**
* 参数加密
*/
String encBankNo;
String encTrueName;
try {
encBankNo = RSA.encrypt(PUBLIC_KEY, request.getEncBankNo().getBytes());
encTrueName = RSA.encrypt(PUBLIC_KEY, request.getEncTrueName().getBytes());
} catch (Exception e) {
throw new Exception(e.getMessage());
}

// 业务必需参数
put(payBankParams, WepayField.PARTNER_TRADE_NO, request.getPartnerTradeNo());
put(payBankParams, WepayField.NONCE_STR, RandomStrs.generate(16));
put(payBankParams, WepayField.ENC_BANK_NO, encBankNo);// RSA加密
put(payBankParams, WepayField.ENC_TRUE_NAME, encTrueName); // RSA加密
put(payBankParams, WepayField.BANK_CODE, request.getBankCode());
put(payBankParams, WepayField.AMOUNT, request.getAmount() + "");

// 业务可选参数
putIfNotEmpty(payBankParams, WepayField.DESC, request.getDesc());

return payBankParams;
}

private Map<String, String> buildQueryBankParams(QueryBankRequest request) {

Map<String, String> queryBankParams = new TreeMap<>();

// 配置参数
buildPartnerConfigParams(queryBankParams);

// 业务必需参数
put(queryBankParams, WepayField.PARTNER_TRADE_NO, request.getPartnerTradeNo());
put(queryBankParams, WepayField.NONCE_STR, RandomStrs.generate(16));

return queryBankParams;
}

private Map<String, String> buildGetPublicKeyParams() {

Map<String, String> getPublicKeyParams = new TreeMap<>();

// 配置参数
buildPartnerConfigParams(getPublicKeyParams);

// 业务必需参数
put(getPublicKeyParams, WepayField.NONCE_STR, RandomStrs.generate(16));

return getPublicKeyParams;
}

private void checkPayBankParams(PayBankRequest request) {
checkNotNull(request, "pay bank request can't be null");
checkNotNullAndEmpty(request.getEncBankNo(), "encBankNo");
checkNotNullAndEmpty(request.getEncTrueName(), "encTrueName");
Integer totalFee = request.getAmount();
checkArgument(totalFee != null && totalFee > 0, "totalFee must > 0");
checkNotNullAndEmpty(request.getBankCode(), "BankCode");

}


}
86 changes: 78 additions & 8 deletions wepay-core/src/main/java/me/hao0/wepay/core/Pays.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
package me.hao0.wepay.core;

import me.hao0.common.date.Dates;
import me.hao0.common.json.Jsons;
import me.hao0.common.security.MD5;
import me.hao0.wepay.exception.WepayException;
import me.hao0.wepay.model.enums.TradeType;
import me.hao0.wepay.model.enums.WepayField;
import me.hao0.wepay.model.pay.AppPayResponse;
import me.hao0.wepay.model.pay.JsPayRequest;
import me.hao0.wepay.model.pay.JsPayResponse;
import me.hao0.wepay.model.pay.PayRequest;
import me.hao0.wepay.model.pay.QrPayRequest;
import me.hao0.wepay.model.pay.QrPayResponse;
import me.hao0.wepay.model.pay.*;
import me.hao0.wepay.util.RandomStrs;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;

import static me.hao0.common.util.Preconditions.*;

/**
Expand All @@ -41,6 +37,18 @@ protected Pays(Wepay wepay) {
super(wepay);
}

/**
* H5支付
*
* @param request 支付请求对象
* @return JsPayResponse对象,或抛WepayException
*/
public H5PayResponse h5Pay(H5PayRequest request) {
checkH5PayParams(request);
Map<String, Object> respData = doH5Pay(request, TradeType.MWEB);
return buildH5PayResp(respData);
}

/**
* JS支付(公众号支付)
* @param request 支付请求对象
Expand Down Expand Up @@ -113,6 +121,33 @@ private Map<String, Object> doJsPay(JsPayRequest request, TradeType tradeType){
return doPay(payParams);
}

/**
* H5支付
*
* @param request 支付信息
* @return 支付结果
*/
private Map<String, Object> doH5Pay(H5PayRequest request, TradeType tradeType) {
Map<String, String> payParams = buildPayParams(request, tradeType);
/**
* scene_info
*/
LinkedHashMap value = new LinkedHashMap();
// 暂时固定这个值,因为微信官方不推荐在app使用h5支付
value.put("type", "wap");
//WAP网站URL地址
value.put(WepayField.WAP_URL, request.getWapUrl());
//WAP 网站名
value.put(WepayField.WAP_NAME, request.getWapName());
Map<String, Object> sceneInfo = new HashMap<>();
// h5_info固定值
sceneInfo.put("h5_info", value);

payParams.put(WepayField.SCENE_INFO, Jsons.DEFAULT.toJson(sceneInfo));

return doPay(payParams);
}

/**
* APP支付
* @param request 支付信
Expand Down Expand Up @@ -181,6 +216,30 @@ private AppPayResponse buildAppPayResp(Map<String, Object> data) {
return new AppPayResponse(appId, partnerId, prepayId, timeStamp, nonceStr, signed);
}

private H5PayResponse buildH5PayResp(Map<String, Object> data) {

String appId = wepay.getAppId();
String prepayId = (String) data.get(WepayField.PREPAY_ID);
String mwebUrl = (String) data.get(WepayField.MWEB_URL);
String nonceStr = RandomStrs.generate(16);
String timeStamp = String.valueOf(new Date().getTime() / 1000);
String pkg = WepayField.PREPAY_ID + "=" +
data.get(WepayField.PREPAY_ID);

String signing =
WepayField.APPID + "=" + appId +
"&" + WepayField.NONCESTR2 + "=" + nonceStr +
"&" + WepayField.MWEB_URL + "=" + mwebUrl +
"&" + WepayField.PKG + "=" + pkg +
"&" + WepayField.SIGN_TYPE + "=MD5" +
"&" + WepayField.TIME_STAMP + "=" + timeStamp +
"&" + WepayField.KEY + "=" + wepay.getAppKey();

String signed = MD5.generate(signing, false).toUpperCase();

return new H5PayResponse(appId, timeStamp, nonceStr, pkg, "MD5", signed, prepayId, mwebUrl);
}

/**
* 检查支付参数合法性
* @param request 支付请求对象
Expand All @@ -190,6 +249,17 @@ private void checkJsPayParams(JsPayRequest request) {
checkNotNullAndEmpty(request.getOpenId(), "openId");
}

/**
* 检查支付参数合法性
*
* @param request 支付请求对象
*/
private void checkH5PayParams(H5PayRequest request) {
checkPayParams(request);
checkNotNullAndEmpty(request.getWapUrl(), "wapUrl");
checkNotNullAndEmpty(request.getWapName(), "wapName");
}

private void checkPayParams(PayRequest request) {
checkNotNull(request, "pay detail can't be null");
checkNotNullAndEmpty(request.getBody(), "body");
Expand Down
Loading