微信App支付和扫码支付对接的关键技术点(避坑文档)

    xiaoxiao2023-11-06  144

    构造发起支付的请求对象,该对象是根据微信支付文档来的 @Setter @Getter @AllArgsConstructor @NoArgsConstructor @Builder @JacksonXmlRootElement(localName = "xml") public class WeChatPayRequest { @JacksonXmlProperty(localName = "appid") private String appId; @JacksonXmlProperty(localName = "mch_id") private String mchId; @JacksonXmlProperty(localName = "device_info") private String deviceInfo; // 非必填 @JacksonXmlProperty(localName = "nonce_str") private String nonceStr; @JacksonXmlProperty(localName = "sign") private String sign; @JacksonXmlProperty(localName = "sign_type") private String signType; // 非必填 @JacksonXmlProperty(localName = "body") private String body; @JacksonXmlProperty(localName = "details") private String details; // 非必填 @JacksonXmlProperty(localName = "attach") private String attach; // 非必填 @JacksonXmlProperty(localName = "out_trade_no") private String outTradeNo; @JacksonXmlProperty(localName = "fee_type") private String feeType; // 非必填 @JacksonXmlProperty(localName = "total_fee") private String totalFee; @JacksonXmlProperty(localName = "spbill_create_ip") private String spbillCreateIp; @JacksonXmlProperty(localName = "time_start") private String timeStart; // 非必填 @JacksonXmlProperty(localName = "time_expire") private String timeExpire; // 非必填 @JacksonXmlProperty(localName = "goods_tag") private String goodsTag; // 非必填 @JacksonXmlProperty(localName = "notify_url") private String notifyUrl; @JacksonXmlProperty(localName = "trade_type") private String tradeType; @JacksonXmlProperty(localName = "limit_pay") private String limitPay; // 非必填 @JacksonXmlProperty(localName = "scene_info") private String sceneInfo; } 调用微信支付统一下单接口,本例使用Spring Cloud Feign(申明式RESTful API),具体使用方法不是本例的重点,请参考相关文档。 package com.hfcsbc.pay.service.feign; import com.hfcsbc.pay.config.WeChatCodecConfig; import com.hfcsbc.pay.dto.wechat.miniApp.WeChatMiniAppPayRequest; import com.hfcsbc.pay.dto.WeChatPayResponse; import com.hfcsbc.pay.dto.WeChatPayRequest; import com.hfcsbc.pay.dto.wechat.openApp.WeChatOpenAppPayRequest; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; /** * Created by lijian on 2017/9/27 */ @FeignClient(value = "weChatClient", url = "https://api.mch.weixin.qq.com", configuration = WeChatCodecConfig.class) public interface WeChatClient { @PostMapping(value = "/pay/unifiedorder", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody WeChatPayResponse unifiedOrder(@RequestBody WeChatPayRequest request); } package com.hfcsbc.pay.config; import feign.codec.Decoder; import org.springframework.beans.factory.ObjectFactory; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder; import org.springframework.cloud.netflix.feign.support.SpringDecoder; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import java.util.ArrayList; import java.util.List; /** * Created by lijian on 2017/9/28 */ public class WeChatCodecConfig { @Bean public Decoder feignDecoder() { MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter(); List<MediaType> types = xmlConverter.getSupportedMediaTypes(); List<MediaType> newTypes = new ArrayList<MediaType>(); newTypes.add(MediaType.TEXT_PLAIN); for(MediaType t: types) { newTypes.add(t); } xmlConverter.setSupportedMediaTypes(newTypes); ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(xmlConverter); return new ResponseEntityDecoder(new SpringDecoder(objectFactory)); } } 统一下单接口返回的对象 package com.hfcsbc.pay.dto; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; /** * Created by lijian on 2017/10/25 */ @Setter @Getter @NoArgsConstructor @AllArgsConstructor @JacksonXmlRootElement(localName = "xml") public class WeChatPayResponse { @JacksonXmlProperty(localName = "return_code") private String returnCode; @JacksonXmlProperty(localName = "return_msg") private String returnMsg; @JacksonXmlProperty(localName = "appid") private String appId; @JacksonXmlProperty(localName = "mch_id") private String mchId; @JacksonXmlProperty(localName = "device_info") private String deviceInfo; @JacksonXmlProperty(localName = "nonce_str") private String nonceStr; private String sign; @JacksonXmlProperty(localName = "result_code") private String resultCode; @JacksonXmlProperty(localName = "err_code") private String errCode; @JacksonXmlProperty(localName = "err_code_des") private String errCodeDes; @JacksonXmlProperty(localName = "trade_type") private String tradeType; @JacksonXmlProperty(localName = "prepay_id") private String prepayId; @JacksonXmlProperty(localName = "code_url") private String codeUrl; private String tradeId; // 此处是自定义的参数,不是微信服务器返回参数 public Boolean isSuccess(){ return "SUCCESS".equals(this.returnCode) && "SUCCESS".equals(this.resultCode); } public Boolean returnCodeIsSuccess(){ return "SUCCESS".equals(this.returnCode); } public void populateOutTradeId(String tradeId){ this.tradeId = tradeId; } } 支付完成回调的对象 package com.hfcsbc.orderservice.command.wechat; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.hfcsbc.orderservice.util.WeChatUtils; import lombok.*; /** * Created by lijian on 2017/9/29 */ @Setter @Getter @NoArgsConstructor @AllArgsConstructor @Builder @JacksonXmlRootElement(localName = "xml") public class WeChatNotifyCmd { @JacksonXmlProperty(localName = "appid") private String appId; @JacksonXmlProperty(localName = "mch_id") private String mchId; @JacksonXmlProperty(localName = "device_info") private String deviceInfo; // 非必填 @JacksonXmlProperty(localName = "nonce_str") private String nonceStr; @JacksonXmlProperty(localName = "sign") private String sign; @JacksonXmlProperty(localName = "sign_type") private String signType; @JacksonXmlProperty(localName = "result_code") private String resultCode; @JacksonXmlProperty(localName = "return_code") private String returnCode; @JacksonXmlProperty(localName = "return_msg") private String returnMsg; @JacksonXmlProperty(localName = "err_code") private String errCode; @JacksonXmlProperty(localName = "err_code_des") private String errCodeDes; @JacksonXmlProperty(localName = "openid") private String openId; @JacksonXmlProperty(localName = "is_subscribe") private String isSubscribe; @JacksonXmlProperty(localName = "trade_type") private String tradeType; @JacksonXmlProperty(localName = "bank_type") private String bankType; @JacksonXmlProperty(localName = "total_fee") private String totalFee; @JacksonXmlProperty(localName = "settlement_total_fee") private String settlementTotalFee; @JacksonXmlProperty(localName = "fee_type") private String feeType; @JacksonXmlProperty(localName = "cash_fee") private String cashFee; @JacksonXmlProperty(localName = "cash_fee_type") private String cashFeeType; @JacksonXmlProperty(localName = "coupon_fee") private String couponFee; @JacksonXmlProperty(localName = "coupon_count") private String couponCount; @JacksonXmlProperty(localName = "coupon_type_0") private String couponType0; @JacksonXmlProperty(localName = "coupon_type_1") private String couponType1; @JacksonXmlProperty(localName = "coupon_type_2") private String couponType2; @JacksonXmlProperty(localName = "coupon_type_3") private String couponType3; @JacksonXmlProperty(localName = "coupon_type_4") private String couponType4; @JacksonXmlProperty(localName = "coupon_id_0") private String couponId0; @JacksonXmlProperty(localName = "coupon_id_1") private String couponId1; @JacksonXmlProperty(localName = "coupon_id_2") private String couponId2; @JacksonXmlProperty(localName = "coupon_id_3") private String couponId3; @JacksonXmlProperty(localName = "coupon_id_4") private String couponId4; @JacksonXmlProperty(localName = "coupon_fee_0") private String couponFee0; @JacksonXmlProperty(localName = "coupon_fee_1") private String couponFee1; @JacksonXmlProperty(localName = "coupon_fee_2") private String couponFee2; @JacksonXmlProperty(localName = "coupon_fee_3") private String couponFee3; @JacksonXmlProperty(localName = "coupon_fee_4") private String couponFee4; @JacksonXmlProperty(localName = "transaction_id") private String transactionId; @JacksonXmlProperty(localName = "out_trade_no") private String outTradeNo; @JacksonXmlProperty(localName = "attach") private String attach; @JacksonXmlProperty(localName = "time_end") private String timeEnd; public Boolean ifValid(){ return ifResultSuccess() && ifSignValid(); } public Boolean ifOpenAppValid(){ return ifResultSuccess() && ifOpenAppSignValid(); } private Boolean ifResultSuccess(){ return "SUCCESS".equals(this.returnCode) && "SUCCESS".equals(this.resultCode); } private Boolean ifSignValid(){ return WeChatUtils.generateSign(this).equals(this.sign); } private Boolean ifOpenAppSignValid(){ return WeChatUtils.generateOpenAppSign(this).equals(this.sign); } } 生成签名的工具类 package com.hfcsbc.orderservice.util; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.hfcsbc.orderservice.constant.WeChatProperty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.StringUtils; import java.util.Map; import java.util.TreeMap; import static com.google.common.collect.Maps.newTreeMap; /** * Created by lijian on 2017/9/27 */ @Slf4j public class WeChatUtils { /** * 微信app支付、扫码支付 根据请求对象生成签名,具体步骤: * 1.将对象转换成按字典排序的map * 2.将map拼装成排序字符串,并拼接key * 3.md5编码,并转成大写 * @param request * @return */ public static String generateSign(Object request){ Map<String, String> sortedMap = convertToSortedMap(request); String sortRequest = convertToStr(sortedMap) + "key=" + WeChatProperty.KEY; return DigestUtils.md5Hex(sortRequest).toUpperCase(); } public static String generateOpenAppSign(Object request){ Map<String, String> sortedMap = convertToSortedMap(request); String sortRequest = convertToStr(sortedMap) + "key=" + WeChatProperty.OPEN_KEY; return DigestUtils.md5Hex(sortRequest).toUpperCase(); } /** * 将对象转换成排序的map * @param object * @return */ private static Map<String, String> convertToSortedMap(Object object){ ObjectMapper objectMapper = new ObjectMapper(); Map<String, String> sortedMap = objectMapper.convertValue(object, TreeMap.class); Map formatedMap = newTreeMap(); sortedMap.forEach((key, value) -> { // sign属性不参与签名 if(!key.equals("sign")){ JacksonXmlProperty jacksonXmlProperty = obtainJacksonXmlPropertyAnnotationByKey(object, key); formatedMap.put(jacksonXmlProperty.localName(), value); } }); return formatedMap; } /** * 获取注解上的属性名称 * 根据微信支付文档,有些属性不遵循驼峰命名规范,但是我们项目中的属性是驼峰的, * 不可能为了接入微信支付而打破代码的命名规范,因此在属性上加了注解,注解中的属性名称与微信支付中一致 * @param object * @param key * @return */ private static JacksonXmlProperty obtainJacksonXmlPropertyAnnotationByKey(Object object, String key){ JacksonXmlProperty jacksonXmlProperty = null; try { jacksonXmlProperty = object.getClass().getDeclaredField(key).getAnnotation(JacksonXmlProperty.class); } catch (NoSuchFieldException e) { e.printStackTrace(); } return jacksonXmlProperty; } /** * 将排序的map转成有序字符串 * @param sortedMap * @return */ private static String convertToStr(Map<String, String> sortedMap){ final String[] temp = {""}; sortedMap.forEach((key, value) -> { if(StringUtils.isNotBlank(value)){ temp[0] += key + "=" + value + "&"; } }); return temp[0]; } }
    最新回复(0)