简介
Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。
应用场景
Native支付适用于PC网站、实体店单品或订单、媒体广告支付等场景,用户扫描商户展示在各种场景的二维码进行支付。聚体步骤如下:
1.商户根据微信支付的规则,为不同商品生成不同的二维码,展示在各种场景,用于用户扫描购买
2.用户使用微信“扫一扫”扫描二维码后,获取商品支付信息,引导用户完成支付。
3.用户确认支付,输入支付密码
4.支付完成后会提示用户支付成功,商户后台得到支付成功的通知,然后进行发货处理
接入前准备
直接跳转微信支付商户平台 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_1.shtml
生成密钥文件:
配置文件:
wx:
appId: appId
keyPath: apiclient_key.pem
certPath: apiclient_cert.pem
certP12Path: 暂不用
platformCertPath: platform_cert.pem
mchId: mchId
apiKey3: 暂不用
apiKey: apiKey
domain: https://hous.exchang.cn
serialNo: 序列号
pom文件:
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.2</version>
</dependency>
代码:
配置类:
//获取yml中微信配置
@Data
@ConfigurationProperties(prefix = "wx")
public class WechatProperties {
private String appId;
private String keyPath;
private String certPath;
private String platformCertPath;
private String mchId;
private String apiKey;
private String domain;
private String serialNo;
}
//配置类
@Configuration
@EnableConfigurationProperties(WechatProperties.class)
public class WechatNativeConfig {
private final WechatProperties wechatProperties;
public WechatNativeConfig(WechatProperties wechatProperties) {
this.wechatProperties = wechatProperties;
}
@Bean
public RSAConfig rsaConfig() throws IOException {
ClassPathResource keyResource = new ClassPathResource(wechatProperties.getKeyPath());
String apiClientKey = keyResource.getFile().getPath();
ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
String platformCertResourceKey = certResource.getFile().getPath();
/*String apiClientKey = wechatProperties.getKeyPath();
// ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
String platformCertResourceKey = wechatProperties.getPlatformCertPath();*/
return new RSAConfig.Builder()
.merchantId(wechatProperties.getMchId())
.privateKeyFromPath(apiClientKey)
.merchantSerialNumber(wechatProperties.getSerialNo())
//平台证书
.wechatPayCertificatesFromPath(platformCertResourceKey)
.build();
}
@Bean
public NativePayService nativePayService() throws IOException {
return new NativePayService.Builder()
.config(this.rsaConfig())
.build();
}
@Bean
public NotificationParser notificationParser() throws IOException{
ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
String platformCertResourceKey = certResource.getFile().getPath();
//String platformCertResourceKey = wechatProperties.getPlatformCertPath();
NotificationConfig config = new RSANotificationConfig.Builder()
.apiV3Key(wechatProperties.getApiKey())
.certificatesFromPath(platformCertResourceKey)
.build();
// 初始化 NotificationParser
return new NotificationParser(config);
}
@Bean
public RefundService refundService() throws IOException {
return new RefundService.Builder().config(this.rsaConfig()).build();
}
}
创建支付单:
@Slf4j
@Service
@EnableConfigurationProperties(WechatProperties.class)
public class WechatNativePayServiceImpl implements BasePayService {
@Resource
private WechatProperties wechatProperties;
@Resource
private NativePayService nativePayService;
@Resource
private RedisCache redisCache;
@Value("${spring.application.name:scm-ofc-system}")
private String serviceName;
@Override
public AjaxResult<?> payOrder(List<OrderSub> orderSubs) {
//总金额,单位分
Integer payTotal = total.multiply(BigDecimal.valueOf(100)).intValue();
PrepayRequest prepayRequest = new PrepayRequest();
prepayRequest.setAppid(wechatProperties.getAppId());
prepayRequest.setMchid(wechatProperties.getMchId());
prepayRequest.setDescription("供应链下单");
//订单编号
prepayRequest.setOutTradeNo(orderSub.getOrderMsn());
prepayRequest.setNotifyUrl(wechatProperties.getDomain() + "/" + serviceName + "/callback/wechat/pay");
prepayRequest.setAttach(BusType.CREATE_ORDER.getDesc());
Amount amount = new Amount();
amount.setTotal(payTotal);
prepayRequest.setAmount(amount);
log.info("创建预支付单入参:【{}】", JSONObject.toJSONString(prepayRequest));
try {
PrepayResponse prepay = nativePayService.prepay(prepayRequest);
log.info("创建预支付单并生成二维码成功,出参:【{}】", JSONObject.toJSONString(prepay));
Map<String, String> hashMap = new HashMap<>();
hashMap.put("orderMsn", orderSub.getOrderMsn());
hashMap.put("codeUrl", prepay.getCodeUrl());
//二维码暂存redis
String value = JSONObject.toJSONString(hashMap);
redisCache.setCacheObject(RedisKeyConstant.ORDER_PAY_URL_CODE + orderSub.getOrderMsn(), value, 15, TimeUnit.MINUTES);
return AjaxResult.ok(hashMap);
} catch (HttpException e) { // 发送HTTP请求失败
log.error("发送HTTP请求失败:{}", e.getHttpRequest());
return AjaxResult.fail("发送HTTP请求失败");
} catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
log.error("微信支付返回状态异常,{}", e.getResponseBody());
return AjaxResult.fail("微信支付返回状态异常");
} catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("返回类型不合法:{}", e.getMessage());
return AjaxResult.fail("返回类型不合法");
}
}
@Override
public void closeOrder(String outTradeNo) {
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
closeOrderRequest.setMchid(wechatProperties.getMchId());
closeOrderRequest.setOutTradeNo(outTradeNo);
nativePayService.closeOrder(closeOrderRequest);
log.info("订单关闭成功,入参:【{}】", JSONObject.toJSONString(closeOrderRequest));
}
@Override
public Optional<Transaction> getOrderInfo(String outTradeNo) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setOutTradeNo(outTradeNo);
request.setMchid(wechatProperties.getMchId());
Transaction transaction = nativePayService.queryOrderByOutTradeNo(request);
if (Objects.isNull(transaction)) {
return Optional.empty();
}
return Optional.of(transaction);
}
}
创建退款单:
@Slf4j
@Service
@EnableConfigurationProperties(WechatProperties.class)
public class WechatNativeRefundServiceImpl implements BaseRefundService {
@Resource
private RefundService refundService;
@Resource
private WechatProperties wechatProperties;
@Resource
private BaseOrderFeignService baseOrderFeignService;
@Resource
private RefundOrderFeignService refundOrderFeignService;
@Value("${spring.application.name:scm-ofc-system}")
private String serviceName;
@Override
public AjaxResult<?> refundOrder(AsRefundApply asRefundApply) {
try {
BigDecimal refundAmount = asRefundApply.getRefundAmount();
String orderSsn = asRefundApply.getOrderSsn();
Optional<OrderSub> data = baseOrderFeignService.getOrderSubDetail(orderSsn).getData();
if (!data.isPresent()) {
return AjaxResult.fail(500, "当前单不存在");
}
//退款金额
long refundTotal = refundAmount.multiply(BigDecimal.valueOf(100)).longValue();
OrderSub orderSub = data.get();
//原支付金额
String orderMsn = orderSub.getOrderMsn();
List<OrderSub> orderSubList = baseOrderFeignService.selectOrderSubs(orderMsn).getData();
BigDecimal officialReceipts = orderSubList.stream().map(OrderSub::getOfficialReceipts).reduce(BigDecimal.ZERO, BigDecimal::add);
long payTotal = officialReceipts.multiply(BigDecimal.valueOf(100)).longValue();
//创建微信退款单
CreateRequest createRequest = new CreateRequest();
AmountReq amountReq = new AmountReq();
amountReq.setCurrency("CNY");
amountReq.setTotal(payTotal);
amountReq.setRefund(refundTotal);
createRequest.setOutTradeNo(orderSub.getOrderMsn());
createRequest.setAmount(amountReq);
createRequest.setOutRefundNo(asRefundApply.getAfterSaleSn());
createRequest.setNotifyUrl(wechatProperties.getDomain() + "/" + serviceName + "/callback/wechat/refund");
log.info("退款单入参:{}", JSONObject.toJSONString(createRequest));
Refund refund = refundService.create(createRequest);
log.info("创建退款单成功:{}", JSONObject.toJSONString(refund));
if (Objects.isNull(refund)) {
log.error("退款异常,参数:{}", JSONObject.toJSONString(createRequest));
return AjaxResult.fail(500, "退款异常,请求微信返回值为null:参数" + JSONObject.toJSONString(createRequest));
}
if (Objects.equals(refund.getStatus(), Status.SUCCESS)) {
return AjaxResult.ok(refund);
}
} catch (Exception e) {
log.error("退款异常:{}", e.getMessage());
return AjaxResult.fail(500, "退款异常,请求微信报错" + e.getMessage());
}
return AjaxResult.ok();
}
@Override
public AjaxResult<?> refundAllOrder(List<OrderSub> orderSubList) {
try {
OrderSub orderSub = orderSubList.get(NumberUtils.INTEGER_ZERO);
BigDecimal officialReceipts = orderSubList.stream().map(OrderSub::getOfficialReceipts).reduce(BigDecimal.ZERO, BigDecimal::add);
long payTotal = officialReceipts.multiply(BigDecimal.valueOf(100)).longValue();
//创建微信退款单
CreateRequest createRequest = new CreateRequest();
AmountReq amountReq = new AmountReq();
amountReq.setCurrency("CNY");
amountReq.setTotal(payTotal);
amountReq.setRefund(payTotal);
createRequest.setOutTradeNo(orderSub.getOrderMsn());
createRequest.setAmount(amountReq);
createRequest.setOutRefundNo("ALL" + orderSub.getOrderMsn());
createRequest.setNotifyUrl(wechatProperties.getDomain() + "/callback/wechat/refund");
Refund refund = refundService.create(createRequest);
log.info("创建退款单成功:{}", JSONObject.toJSONString(refund));
if (Objects.isNull(refund)) {
log.error("退款异常,参数:{}", JSONObject.toJSONString(createRequest));
return AjaxResult.fail(500, "退款异常,请求微信返回值为null:参数" + JSONObject.toJSONString(createRequest));
}
if (Objects.equals(refund.getStatus(), Status.SUCCESS)) {
return AjaxResult.ok(refund);
}
} catch (Exception e) {
log.error("退款异常:{}", e.getMessage());
return AjaxResult.fail(500, "退款异常,请求微信报错" + e.getMessage());
}
return AjaxResult.ok();
}
/**
* 查询微信退款单详情
*
* @param afterSaleSn
*/
@Override
public Refund getRefundOrderDetail(String afterSaleSn) {
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(afterSaleSn);
return refundService.queryByOutRefundNo(request);
}
@Override
public AjaxResult<?> returnPayOrder(String orderSn) {
Optional<OrderSub> optional = baseOrderFeignService.getOrderSubDetail(orderSn).getData();
if (!optional.isPresent()) {
throw new ServiceException("订单信息不存在");
}
OrderSub orderSub = optional.get();
String orderSsn = orderSub.getOrderSsn();
AjaxResult<AsRefundApply> ajaxResult = refundOrderFeignService.createReturnOrder(orderSsn);
if (ajaxResult.isSuccess()) {
AsRefundApply asRefundApply = ajaxResult.getData();
AjaxResult<?> result = this.refundOrder(asRefundApply);
if (result.isError()) {
//refundOrderFeignService.deleteReturnOrder();
throw new ServiceException(result.getMsg());
}
}
return ajaxResult;
}
}
支付与退款回调
public interface CallbackService {
/**
* 微信支付回调
* @param request
* @param response
* @return
*/
void wechatPayCallback(HttpServletRequest request, HttpServletResponse response);
/**
* 微信退款回调
* @param request
* @param response
*/
void wechatRefundCallback(HttpServletRequest request, HttpServletResponse response);
}
//实现类
@Slf4j
@Service
public class CallbackServiceImpl implements CallbackService {
@Resource
private NotificationParser notificationParser;
@Override
public void wechatPayCallback(HttpServletRequest request, HttpServletResponse response) {
try {
log.info("=========================微信native支付回调通知============================");
Transaction transaction = this.verifyAndDecrypt(request, Transaction.class);
log.info("验证签名成功:{}", JSONObject.toJSONString(transaction));
Transaction.TradeStateEnum tradeState = transaction.getTradeState();
if (!Objects.equals(tradeState, Transaction.TradeStateEnum.SUCCESS)) {
return;
}
//支付业务
} catch (Exception e) {
log.error("支付回调异常:{}", e.getMessage());
}
}
@Override
public void wechatRefundCallback(HttpServletRequest request, HttpServletResponse response) {
log.info("=========================微信native退款回调通知============================");
RefundNotification refundNotification = this.verifyAndDecrypt(request, RefundNotification.class);
Status refundStatus = refundNotification.getRefundStatus();
if (!Objects.equals(refundStatus, Status.SUCCESS)) {
return;
}
}
/**
* 验证并解密报文
*
* @param request
*/
private <T> T verifyAndDecrypt(HttpServletRequest request, Class<T> clazz) {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
String signType = request.getHeader("Wechatpay-Signature-Type");
String body = this.getBody(request);
log.info("\n请求头信息:\n" +
"Wechatpay-Timestamp:{},\n" +
"Wechatpay-Nonce:{},\n" +
"Wechatpay-Serial:{},\n" +
"Wechatpay-Signature:{},\n" +
"Wechatpay-Signature-Type:{},\n" +
"body: {}",
timestamp, nonce, serialNo, signature, signType, body);
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.signType(signType)
.body(body)
.build();
return notificationParser.parse(requestParam, clazz);
}
/**
* 获取请求体内容
*
* @param request
* @return
*/
private String getBody(HttpServletRequest request) {
BufferedReader reader;
String body = "";
try {
reader = request.getReader();
String line;
StringBuilder inputString = new StringBuilder();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
body = inputString.toString();
} catch (IOException e) {
e.printStackTrace();
}
return body;
}
}
以上就是微信native支付的对接,仅供参考