简介
JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。
应用场景
JSAPI支付适用于线下场所、公众号场景和PC网站场景。
商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。具体操作流程如下:
1.商户下发图文消息或者通过自定义菜单吸引用户点击进入商户网页
2.进入商户网页,用户选择购买,完成选购流程。
3.调起微信支付控件,用户开始输入支付密码
4.密码验证通过,支付成功。商户后台得到支付成功的通知
5.返回商户页面,显示购买成功。该页面由商户自定义
6.微信支付公众号下发支付凭证
接入前准备
直接跳转微信支付商户平台 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml
生成密钥文件:
配置文件:
wx:appId: appIdkeyPath: apiclient_key.pemcertPath: apiclient_cert.pemcertP12Path: 暂不用platformCertPath: platform_cert.pemmchId: mchIdapiKey3: 暂不用apiKey: apiKeydomain: https://hous.exchang.cnserialNo: 序列号
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 WechatJsapiConfig {private final WechatProperties wechatProperties;public WechatJsapiConfig(WechatProperties wechatProperties) {this.wechatProperties = wechatProperties;}@Beanpublic 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();}@Beanpublic JsapiServiceExtension jsapiService() throws IOException {return new JsapiServiceExtension.Builder().config(this.rsaConfig()).build();}@Beanpublic 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();// 初始化 NotificationParserreturn new NotificationParser(config);}@Bean(name = "payRefundService")public RefundService refundService() throws IOException {return new RefundService.Builder().config(this.rsaConfig()).build();}}
创建支付单:
@Slf4j
@Service
@EnableConfigurationProperties(WechatProperties.class)
public class WechatJsapiPayServiceImpl implements BasePayService {@Resourceprivate WechatProperties wechatProperties;@Resourceprivate JsapiServiceExtension jsapiService;@Value("${spring.application.name}")private String serviceName;@Resourceprivate MemberDao memberDao;@Resourceprivate RedisCache redisCache;@Resourceprivate ShopDao shopDao;@Overridepublic R<?> paymentOrder(List<Order> orderList) {BigDecimal total = orderList.stream().map(Order::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);Order order = orderList.get(0);Long mId = order.getMId();Member member = memberDao.queryById(mId);Integer payTotal = total.multiply(BigDecimal.valueOf(100)).intValue();PrepayRequest prepayRequest = new PrepayRequest();prepayRequest.setAppid(wechatProperties.getAppId());prepayRequest.setMchid(wechatProperties.getMchId());prepayRequest.setDescription("供应链下单");prepayRequest.setOutTradeNo(order.getBatchSn());prepayRequest.setNotifyUrl(wechatProperties.getDomain() + "/callback/wechat/pay");prepayRequest.setAttach("1");prepayRequest.setTimeExpire(this.getTimeExpire());Amount amount = new Amount();amount.setTotal(payTotal);Payer payer = new Payer();payer.setOpenid(member.getOpenId());prepayRequest.setAmount(amount);prepayRequest.setPayer(payer);log.info("创建预支付单入参:【{}】", JSONObject.toJSONString(prepayRequest));try {PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(prepayRequest);log.info("创建预支付单并生成二维码成功,出参:【{}】", JSONObject.toJSONString(response));//未支付监听redisCache.setCacheObject(RedisConstants.ORDER_PAY_RESULT.concat(order.getBatchSn()),JSONObject.toJSONString(response), 10, TimeUnit.MINUTES);redisCache.setCacheObject(RedisConstants.ORDER_NOT_PAY_LISTENER.concat(order.getBatchSn()),NumberUtils.INTEGER_ZERO, 10, TimeUnit.MINUTES);//发送未支付消息redisCache.setCacheObject(RedisConstants.ORDER_MSG_LISTENER.concat(order.getBatchSn()),NumberUtils.INTEGER_ZERO, 2, TimeUnit.MINUTES);return R.ok(response);} catch (HttpException e) { // 发送HTTP请求失败log.error("发送HTTP请求失败:{}", e.getHttpRequest());return R.fail("发送HTTP请求失败");} catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500log.error("微信支付返回状态异常,{}", e.getResponseBody());return R.fail("微信支付返回状态异常");} catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败log.error("返回类型不合法:{}", e.getMessage());return R.fail("返回类型不合法");}}@Overridepublic void closeOrder(String outTradeNo) {CloseOrderRequest closeOrderRequest = new CloseOrderRequest();closeOrderRequest.setMchid(wechatProperties.getMchId());closeOrderRequest.setOutTradeNo(outTradeNo);jsapiService.closeOrder(closeOrderRequest);log.info("订单关闭成功,入参:【{}】", JSONObject.toJSONString(closeOrderRequest));}@Overridepublic Optional<Transaction> getOrderInfo(String outTradeNo) {QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();request.setOutTradeNo(outTradeNo);request.setMchid(wechatProperties.getMchId());Transaction transaction = jsapiService.queryOrderByOutTradeNo(request);if (Objects.isNull(transaction)) {return Optional.empty();}return Optional.of(transaction);}private String getTimeExpire(){//过期时间 RFC 3339格式SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");//支付订单过期时间return sdf.format(new Date(System.currentTimeMillis() + 1000 * 60 * 5));}}
创建退款单:
@Slf4j
@Service
@EnableConfigurationProperties(WechatProperties.class)
public class WechatRefundServiceImpl implements BaseRefundService {@Resourceprivate RefundService refundService;@Resourceprivate WechatProperties wechatProperties;@Value("${spring.application.name:scm-ofc-system}")private String serviceName;@Resourceprivate OrderDao orderDao;@Resourceprivate OrderItemDao orderItemDao;@Resourceprivate ShopDao shopDao;@Overridepublic R<?> refundOrder(OrderRefund orderRefund) {try {BigDecimal refundAmount = orderRefund.getRefundAmount();Long orderId = orderRefund.getOrderId();Order order = orderDao.queryById(orderId);//退款金额long refundTotal = refundAmount.multiply(BigDecimal.valueOf(100)).longValue();//原支付金额String batchSn = order.getBatchSn();OrderDTO params = new OrderDTO();params.setBatchSn(batchSn);List<Order> orders = orderDao.selectOrderList(params);BigDecimal officialReceipts = orders.stream().map(Order::getPayAmount).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(batchSn);createRequest.setAmount(amountReq);createRequest.setOutRefundNo(orderRefund.getRefundSn());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 R.fail(500, "退款异常,请求微信返回值为null:参数" + JSONObject.toJSONString(createRequest));}if (Objects.equals(refund.getStatus(), Status.SUCCESS)) {return R.ok(refund);}} catch (Exception e) {log.error("退款异常:{}", e.getMessage());return R.fail(500, "退款异常,请求微信报错" + e.getMessage());}return R.ok();}}
支付与退款回调
@Slf4j
@Service
public class CallbackServiceImpl implements CallbackService {@Resourceprivate NotificationParser notificationParser;@Resourceprivate RedisCache redisCache;@Resourceprivate OrderService orderService;@Resourceprivate BillDao billDao;@Resourceprivate ShopAccountDao shopAccountDao;@Resourceprivate OrderScmService orderScmService;@Resourceprivate OrderRefundDao orderRefundDao;@Resourceprivate ShopSkuService shopSkuService;@Resourceprivate OrderItemDao orderItemDao;@Resourceprivate MessageService messageService;@Override@Transactional(rollbackFor = Exception.class)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;}String outTradeNo = transaction.getOutTradeNo();log.info("支付回调执行成功,待支付->待发货");} catch (Exception e) {log.error("支付回调异常:{}", e.getMessage());}}@Overridepublic 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;}//执行退款业务}/*** 获取供应链订单id* @param data* @param orderList* @return*/private Map<Long, String> getOrderIdMap(OrderByThirdIdVO data,List<Order> orderList){List<OrderSkuDetailVO> skus = data.getSkus();Map<String, String> skuOrderIdMap = skus.stream().collect(Collectors.toMap(OrderSkuDetailVO::getSku, OrderSkuDetailVO::getOrderId));List<OrderItem> orderItems = orderList.stream().map(Order::getOrderItem).collect(Collectors.toList());return orderItems.stream().map(item -> {String originId = item.getOriginId();String orderId = skuOrderIdMap.get(originId);return new DefaultKeyValue<>(item.getOrderId(), orderId);}).collect(Collectors.toMap(DefaultKeyValue::getKey, DefaultKeyValue::getValue));}/*** 验证并解密报文** @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;}}
以上就是微信jsapi支付的对接,仅供参考