时隔两年再次回归
本文主要讲的是小程序实现微信支付功能,后台采用JAVA。
一.准备工作
1.小程序
2.微信商户号
1.商户号申请
这里对小程序的申请不做赘述。
如果没有微信商户号的同学,点击该链接https://pay.weixin.qq.com/,按照下属步骤进行商户号申请。
扫码之后点击"成为商家",这里主要有个体工商户和企业,按照事实填写,然后按照步骤填写就行了。
主要需要营业执照,法人信息,公账信息等。
2.微信商户号关联小程序
点击"产品中心"的"开发配置",点击"新增授权申请单"。
输入你的小程序appid,点击下一步。
然后到小程序后台>微信支付>商户号管理里,会出现一个申请单,点击“查看”。
点击确认绑定,这样你的商户号就与小程序进行绑定了。
二.代码编写
1.小程序
小程序这块主要是调用一下后台接口获取参数,然后通过参数拉起微信支付。
orderPay(payInfo){ let that = this wx.requestPayment({ 'timeStamp': payInfo.timeStamp, 'nonceStr': payInfo.nonceStr, 'package': payInfo.package, 'signType': payInfo.signType, 'paySign': payInfo.paySign, 'success': function (res) { // 支付成功的回调 }, 'fail': function (res) { console.log(JSON.stringify(res)); wx.showToast({title: '支付失败', icon: 'none',duration: 2000,mask: true}) } })},
这里的payInfo就是从后台接口获取的支付参数,通过wx.requestPayment就可以拉起微信支付了。具体的参数信息在下面会进行讲解。
2.服务端(JAVA)
服务端这边主要是三个接口:
1.提交支付订单
这个主要是为了获取提交支付订单,获取前端拉起支付的参数。
2.微信支付回调
当你小程序拉起支付并且成功支付后,会将支付结果回调到这个接口
3.支付订单查询
你也可以主动通过订单号查询支付订单状态
下面是我的代码,包含了我的业务代码,大家将就着看吧
pom.xml
<!--小程序支付 v3--><dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.12</version></dependency>
controller:
import com.smart.iot.gmt.app.bean.CommonReponse;import com.smart.iot.gmt.app.constant.ResponseContant;import com.smart.iot.gmt.app.constant.SessionKeyConstants;import com.smart.iot.gmt.app.request.wechatPay.PaymentRequest;import com.smart.iot.gmt.app.request.wechatPay.QueryPayOrderRequest;import com.smart.iot.gmt.app.response.wechat.WechatPaymentResponse;import com.smart.iot.gmt.app.response.wechat.WechatQueryPayOrderResponse;import com.smart.iot.gmt.app.service.CommonService;import com.smart.iot.gmt.app.service.pay.wechat.WxPayService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import java.util.Map;@Slf4j@RestControllerpublic class WxPayController { @Autowired private WxPayService service; @Autowired private CommonService commonService; /** * * @author Rick chou * @date 2024/7/18 15:33 * 提交支付 * */ @PostMapping("/payment") public CommonReponse payment(@RequestBody PaymentRequest paymentRequest) { Integer price = paymentRequest.getPrice(); String orderId = paymentRequest.getOrderId(); Map<String, Object> result = service.payment(price, orderId, userId); CommonReponse commonResponse = commonService.getCommonResponse(ResponseContant.SUCCESS_CODE, ResponseContant.SUCCESS, result); return new WechatPaymentResponse(commonResponse,result); } /** * * @author Rick chou * @date 2024/7/15 16:57 * 微信支付回调 * */ @PostMapping("/payNotify") public void payNotify(HttpServletRequest request) throws Exception { service.payNotify(request); } /** * * @author Rick chou * @date 2024/7/15 16:57 * 支付查询 * */ @PostMapping("/queryPayOrder") public CommonReponse queryPayOrder(@RequestBody QueryPayOrderRequest request) { Map<String, Object> result = service.queryPayOrder(request); CommonReponse commonResponse = commonService.getCommonResponse(ResponseContant.SUCCESS_CODE, ResponseContant.SUCCESS, result); return new WechatQueryPayOrderResponse(commonResponse,result); }}
其中payment接口返回值就是小程序wx.requestPayment需要的参数
service:
import com.alibaba.fastjson.JSON;import com.smart.iot.constant.RedisKeys;import com.smart.iot.device.dto.LockNotifyMessageDTO;import com.smart.iot.device.service.DeviceRedisCacheService;import com.smart.iot.gmt.app.annotation.SpringUtil;import com.smart.iot.gmt.app.bo.*;import com.smart.iot.gmt.app.entity.MemberOrderDetailEntity;import com.smart.iot.gmt.app.enums.MemberOrderStateEnum;import com.smart.iot.gmt.app.request.wechatPay.QueryPayOrderRequest;import com.smart.iot.gmt.app.service.LockNotifyMessageService;import com.wechat.pay.java.service.payments.model.Transaction;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;import javax.transaction.Transactional;import java.util.Map;@Slf4j@Servicepublic class WxPayService { public Map<String, Object> payment(Integer price, String orderId, String userId){ Map<String, Object> result = WechatPayBo.payment(price, orderId, userId); return result; } @Transactional public void payNotify(HttpServletRequest request) throws Exception { Transaction parse = WechatPayBo.payNotify(request); updateAccountDetail(parse,false); } @Transactional public Map<String,Object> queryPayOrder(QueryPayOrderRequest request) { String orderId = request.getOrderId(); Transaction parse = WechatPayBo.queryPayOrder(orderId); return updateAccountDetail(parse,true); } /** * * @author Rick chou * @date 2024/7/16 11:03 * 支付回调处理 * 1.更新订单状态 * 2.添加支付记录 * 3.通知小程序 * */ public Map<String,Object> updateAccountDetail(Transaction parse, boolean active) { String orderId = parse.getOutTradeNo(); Transaction.TradeStateEnum tradeState = parse.getTradeState(); if(tradeState==Transaction.TradeStateEnum.SUCCESS) { if(OrderBo.check(orderId).getState() == MemberOrderStateEnum.IN_PROGRESS.code) { OrderBo order = this.updateOrder(orderId); this.renewMember(orderId); this.saveRecord(parse, order.getUserId()); } } if(active){ return JSON.parseObject(buildParse(parse),Map.class); }else{ this.payNoticeMessage(parse,orderId); } return null; } /** * * @author Rick chou * @date 2024/7/19 15:31 * 更新订单状态 * */ private OrderBo updateOrder(String orderId){ OrderBo order = OrderBo.check(orderId); order.finish(); String key = RedisKeys.ADD_ORDER_PAY_TIME_PREFIX + orderId; SpringUtil.getBean(DeviceRedisCacheService.class).delete(key); return order; } /** * * @author Rick chou * @date 2024/7/19 15:32 * 更新会员时间 * */ private void renewMember(String orderId){ MemberOrderDetailEntity orderDetail = OrderDetailBo.getByOrderId(orderId); UserMemberBo.renew(orderDetail); } /** * * @author Rick chou * @date 2024/7/19 15:33 * 保存支付记录 * */ private void saveRecord(Transaction parse,String userId){ String orderId = parse.getOutTradeNo(); Integer amount = parse.getAmount().getTotal(); String tradeType = parse.getTradeType().name(); PayRecordBo.create(userId,orderId,amount,tradeType); } /** * * @author Rick chou * @date 2024/7/19 15:38 * 将支付结果下发小程序 * */ private void payNoticeMessage(Transaction parse,String orderId){ MemberOrderDetailEntity detail = OrderDetailBo.getByOrderId(orderId); LockNotifyMessageService service = SpringUtil.getBean(LockNotifyMessageService.class); LockNotifyMessageDTO dto = new LockNotifyMessageDTO(); dto.setLockId(detail.getDeviceId()); dto.setMessageParams(buildParse(parse)); service.dealPayResultNotifyMessage(dto); } private String buildParse(Transaction parse){ parse.setMchid(null); parse.setAppid(null); parse.setBankType(null); parse.setBankType(null); parse.setAttach(null); return JSON.toJSONString(parse); }}
bo:
import com.smart.iot.gmt.app.annotation.SpringUtil;import com.smart.iot.gmt.app.entity.PayRecordEntity;import com.smart.iot.gmt.app.service.pay.wechat.PayInfoConfig;import com.smart.iot.gmt.app.service.pay.wechat.WXPayUtil;import com.smart.iot.util.StringUtil;import com.wechat.pay.java.core.Config;import com.wechat.pay.java.core.RSAAutoCertificateConfig;import com.wechat.pay.java.core.exception.ServiceException;import com.wechat.pay.java.core.exception.ValidationException;import com.wechat.pay.java.core.notification.NotificationConfig;import com.wechat.pay.java.core.notification.NotificationParser;import com.wechat.pay.java.core.notification.RequestParam;import com.wechat.pay.java.service.payments.jsapi.JsapiService;import com.wechat.pay.java.service.payments.jsapi.model.*;import com.wechat.pay.java.service.payments.model.Transaction;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.UUID;import java.util.stream.Collectors;import java.util.stream.Stream;import static com.wechat.pay.java.core.http.Constant.*;/** * * @author Rick chou * @date 2024/7/16 9:10 * 微信支付BO * */@Slf4j@Componentpublic class WechatPayBo extends PayRecordEntity { private static PayInfoConfig getConfig(){ PayInfoConfig payInfoConfig = SpringUtil.getBean(PayInfoConfig.class); return payInfoConfig; } /** * * @author Rick chou * @date 2024/7/16 9:35 * 构建支付请求SERVICE * */ public static JsapiService getService(){ PayInfoConfig payInfoConfig = getConfig(); Config config = new RSAAutoCertificateConfig.Builder() .merchantId(payInfoConfig.getMchId()) .privateKeyFromPath(payInfoConfig.getKeyPath()) .merchantSerialNumber(payInfoConfig.getMchSerialNo()) .apiV3Key(payInfoConfig.getApiKey()) .build(); JsapiService service = new JsapiService.Builder().config(config).build(); return service; } /** * * @author Rick chou * @date 2024/7/16 10:31 * 构造NOTIFY_CONFIG * */ private static NotificationConfig buildNotifyConfig(){ PayInfoConfig payInfoConfig = getConfig(); NotificationConfig config = new RSAAutoCertificateConfig.Builder() .merchantId(payInfoConfig.getMchId()) .privateKeyFromPath(payInfoConfig.getKeyPath()) .merchantSerialNumber(payInfoConfig.getMchSerialNo()) .apiV3Key(payInfoConfig.getApiKey()) .build(); return config; } /** * * @author Rick chou * @date 2024/7/16 9:35 * 构建支付请求参数 * */ private static PrepayRequest buildParam(Integer price, String orderId, String userId){ PayInfoConfig payInfoConfig = getConfig(); PrepayRequest prepayRequest = new PrepayRequest(); Amount amount = new Amount(); amount.setTotal(price); prepayRequest.setAmount(amount); prepayRequest.setAppid(payInfoConfig.getAppId()); prepayRequest.setMchid(payInfoConfig.getMchId()); prepayRequest.setNotifyUrl(payInfoConfig.getNotifyUrl()); // 回调接口地址 prepayRequest.setDescription("微信支付"); prepayRequest.setOutTradeNo(orderId); // 订单号 prepayRequest.setAttach("member"); // 订单类型(回调时可根据这个数据辨别订单类型或其他) //根据token拿到openid,指定该预支付订单的支付者身份 Payer payer = new Payer(); payer.setOpenid(WeixinUserBo.getOpenId(userId)); prepayRequest.setPayer(payer); return prepayRequest; } /** * * @author Rick chou * @date 2024/7/16 9:53 * 解析支付结果 * */ private static Map<String,Object> parsePay(PrepayResponse response){ PayInfoConfig payInfoConfig = getConfig(); Map<String, Object> params = new HashMap<>(); Long timeStamp = System.currentTimeMillis() / 1000; String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); String signatureStr = Stream.of( payInfoConfig.getAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId() ).collect(Collectors.joining("/n", "", "/n")); String sign = WXPayUtil.getSign(signatureStr, payInfoConfig.getKeyPath()); params.put("timeStamp", String.valueOf(timeStamp)); params.put("nonceStr", substring); params.put("paySign", sign); params.put("signType", "RSA"); params.put("package", "prepay_id=" + response.getPrepayId()); return params; } /** * * @author Rick chou * @date 2024/7/16 10:33 * 解析回调结果 * */ private static RequestParam parseNotify(HttpServletRequest request)throws IOException { String data = StringUtil.getStringForInput(request.getInputStream()); String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); String nonce = request.getHeader(WECHAT_PAY_NONCE); String signType = request.getHeader("Wechatpay-Signature-Type"); String serialNo = request.getHeader(WECHAT_PAY_SERIAL); String signature = request.getHeader(WECHAT_PAY_SIGNATURE); RequestParam requestParam = new RequestParam.Builder() .serialNumber(serialNo) .nonce(nonce) .signature(signature) .timestamp(timestamp) .signType(signType) // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048 .body(data) .build(); return requestParam; } /** * * @author Rick chou * @date 2024/7/16 9:47 * 调起支付 * */ public static Map<String, Object> payment(Integer price, String orderId, String userId){ JsapiService service = getService(); PrepayRequest prepayRequest = buildParam(price, orderId, userId); PrepayResponse response = service.prepay(prepayRequest); Map<String, Object> result = parsePay(response); result.put("orderId",orderId); return result; } /** * * @author Rick chou * @date 2024/7/16 10:16 * 支付回调 * */ public static Transaction payNotify(HttpServletRequest request) throws Exception { NotificationConfig config = buildNotifyConfig(); NotificationParser parser = new NotificationParser(config); RequestParam requestParam = parseNotify(request); Transaction parse = null; try { parse = parser.parse(requestParam, Transaction.class); }catch (ValidationException e){ log.error("sign verification failed", e); } return parse; } /** * * @author Rick chou * @date 2024/7/16 11:17 * 查询订单 * */ public static Transaction queryPayOrder(String orderId) { PayInfoConfig payInfoConfig = getConfig(); JsapiService service = getService(); QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest(); queryRequest.setMchid(payInfoConfig.getMchId()); queryRequest.setOutTradeNo(orderId); Transaction parse = null; try { parse = service.queryOrderByOutTradeNo(queryRequest); }catch (ServiceException e){ log.info("code=[%s], message=[%s]/n", e.getErrorCode(), e.getErrorMessage()); log.info("reponse body=[%s]/n", e.getResponseBody()); } return parse; }}
WXPayUtil
import com.smart.iot.gmt.app.config.exception.NormalException;import com.wechat.pay.java.core.util.PemUtil;import org.springframework.util.Base64Utils;import java.nio.charset.StandardCharsets;import java.security.PrivateKey;import java.security.Signature;/** * * @author Rick chou * @date 2024/7/16 16:34 * WXPayUtil * */public class WXPayUtil { public static String getSign(String signatureStr,String privateKey){ //replace 根据实际情况,不一定都需要 try { String replace = privateKey.replace("//n", "/n"); PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(replace); Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(merchantPrivateKey); sign.update(signatureStr.getBytes(StandardCharsets.UTF_8)); return Base64Utils.encodeToString(sign.sign()); }catch (Exception e){ throw new NormalException(e.getMessage()); } }}
PayInfoConfig
import lombok.Data;import lombok.ToString;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Data@ToString@Component@ConfigurationProperties(prefix = "wx")public class PayInfoConfig { //小程序appid private String appId; //商户号 private String mchId; //证书序列号 private String mchSerialNo; //小程序秘钥 private String appSecret; //api秘钥 private String apiKey; //回调接口地址 private String notifyUrl; //证书地址 private String keyPath;}
上述的PayInfoConfig中的参数第二、三、五、七个参数去商户号后台获取。
三.补充说明
在实际的支付开发中需要注意一些比较重要的点,假设你现在做的是一个会员开通功能。
1.在你点击开通的时候,你需要做的肯定是调用你自己的后台业务接口生成一个会员订单,同时调用微信支付获取支付参数返回到前端。这样用户看到的就是直接拉起支付。2.当你执行支付操作后你的支付回调接口会收到支付结果,这个时候你服务端要主动通知小程序,并且当小程序拉起支付后要定时调用支付查询接口来主动查询支付完成支付。做个双保险比较好。3.微信支付完成后有个"完成"按钮,点击后就会回到wx.requestPayment的success回调里,这里最好也要查询下订单状态。4.还有点我还没怎么做处理,也是个题外话,就是当支付回调时服务器挂了咋整,得想个万全之策,这个就交给你们解答了。