1.微信支付
1.1 了解微信支付相关基础知识
参考地址:
https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/ico-guide/chapter1_1.shtml
1.1.1 理解三种账号:
公众平台:服务号、订阅号,统称公众号,和小程序。
商户平台:微信支付。
开放平台:APP、网站、公众号(服务号、订阅号)。
1.1.2 设置相关参数
API密钥(KEY):用于接口API的签名计算。
APIv3密钥:在微信支付接口V3版本,为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。API v3密钥是解密时使用的对称密钥。
支付目录:JSAPI支付(公众号支付)、H5支付都要求发起支付请求的页面域名必须在商户平台配置后才可正常调用微信支付
授权域名:获取用户身份标识openid时,要求请求来源域名必须在公众平台配置过,才被允许获取用户身份标识openid
1.1.3 下载证书
api证书:微信退款的时候会用到
1.1.4 两种模式
普通商户:信息、资金流:微信支付—>直连商户
普通服务商:服务商下可签约特约商户、资金流直接到商户,服务商可以参与分成
1.2 了解微信支付产品
了解完这些可以再了解一下微信的几种方式来确定你需要使用的api文档,微信官方了解地址:
https://pay.weixin.qq.com/static/product/product_index.shtml
1.3 了解微信支付api文档
目前有两版api文档,自助支付的话使用V2版即可,V3版给定制商户使用的。
SDK下载地址:
https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1
开发步骤:
1)获取商户信息:appid、mch_id,如果是服务商,需要签约特殊商户来获取子商户sub_appid和sub_mch_id
2)生成签名,参考https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_3,微信SDK会帮你实现
3)包装请求参数:转xml格式,SDK也会帮你实现
4)分析请求结果,然后进行业务处理
5)如果是退款和撤销支付的话,需要携带证书
付款码支付代码参考:
微信配置pojo类参考:实现微信提供的抽象配置类
/**
* 商户微信配置信息
*/
public class WxPayAppConfig implements WXPayConfig {
/**
* appID
*/
private String appID;
/**
* 商户号
*/
private String mchID;
/**
* API 密钥
*/
private String key;
/**
* API证书绝对路径 (本项目放在了 resources/cert/wxpay/apiclient_cert.p12")
*/
private String certPath;
/**
* HTTP(S) 连接超时时间,单位毫秒
*/
private int httpConnectTimeoutMs = 8000;
/**
* HTTP(S) 读数据超时时间,单位毫秒
*/
private int httpReadTimeoutMs = 10000;
/**
* 微信支付异步通知地址
*/
private String payNotifyUrl;
/**
* 微信退款异步通知地址
*/
private String refundNotifyUrl;
/**
* 获取商户证书内容(这里证书需要到微信商户平台进行下载)
*
* @return 商户证书内容
*/
@Override
public InputStream getCertStream() {
File file = new File(certPath);
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
微信支付代码参考:
/**
* 微信付款码支付接口(注:用户付款码条形码规则:18位纯数字,以10、11、12、13、14、15开头)
*
* @param key: 商户key 必填
* @param mchId: 商户mchid 必填
* @param appId: 商户appid 必填
* @param authCode: 付款码, 必填
* @param subMchId: 子商户mchid,有服务商的子商户必填
* @param orderNo: 订单编号, 必填
* @param amount: 实际支付金额, 必填
* @param body: 订单描述 必填
* @param ip: 请求ip地址, 必填
* @param certPath: 证书请求路径,必填
* @return
*/
public ResultMap micropay(String key, String mchId, String appId, String authCode, String subMchId, String orderNo, double amount, String body, String ip, String certPath) {
// Map<String, String> returnMap = new HashMap<>();
// Map<String, String> responseMap = new HashMap<>();
Map<String, String> requestMap = new HashMap<>();
ResultMap returnMap = new ResultMap();
try {
if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(mchId) && StringUtils.isNotEmpty(appId) && StringUtils.isNotEmpty(certPath)) {
wxPayAppConfig.setKey(key);
wxPayAppConfig.setMchID(mchId);
wxPayAppConfig.setAppID(appId);
wxPayAppConfig.setCertPath(certPath);
if (null == wxPayAppConfig.getCertStream()) {
return ResultMap.error("证书路径不对或证书内容为空");
}
} else {
return ResultMap.error("商户系统参数必填");
}
WXPay wxpay = new WXPay(wxPayAppConfig);
requestMap.put("body", body); // 商品描述
requestMap.put("out_trade_no", orderNo); // 商户订单号
requestMap.put("auth_code", authCode); // 付款码
requestMap.put("sub_mch_id", subMchId); // 子商户mchId
requestMap.put("total_fee", String.valueOf((int) (amount * 100))); // 总金额
requestMap.put("spbill_create_ip", ip); // 终端IP
// requestMap.put("notify_url", wxPayAppConfig.getPayNotifyUrl()); // 接收微信支付异步通知回调地址
// Map<String, String> resultMap = wxpay.microPay(requestMap); //直接支付,若是没有输密码,或是其他原因没有重试机制
Map<String, String> resultMap = this.microPayWithPos(requestMap, wxpay, wxPayAppConfig.getHttpConnectTimeoutMs());
//获取返回码
String returnCode = resultMap.get("return_code");
String returnMsg = resultMap.get("return_msg");
//若返回码为SUCCESS,则会返回一个result_code,再对该result_code进行判断
if ("SUCCESS".equals(returnCode)) {
String resultCode = resultMap.get("result_code");
String errCodeDes = resultMap.get("err_code_des");
if ("SUCCESS".equals(resultCode)) {
logger.info("订单号:{},交易金额:{}", orderNo, resultMap.get("cash_fee"));
returnMap.put("data", resultMap);
} else if ("FAIL".equals(resultCode)) {
logger.info("订单号:{},错误描述:{}", orderNo, errCodeDes);
returnMap = ResultMap.error(errCodeDes);
} else if ("USERPAYING".equals(resultCode)) {
logger.info("订单号:{},错误描述:{}", orderNo, errCodeDes);
returnMap = ResultMap.error(errCodeDes);
}
} else {
logger.info("订单号:{},错误描述:{}", orderNo, returnMsg);
returnMap = ResultMap.error(returnMsg);
}
return returnMap;
} catch (Exception e) {
logger.error("订单号:{},错误信息:{}", orderNo, e.getMessage());
return ResultMap.error("微信付款码支付失败" + e.getMessage());
}
}
/**
* 提交刷卡支付,针对软POS,尽可能做成功
* 内置重试机制,最多60s
*
* @param reqData
* @param connectTimeoutMs
* @return
* @throws Exception
*/
public Map<String, String> microPayWithPos(Map<String, String> reqData, WXPay wxpay, int connectTimeoutMs) throws Exception {
int remainingTimeMs = 60 * 1000;
long startTimestampMs = 0;
Map<String, String> lastResult = null;
Exception lastException = null;
while (true) {
startTimestampMs = System.currentTimeMillis();
;
int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
if (readTimeoutMs > 1000) {
try {
lastResult = wxpay.microPay(reqData, connectTimeoutMs, readTimeoutMs);
String returnCode = lastResult.get("return_code");
if (returnCode.equals("SUCCESS")) {
String resultCode = lastResult.get("result_code");
String errCode = lastResult.get("err_code");
if (resultCode.equals("SUCCESS")) {
break;
} else {
// 看错误码,若支付结果未知,则重试提交刷卡支付
if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
remainingTimeMs = remainingTimeMs - (int) (System.currentTimeMillis() - startTimestampMs);
if (remainingTimeMs <= 100) {
break;
} else {
logger.info("microPayWithPos: " + errCode + "----try micropay again");
if (remainingTimeMs > 5 * 1000) {
Thread.sleep(5 * 1000);
} else {
Thread.sleep(1 * 1000);
}
continue;
}
} else {
HashMap<String, String> requestMap = new HashMap<>();
requestMap.put("out_trade_no", reqData.get("out_trade_no"));
wxpay.reverse(requestMap);
break;
}
}
} else {
HashMap<String, String> requestMap = new HashMap<>();
requestMap.put("out_trade_no", reqData.get("out_trade_no"));
wxpay.reverse(requestMap);
break;
}
} catch (Exception ex) {
lastResult = null;
lastException = ex;
break;
}
} else {
break;
}
}
if (lastResult == null) {
throw lastException;
} else {
return lastResult;
}
}
微信退款代码参考:
/**
* 微信付款码支付退款
*
* @param key: 商户key 必填
* @param mchId: 商户mchid 必填
* @param appId: 商户appid 必填
* @param subMchId: 子商户mchid,有服务商的子商户必填
* @param orderNo: 订单编号 二选一,次选
* @param transactionId: 微信订单号 二选一,优选
* @param totalFee: 订单金额 必填
* @param refundFee: 申请退款金额 必填
* @param refundReason: 退款原因
* @param outRefundNo: 退款订单号 必填
* @param certPath: 证书路径,服务商的商户是服务商的证书,普通商户是商户的证书 必填
* @return
*/
public ResultMap refund(String key, String mchId, String appId, String subMchId, String orderNo, String transactionId, double totalFee, double refundFee, String refundReason, String outRefundNo, String certPath) {
ResultMap returnMap = new ResultMap();
if (StringUtils.isBlank(orderNo)) {
return ResultMap.error("订单编号不能为空");
}
if (refundFee <= 0) {
return ResultMap.error("退款金额必须大于0");
}
if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(mchId) && StringUtils.isNotEmpty(appId) && StringUtils.isNotEmpty(certPath)) {
wxPayAppConfig.setKey(key);
wxPayAppConfig.setMchID(mchId);
wxPayAppConfig.setAppID(appId);
wxPayAppConfig.setCertPath(certPath);
if (null == wxPayAppConfig.getCertStream()) {
return ResultMap.error("证书路径不对或证书内容为空");
}
} else {
return ResultMap.error("商户系统参数必填");
}
Map<String, String> responseMap = new HashMap<>();
Map<String, String> requestMap = new HashMap<>();
WXPay wxpay = new WXPay(wxPayAppConfig);
if (StringUtils.isNotEmpty(transactionId)) {
requestMap.put("transaction_id", transactionId);
} else {
requestMap.put("out_trade_no", orderNo);
}
requestMap.put("sub_mch_id", subMchId);
requestMap.put("out_refund_no", outRefundNo);
requestMap.put("total_fee", String.valueOf((int) (totalFee * 100)));
requestMap.put("refund_fee", String.valueOf((int) (refundFee * 100)));//所需退款金额
requestMap.put("refund_desc", refundReason);
try {
responseMap = wxpay.refund(requestMap);
} catch (Exception e) {
e.printStackTrace();
}
String return_code = responseMap.get("return_code"); //返回状态码
String return_msg = responseMap.get("return_msg"); //返回信息
if ("SUCCESS".equals(return_code)) {
String result_code = responseMap.get("result_code"); //业务结果
String err_code_des = responseMap.get("err_code_des"); //错误代码描述
if ("SUCCESS".equals(result_code)) {
//表示退款申请接受成功,结果通过退款查询接口查询
//修改用户订单状态为退款申请中或已退款。退款异步通知根据需求,可选
//
return returnMap.put("date", responseMap);
} else {
logger.info("订单号:{}错误信息:{}", orderNo, err_code_des);
return ResultMap.error(err_code_des);
}
} else {
logger.info("订单号:{}错误信息:{}", orderNo, return_msg);
return ResultMap.error(return_msg);
}
}
以上是微信支付部分讲解。
2.支付宝支付
2.1 商户入驻支付宝并创建应用
官方参考指南:
https://opendocs.alipay.com/open/200/105304
自己整理参考指南:https://blog.csdn.net/weixin_42556829/article/details/105051497
主要为了使商户入驻支付宝,创建属于自己的收款应用,产生1.APPID 2.支付宝公钥 3.商户应用公钥 4.商户应用私钥,用户后面支付的商户配置信息。
2.2 开发参考指南
支付宝SDK下载地址
https://opendocs.alipay.com/open/194/105201
支付宝支付API官方地址:
https://opendocs.alipay.com/apis/api_1/alipay.trade.pay
个人感觉api有点乱,可以从产品介绍找对应的接口地址比较好一点,参考:https://opendocs.alipay.com/open/194/105072
2.3 代码参考
支付宝支付代码:
private static final String ALIPAY_TRADE_GATEWAY = "https://openapi.alipay.com/gateway.do";
private static final String SIGN_TYPE = "MD5";
private static final String INPUT_CHARSET = "utf-8";
private static final String PRODUCT_CODE = "BARCODE_PAY_OFFLINE";
private static final String DYNAMIC_ID_TYPE = "barcode";
private static final String FORMAT = "JSON";
private static final String SCENE = "bar_code";
/**
* 支付宝付款码支付
* @param appId:商户appid
* @param privateKey:商户私钥
* @param alipayPulicKey:支付宝公钥
* @param outTradeNo:订单号
* @param authCode:付款码
* @param totalAmount:支付金额,单位元
* @param subject:订单标题, 以上必填
* @param alipayServicePid:系统商编号 isv服务商下的商户可填
* 该参数作为系统商返佣数据提取的依据,请填写系统商签约协议的PID
* @param alipayStoreId:口碑门店id,若没有为null
* @param alipaySignType:生成签名的算法类型,默认:RSA,阿里推荐RSA2
* @param scene:支付场景:默认bar_code,条码支付
* @param appAuthToken:第三方应用授权token,ISV服务商下的商户可填
* @return
*/
public static ResultMap tradePayRequest(String appId, String privateKey, String alipayPulicKey, String outTradeNo, String authCode, String totalAmount, String subject, String alipayServicePid, String alipayStoreId, String alipaySignType, String scene, String appAuthToken) {
AlipayClient alipayClient = new DefaultAlipayClient(ALIPAY_TRADE_GATEWAY, appId, privateKey, FORMAT, INPUT_CHARSET, alipayPulicKey,alipaySignType);
AlipayTradePayRequest payRequest = new AlipayTradePayRequest();
if (StringUtils.isBlank(scene)){
scene = SCENE;
}
Map<String, Object> requestMap = new HashMap<String, Object>();
requestMap.put("out_trade_no", outTradeNo);
requestMap.put("scene", scene);
requestMap.put("auth_code", authCode);
requestMap.put("total_amount", totalAmount);
requestMap.put("subject", subject);
if(StringUtils.isNotEmpty(alipayStoreId)){
requestMap.put("alipay_store_id", alipayStoreId);
}if(StringUtils.isNotEmpty(appAuthToken)){
requestMap.put("app_auth_token", appAuthToken);
}
if (StringUtils.isNotEmpty(alipayServicePid) ) {
Map<String, Object> extendParamsMap = new HashMap<String, Object>();
extendParamsMap.put("sys_service_provider_id", alipayServicePid);
requestMap.put("extend_params", extendParamsMap);
}
try {
payRequest.setBizContent(new ObjectMapper().writeValueAsString(requestMap));
} catch (IOException e) {
logger.error("AlipayTradeUtils.tradePayRequest - Map转Json异常 - " + e.getClass().getSimpleName() + " - " + e.getMessage());
}
try {
AlipayTradePayResponse response = alipayClient.execute(payRequest);
if (response != null && "10000".equals(response.getCode())) {
return new ResultMap().put("data",response);
} else {
return ResultMap.error(response!=null?response.getSubMsg():"支付失败,支付宝未返回。");
}
} catch (AlipayApiException e) {
logger.error("AlipayTradeUtils.tradePayRequest - 获取Response异常 - " + e.getClass().getSimpleName() + " - " + e.getMessage());
return ResultMap.error(e.getClass().getSimpleName() + " - " + e.getMessage());
}
}
3.银联支付
1.商户注册
进行商户注册获取配置信息,参考地址:
https://ump.chinaums.com/v2/pages/perp/mobileRegist.jsp?id=10059
2.开发文档
开发API参考地址:
https://open.chinaums.com/resources/?code=151539797785864&url=b7abc3a6-0c49-43d4-ad7d-f6dd16ff35eb
SDK下载地址:https://open.chinaums.com/resources/?code=101528298407846&url=8838eff5-3809-4f8e-aae2-2c5b00ef265e
各种支付方式的API参考文档地址:https://open.chinaums.com/resources/?code=651539656974952&url=b7abc3a6-0c49-43d4-ad7d-f6dd16ff35eb
3.代码参考
// private String umsUrl = "http://58.247.0.18:29015/"
// private String umsUrl = "https://api-mop.chinaums.com/"
private String umsUrl = PropertyUtils.getDefault("ums.umsUrl");
/**
* 银联B2C扫码支付
* @param params
* @return
*/
def umsPay(Map params) {
LogUtil.logInfo("银联扫码支付参:" + (params as JSON).toString())
String enabled = PropertyUtils.getDefault("ums.enabled");
if (StringUtils.isNotEmpty(enabled) && enabled.equals("false")) {
return new ApiRest(false, "银联支付服务已停止");
}
ApiRest r = new ApiRest();
String url = umsUrl;
// String url = "https://test-api-open.chinaums.com/";
//交易金额
String transactionAmount = params.get("transactionAmount")
//交易币种:156
String transactionCurrencyCode = params.get("transactionCurrencyCode")
//商户订单号
String merchantOrderId = params.get("merchantOrderId")
//商户备注
String merchantRemark = params.get("merchantRemark")
//支付方式,E_CASH – 电子现金,SOUNDWAVE– 声波,NFC – NFC,CODE_SCAN– 扫码,MANUAL –手输
String payMode = params.get("payMode")
//支付码
String payCode = params.get("payCode")
if (StringUtils.isEmpty(transactionAmount)||StringUtils.isEmpty(transactionCurrencyCode)||StringUtils.isEmpty(merchantOrderId)||StringUtils.isEmpty(merchantRemark)||StringUtils.isEmpty(payMode)||StringUtils.isEmpty(payCode)) {
return ApiRest.INVALID_PARAMS_ERROR;
}
try {
//获取商户支付信息 开始
SysPayAccount sysPayAccount = findSysPayAccount((String) params.get("tenantId"), (String) params.get("branchId"), (String) params.get("pBranchId"), "umsPay");
if (sysPayAccount == null) {
r.setIsSuccess(false)
r.setCode("NOACCOUNT")
r.setError("商户未开通或未设置支付宝支付功能")
return r;
}
LogUtil.logInfo("银联支付账号:" + (sysPayAccount as JSON).toString())
savePayLogEvent("PayService.umsPay", "银联扫码支付:" + (sysPayAccount as JSON), sysPayAccount.umsMid, sysPayAccount.tenantId, 4)
//开发者ID
String appId = sysPayAccount.getUmsAppid()
//开发者秘钥
String appKey = sysPayAccount.getUmsAppkey()
//商户号
String merchantCode = sysPayAccount.getUmsMid()
//终端编号
String terminalCode = sysPayAccount.getUmsTid()
//transactionCurrencyCode
// String transactionCurrencyCode = "156"
// String payMode = "CODE_SCAN"
//获取access token
// String accessToken = CacheUtils.get("_ums_access_token:"+(String) params.get("branchId"))
// if (StringUtils.isEmpty(accessToken)){
// TokenResponse tokenResponse = getUmsToken(appId, appKey, url)
// accessToken = tokenResponse.getAccessToken()
// CacheUtils.set("_ums_access_token:"+(String) params.get("branchId"),accessToken,3000)
// }
//实例化客户端
OpenApiClient openApiClient = new DefaultOpenApiClient(url, appId, appKey);
//根据请求接口拼装参数
UmsPayRequest umsPayRequest = new UmsPayRequest();
def requestMap = new HashMap<>()
requestMap.put("merchantCode", merchantCode)
requestMap.put("terminalCode", terminalCode)
requestMap.put("transactionCurrencyCode", transactionCurrencyCode)
requestMap.put("payMode", payMode)
requestMap.put("transactionAmount", transactionAmount)
requestMap.put("merchantRemark", merchantRemark)
requestMap.put("merchantOrderId", merchantOrderId)
requestMap.put("payCode", payCode)
requestMap.put("storeId", params.get("branchId"))
//商品信息,数组-JSON
if (StringUtils.isNotBlank(params.get("goods"))){
requestMap.put("goods", params.get("goods"))
}
//商户冗余信息
if (StringUtils.isNotBlank(params.get("srcReserved"))){
requestMap.put("srcReserved", params.get("srcReserved"))
}
//是否限制信用卡
if (StringUtils.isNotBlank(params.get("limitCreditCard"))){
requestMap.put("limitCreditCard", params.get("limitCreditCard"))
}
//操作员编号
if (StringUtils.isNotBlank(params.get("operatorId"))){
requestMap.put("operatorId", params.get("operatorId"))
}
//业务标识,标识接入的具体业务,除非特殊说明,一般不需要上送
if (StringUtils.isNotBlank(params.get("bizIdentifier"))){
requestMap.put("bizIdentifier", params.get("bizIdentifier"))
}
//商品标识
if (StringUtils.isNotBlank(params.get("goodsTag"))){
requestMap.put("goodsTag", params.get("goodsTag"))
}
umsPayRequest.setData((requestMap as JSON).toString())
// if (StringUtils.isNotEmpty(accessToken)){
// umsPayRequest.setNeedToken(false)
// }
//请求调用返回
BankVerifyResponse bankVerifyResponse = openApiClient.execute(umsPayRequest,null);
// System.out.print("PayService.umsPay,返回结果"+bankVerifyResponse.data)
if ("00".equals(bankVerifyResponse.errCode)){//返回处理 示例仅对于当前接口,具体返回格式处理,请以真实接口为准进行处理
log.info(bankVerifyResponse.getData().toString());
log.info(bankVerifyResponse.getResultCode());
log.info(bankVerifyResponse.getResultInfo());
//保存订单记录
com.alibaba.fastjson.JSONObject parse = com.alibaba.fastjson.JSON.parse(bankVerifyResponse.errInfo)
savePayInfo(parse.get("thirdPartyBuyerId"), parse.get("orderId"), params.get("merchantOrderId"), params["payCode"], params["tenantId"] ? params["tenantId"].asType(BigInteger) : BigInteger.ZERO, params.get("branchId").asType(BigInteger), parse.get("transactionAmount").asType(BigDecimal) / 100, params["type"]?params["type"].asType(Integer):6, 1, params["opFrom"] ? params["opFrom"].asType(Integer) : 1)
r = new ApiRest()
r.message = "银联支付成功"
r.data = bankVerifyResponse.errInfo
r.code = bankVerifyResponse.errCode
r.isSuccess = true
} else {
r = new ApiRest()
r.isSuccess = false
r.code = bankVerifyResponse.errCode
r.data = bankVerifyResponse.errInfo
r.message = "银联支付失败"
}
} catch (Exception e) {
r.isSuccess = false;
r.message = "银联支付异常";
r.error = e.getMessage();
LogUtil.logError("PayService.umsPay for Usages in All Places...(${params}) - ${e.getMessage()}");
}
return r;
}