微信V3支付与微信V3合单支付案例实现

This commit is contained in:
egzosn
2021-10-06 16:35:32 +08:00
parent 6885cacfc5
commit 175fa42977
8 changed files with 368 additions and 93 deletions

View File

@@ -0,0 +1,307 @@
package com.egzosn.pay.demo.controller;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.egzosn.pay.common.bean.CertStoreType;
import com.egzosn.pay.common.bean.RefundOrder;
import com.egzosn.pay.demo.request.QueryOrder;
import com.egzosn.pay.demo.service.handler.WxV3CombinePayMessageHandler;
import com.egzosn.pay.web.support.HttpRequestNoticeParams;
import com.egzosn.pay.wx.v3.api.WxCombinePayService;
import com.egzosn.pay.wx.v3.api.WxPayConfigStorage;
import com.egzosn.pay.wx.v3.bean.WxTransactionType;
import com.egzosn.pay.wx.v3.bean.combine.CombineAmount;
import com.egzosn.pay.wx.v3.bean.combine.CombineCloseOrder;
import com.egzosn.pay.wx.v3.bean.combine.CombinePayOrder;
import com.egzosn.pay.wx.v3.bean.combine.CombineSubOrder;
import com.egzosn.pay.wx.v3.bean.order.H5Info;
import com.egzosn.pay.wx.v3.bean.order.SceneInfo;
import com.egzosn.pay.wx.v3.bean.order.SubOrder;
import com.egzosn.pay.wx.v3.bean.response.WxRefundResult;
/**
* 微信V3合单发起支付入口
*
* @author egan
* email egzosn@gmail.com
* date 2016/11/18 0:25
*/
@RestController
@RequestMapping("wxV3combine")
public class WxV3CombinePayController {
private WxCombinePayService service = null;
@PostConstruct //没有证书的情况下注释掉,避免启动报错
public void init() {
WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage();
wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c");
wxPayConfigStorage.setMchId("1602947766");
//V3密钥 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml
wxPayConfigStorage.setV3ApiKey("9bd8f0e7af4841299d782406b7774f56");
wxPayConfigStorage.setNotifyUrl("http://sailinmu.iok.la/wxV3/payBack.json");
wxPayConfigStorage.setReturnUrl("http://sailinmu.iok.la/wxV3/payBack.json");
wxPayConfigStorage.setInputCharset("utf-8");
//使用证书时设置为true
wxPayConfigStorage.setCertSign(true);
//商户API证书 https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml
wxPayConfigStorage.setApiClientKeyP12("E:\\Documents\\gitee\\支付\\yifenli_mall.p12");
wxPayConfigStorage.setCertStoreType(CertStoreType.PATH);
service = new WxCombinePayService(wxPayConfigStorage);
//设置回调消息处理
//TODO {@link com.egzosn.pay.demo.controller.WxPayController#payBack}
service.setPayMessageHandler(new WxV3CombinePayMessageHandler());
}
/**
* 跳到支付页面
* 针对实时支付
*
* @return 跳到支付页面
*/
@RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8")
public String toPay() {
CombinePayOrder order = new CombinePayOrder();
SceneInfo sceneInfo = new SceneInfo();
sceneInfo.setPayerClientIp("用户终端IP ");
sceneInfo.setDeviceId("终端设备号门店号或收银设备ID 。为了方便问题定位H5支付场景下该字段必填");
sceneInfo.setH5Info(new H5Info("场景类型,枚举值:\n" +
"iOSIOS移动应用\n" +
"Android安卓移动应用\n" +
"WapWAP网站应用"));
order.setSceneInfo(sceneInfo);
order.setCombineOutTradeNo("合单商户订单号");
//子单信息最多50单.
List<SubOrder> subOrders = new ArrayList<>();
SubOrder subOrder = new SubOrder();
subOrder.setMchid("子单商户号");
subOrder.setAttach("附加数据在查询API和支付通知中原样返回可作为自定义参数使用。 ");
//"子单金额,单位为分。 "
subOrder.setAmount(new CombineAmount(121));
subOrder.setOutTradeNo("子单商户订单号 ");
subOrder.setDescription("商品描述");
subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
subOrders.add(subOrder);
order.setSubOrders(subOrders);
order.setTransactionType(WxTransactionType.COMBINE_H5);
return service.toPay(order);
}
/**
* 公众号支付,小程序
*
* @return 返回jsapi所需参数
*/
@RequestMapping(value = "jsapi")
public Map jsapi() {
CombinePayOrder order = new CombinePayOrder();
order.setTransactionType(WxTransactionType.COMBINE_JSAPI);
order.setCombineOutTradeNo("合单商户订单号");
order.setOpenid("使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。 ");
//子单信息最多50单.
List<SubOrder> subOrders = new ArrayList<>();
SubOrder subOrder = new SubOrder();
subOrder.setMchid("子单商户号");
subOrder.setAttach("附加数据在查询API和支付通知中原样返回可作为自定义参数使用。 ");
//"子单金额,单位为分。 "
subOrder.setAmount(new CombineAmount(111));
subOrder.setOutTradeNo("子单商户订单号 ");
subOrder.setDescription("商品描述");
subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
subOrders.add(subOrder);
order.setSubOrders(subOrders);
Map orderInfo = service.orderInfo(order);
orderInfo.put("code", 0);
return orderInfo;
}
/**
* 获取支付预订单信息
*
* @return 支付预订单信息
*/
@RequestMapping("app")
public Map<String, Object> app() {
CombinePayOrder order = new CombinePayOrder();
order.setTransactionType(WxTransactionType.COMBINE_APP);
order.setCombineOutTradeNo("合单商户订单号");
//子单信息最多50单.
List<SubOrder> subOrders = new ArrayList<>();
SubOrder subOrder = new SubOrder();
subOrder.setMchid("子单商户号");
subOrder.setAttach("附加数据在查询API和支付通知中原样返回可作为自定义参数使用。 ");
//"子单金额,单位为分。 "
subOrder.setAmount(new CombineAmount(211));
subOrder.setOutTradeNo("子单商户订单号 ");
subOrder.setDescription("商品描述");
subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
subOrders.add(subOrder);
order.setSubOrders(subOrders);
Map orderInfo = service.orderInfo(order);
orderInfo.put("code", 0);
return orderInfo;
}
/**
* 获取二维码图像
* 二维码支付
*
* @return 二维码图像
* @throws IOException IOException
*/
@RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8")
public byte[] toWxQrPay() throws IOException {
CombinePayOrder order = new CombinePayOrder();
order.setTransactionType(WxTransactionType.COMBINE_NATIVE);
order.setCombineOutTradeNo("合单商户订单号");
//子单信息最多50单.
List<SubOrder> subOrders = new ArrayList<>();
SubOrder subOrder = new SubOrder();
subOrder.setMchid("子单商户号");
subOrder.setAttach("附加数据在查询API和支付通知中原样返回可作为自定义参数使用。 ");
//"子单金额,单位为分。 "
subOrder.setAmount(new CombineAmount(131));
subOrder.setOutTradeNo("子单商户订单号 ");
subOrder.setDescription("商品描述");
subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
subOrders.add(subOrder);
order.setSubOrders(subOrders);
//获取对应的支付账户操作工具可根据账户id
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(service.genQrPay(order), "JPEG", baos);
return baos.toByteArray();
}
/**
* 获取二维码地址
* 二维码支付
*
* @return 二维码图像
* @throws IOException IOException
*/
@RequestMapping(value = "getQrPay.json")
public String getQrPay() {
CombinePayOrder order = new CombinePayOrder();
order.setTransactionType(WxTransactionType.COMBINE_NATIVE);
order.setCombineOutTradeNo("合单商户订单号");
//子单信息最多50单.
List<SubOrder> subOrders = new ArrayList<>();
SubOrder subOrder = new SubOrder();
subOrder.setMchid("子单商户号");
subOrder.setAttach("附加数据在查询API和支付通知中原样返回可作为自定义参数使用。1 ");
//"子单金额,单位为分。 "
subOrder.setAmount(new CombineAmount(115));
subOrder.setOutTradeNo("子单商户订单号 ");
subOrder.setDescription("商品描述");
subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
subOrders.add(subOrder);
order.setSubOrders(subOrders);
//获取对应的支付账户操作工具可根据账户id
return service.getQrPay(order);
}
/**
* 支付回调地址
*
* @param request 请求
* @return 是否成功
* <p>
* 业务处理在对应的PayMessageHandler里面处理在哪里设置PayMessageHandler详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)}
* <p>
* 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler}
* @throws IOException IOException
*/
@RequestMapping(value = "payBack.json")
public String payBack(HttpServletRequest request) {
//业务处理在对应的PayMessageHandler里面处理在哪里设置PayMessageHandler详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler()
return service.payBack(new HttpRequestNoticeParams(request)).toMessage();
}
/**
* 查询
*
* @param order 订单的请求体
* @return 返回查询回来的结果集,支付方原值返回
*/
@RequestMapping("query")
public Map<String, Object> query(QueryOrder order) {
return service.query(order.getTradeNo(), order.getOutTradeNo());
}
/**
* 交易关闭接口
*
* @return 返回支付方交易关闭后的结果
*/
@RequestMapping("close")
public Map<String, Object> close() {
CombineCloseOrder order = new CombineCloseOrder();
order.setOutTradeNo("合单商户订单号");
//子单信息最多50单.
List<CombineSubOrder> subOrders = new ArrayList<>();
CombineSubOrder subOrder = new CombineSubOrder();
subOrder.setMchid("子单商户号");
subOrder.setOutTradeNo("子单商户订单号 ");
subOrder.setSubMchid("二级商户商户号,由微信支付生成并下发。服务商子商户的商户号,被合单方。直连商户不用传二级商户号。 ");
subOrders.add(subOrder);
order.setSubOrders(subOrders);
return service.close(order);
}
/**
* 申请退款接口
*
* @param order 订单的请求体
* @return 返回支付方申请退款后的结果
*/
@RequestMapping("refund")
public WxRefundResult refund(RefundOrder order) {
return service.refund(order);
}
/**
* 查询退款
*
* @param order 订单的请求体
* @return 返回支付方查询退款后的结果
*/
@RequestMapping("refundquery")
public Map<String, Object> refundquery(RefundOrder order) {
return service.refundquery(order);
}
/**
* 下载对账单
*
* @param order 订单的请求体
* @return 返回支付方下载对账单的结果
*/
@RequestMapping("downloadbill")
public Object downloadBill(QueryOrder order) {
return service.downloadBill(order.getBillDate(), order.getBillType());
}
}

View File

@@ -19,19 +19,16 @@ import org.springframework.web.bind.annotation.RestController;
import com.egzosn.pay.common.bean.CertStoreType;
import com.egzosn.pay.common.bean.PayOrder;
import com.egzosn.pay.common.bean.RefundOrder;
import com.egzosn.pay.common.bean.TransferOrder;
import com.egzosn.pay.demo.request.QueryOrder;
import com.egzosn.pay.demo.service.handler.WxV3PayMessageHandler;
import com.egzosn.pay.web.support.HttpRequestNoticeParams;
import com.egzosn.pay.wx.bean.WxBank;
import com.egzosn.pay.wx.bean.WxTransferType;
import com.egzosn.pay.wx.v3.api.WxPayConfigStorage;
import com.egzosn.pay.wx.v3.api.WxPayService;
import com.egzosn.pay.wx.v3.bean.WxTransactionType;
import com.egzosn.pay.wx.v3.bean.response.WxRefundResult;
/**
* 发起支付入口
* 微信V3发起支付入口
*
* @author egan
* email egzosn@gmail.com
@@ -44,13 +41,8 @@ public class WxV3PayController {
private WxPayService service = null;
@PostConstruct //没有证书的情况下注释掉,避免启动报错
public void init() {
System.out.println("v3 init");
WxPayConfigStorage wxPayConfigStorage = new WxPayConfigStorage();
wxPayConfigStorage.setAppId("wxc7b993ff15a9f27c");
wxPayConfigStorage.setMchId("1602947766");
@@ -161,8 +153,6 @@ public class WxV3PayController {
}
/**
* 支付回调地址
*
@@ -175,7 +165,7 @@ public class WxV3PayController {
* @throws IOException IOException
*/
@RequestMapping(value = "payBack.json")
public String payBack(HttpServletRequest request) {
public String payBack(HttpServletRequest request) {
//业务处理在对应的PayMessageHandler里面处理在哪里设置PayMessageHandler详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler()
return service.payBack(new HttpRequestNoticeParams(request)).toMessage();
}
@@ -239,68 +229,4 @@ public class WxV3PayController {
}
/**
* 转账到余额
*
* @param order 转账订单
* @return 对应的转账结果
*/
@RequestMapping("transfer")
public Map<String, Object> transfer(TransferOrder order) {
order.setOutNo("partner_trade_no 商户转账订单号");
order.setPayeeAccount("用户openid");
order.setPayeeName("收款用户姓名, 非必填,如果填写将强制验证收款人姓名");
order.setRemark("转账备注, 非必填");
order.setAmount(new BigDecimal(10));
//转账到余额,这里默认值是转账到银行卡
order.setTransferType(WxTransferType.TRANSFERS);
return service.transfer(order);
}
/**
* 转账到银行卡
*
* @param order 转账订单
* @return 对应的转账结果
*/
@RequestMapping("transferPayBank")
public Map<String, Object> transferPayBank(TransferOrder order) {
order.setOutNo("partner_trade_no 商户转账订单号");
//采用标准RSA算法公钥由微信侧提供,将公钥信息配置在PayConfigStorage#setKeyPublic(String)
order.setPayeeAccount("enc_bank_no 收款方银行卡号");
order.setPayeeName("收款方用户名");
order.setBank(WxBank.ABC);
order.setRemark("转账备注, 非必填");
order.setAmount(new BigDecimal(10));
//转账到银行卡,这里默认值是转账到银行卡
order.setTransferType(WxTransferType.PAY_BANK);
return service.transfer(order);
}
/**
* 转账查询
*
* @param outNo 商户转账订单号
* @param wxTransferType 微信转账类型,
* .....这里没办法了只能这样写(┬_┬),请见谅
* {@link WxTransferType#QUERY_BANK}
* {@link WxTransferType#GETTRANSFERINFO}
*
* <p>
* <a href="https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3">企业付款到零钱</a>
* <a href="https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_3">商户企业付款到银行卡</a>
* </p>
* @return 对应的转账订单
*/
@RequestMapping("transferQuery")
public Map<String, Object> transferQuery(String outNo, String wxTransferType) {
//默认查询银行卡的记录 com.egzosn.pay.wx.v3.bean.WxTransferType#QUERY_BANK
return service.transferQuery(outNo, wxTransferType);
}
}

View File

@@ -0,0 +1,29 @@
package com.egzosn.pay.demo.service.handler;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.egzosn.pay.common.api.DefaultPayMessageHandler;
import com.egzosn.pay.common.api.PayMessageHandler;
import com.egzosn.pay.common.api.PayService;
import com.egzosn.pay.common.bean.PayOutMessage;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.wx.v3.bean.combine.CombinePayMessage;
/**
* 微信合单支付回调处理器
* Created by ZaoSheng on 2016/6/1.
*/
public class WxV3CombinePayMessageHandler implements PayMessageHandler<CombinePayMessage, PayService> {
private final Logger LOG = LoggerFactory.getLogger(DefaultPayMessageHandler.class);
@Override
public PayOutMessage handle(CombinePayMessage payMessage, Map<String, Object> context, PayService payService) throws PayErrorException {
LOG.info("回调支付消息处理器,回调消息:{}", JSON.toJSONString(payMessage));
return payService.successPayOutMessage(payMessage);
}
}

View File

@@ -21,6 +21,12 @@ public class CombineAmount extends Amount {
@JSONField(name = "total_amount")
private Integer totalAmount;
public CombineAmount() {
}
public CombineAmount(Integer totalAmount) {
this.totalAmount = totalAmount;
}
public Integer getTotalAmount() {
return totalAmount;

View File

@@ -16,6 +16,15 @@ public class CombineSubOrder {
* 子单发起方商户号必填必须与发起方appid有绑定关系。
*/
private String mchid;
/**
* 二级商户商户号,由微信支付生成并下发。
* <p>
* 服务商子商户的商户号,被合单方。
* <p>
* 直连商户不用传二级商户号。
*/
@JSONField(name = "sub_mchid")
private String subMchid;
/**
* 商户系统内部订单号要求32个字符内只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
@@ -32,6 +41,14 @@ public class CombineSubOrder {
this.mchid = mchid;
}
public String getSubMchid() {
return subMchid;
}
public void setSubMchid(String subMchid) {
this.subMchid = subMchid;
}
public String getOutTradeNo() {
return outTradeNo;
}

View File

@@ -23,7 +23,7 @@ public class Amount {
* 货币类型 CNY人民币境内商户号仅支持人民币。
* {@link com.egzosn.pay.common.bean.CurType}
*/
private String currency;
private String currency = DefaultCurType.CNY.getType();
public Amount() {
}

View File

@@ -41,10 +41,15 @@ public class H5Info {
@JSONField(name = "package_name")
private String packageName;
public H5Info() {
this.type = "Wap";
}
public H5Info(String type) {
this.type = type;
}
public H5Info(String appName, String appUrl) {
this();

View File

@@ -32,15 +32,7 @@ public class SubOrder extends CombineSubOrder {
private String description;
/**
* 二级商户商户号,由微信支付生成并下发。
* <p>
* 服务商子商户的商户号,被合单方。
* <p>
* 直连商户不用传二级商户号。
*/
@JSONField(name = "sub_mchid")
private String subMchid;
/**
* 结算信息,选填
@@ -113,13 +105,6 @@ public class SubOrder extends CombineSubOrder {
}
public String getSubMchid() {
return subMchid;
}
public void setSubMchid(String subMchid) {
this.subMchid = subMchid;
}
public SettleInfo getSettleInfo() {
return settleInfo;