前期准备工作
1. 微信各平台功能认识
- 1.1 微信开放平台: 支持移动应用,公众号的开发,创建应用并得到APPID,使你的应用支持微信支付。
- 1.2 微信公众平台: 微信小程序,服务号,订阅号的开发。
- 1.3 微信商户平台: 提供微信应用所需要的支付产品,类似支付宝开放平台对应用进行功能签约。
注
:微信支付交易发起依赖于公众号、小程序、移动应用(即APPID)与商户号(即MCHID)的绑定关系,因此商户在完成签约后,需要确认当前商户号同APPID的绑定关系,方可使用
参考url:https://pay.weixin.qq.com/static/pay_setting/appid_protocol.shtml
2. 微信开发平台创建应用信息
- 2.1 创建应用
- 2.2 应用信息APPID
- 2.3 签约APP
3. 微信商户平台信息
- 3.1 商户信息MCH_ID
4.使用feign通过RPC进行微信支付接入
实战
1. 配置及依赖
- 1.1 依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>//参数编码依赖
<version>1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>//远程调用feign
<version>2.1.2.RELEASE</version>
</dependency>
- 1.2 配置
- 1.2.1 前期准备获取的参数
- 1.2.2 接口参数编码配置
- 1.2.1 前期准备获取的参数
@Configuration
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));
}
}
注:
微信APP支付拉起支付分两步,第一步统一下单获取prepay_id及相关返回参数,第二步再次构造参数调起微信支付接口。
2. 请求参数及返回值
- 请求参数
@Data
@AllArgsConstructor
public class WeChatPayCommand {
private Integer fee;//订单金额 单位分----微信支付金额单位统一为分
private String title;//标题
private String remark;//备注
private String ip;//ip地址
}
- 接口返回值
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Slf4j
public class WeChatAppPaySecondResponse {
//1.调起支付再次构造的参数
@JacksonXmlProperty(localName = "appid")
private String appId;
@JacksonXmlProperty(localName = "partnerid")
private String partnerId;
@JacksonXmlProperty(localName = "prepayid")
private String prepayId;
@JacksonXmlProperty(localName = "package")
private String pack;
@JacksonXmlProperty(localName = "noncestr")
private String nonceStr;
@JacksonXmlProperty(localName = "timestamp")
private String timestamp;
@JacksonXmlProperty(localName = "sign")
private String sign;
private String result; // 不参与签名,成功标志
}
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JacksonXmlRootElement(localName = "xml")
public class WeChatAppPayResponse {
//2.统一下单获取的返回值
//返回结果此返回结果是手机支付的返回值
@JacksonXmlProperty(localName = "return_code")
private String returnCode;
@JacksonXmlProperty(localName = "return_msg")
private String returnMsg;
//return_code为success时返回
@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")
//return_code,result_code为success时返回
private String tradeType;
@JacksonXmlProperty(localName = "prepay_id")
private String prepayId;//预支付交易id
}
3. 代码
- 3.1 业务代码
public WeChatAppPaySecondResponse generateAppPayInfo(WeChatPayCommand command) {
WeChatPayRequest request = WeChatPayRequestBuilder.generateWeChatAppPayRequest(command,weChatAppPayCallBack);
WeChatAppPayResponse response = weChatClient.appOrderPayInfo(request);//统一下单生成预支付订单信息
if(!response.isSuccess()){
return null;
}
//获取到prepay_id后将参数再次签名,重新构造参数 WeChatAppPaySecondResponse 传输给APP发起支付
return WeChatAppPaySecondResponse.buildAppPaySecondResponseDto(response);
}
- 3.2 获取请求参数
public static WeChatPayRequest generateWeChatAppPayRequest(WeChatPayCommand command, String notifyUrl){
WeChatPayRequest request = WeChatPayRequest.builder()
.appId(WeChatProperty.APP_ID)
.mchId(WeChatProperty.MCH_ID)
.nonceStr(WeChatPayUtil.generateWeChatNonceStr()) : notifyUrl是微信支付成功后异步回调处理通知地址即我们自己处理业务的接口程序地址
.signType(WeChatProperty.SIGN_TYPE)
.body(command.getTitle())
.attach(command.getRemark())
.outTradeNo(generateTradeId())
.totalFee(command.getFee().toString())
.spbillCreateIp(command.getIp())
.timeStart(DateUtil.obtainStartTime())
.timeExpire(DateUtil.obtainEndTime())
.notifyUrl(notifyUrl)
.tradeType(WeChatProperty.APP_PAY)
.build();
request.setSign(WeChatPayUtil.generateSign(request));
return request;
}
- 3.3 生成微信支付签名
public static String generateSign(Object request){
Map<String, String> sortedMap = convertToSortedMap(request);
String sortRequest = convertToStr(sortedMap) + "key=" + WeChatProperty.KEY;
return DigestUtils.md5Hex(sortRequest).toUpperCase();
}
private static Map<String, String> convertToSortedMap(Object object){
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> sortedMap = objectMapper.convertValue(object, TreeMap.class);
Map formatedMap = new TreeMap();
sortedMap.forEach((key, value) -> {
if(!key.equals("sign") && !key.equals("result")){
JacksonXmlProperty jacksonXmlProperty = obtainJacksonXmlPropertyAnnotationByKey(object, key);
formatedMap.put(jacksonXmlProperty.localName(), value);
}
});
return formatedMap;
}
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;
}
private static String convertToStr(Map<String, String> sortedMap){
final String[] temp = {""};
sortedMap.forEach((key, value) -> {
if(!StringUtils.isEmpty(value)){
temp[0] += key + "=" + value + "&";
}
});
return temp[0];
}
- 3.4 再次构造调起支付接口参数
private static WeChatAppPaySecondResponse buildSuccessResponse(WeChatAppPayResponse response){
WeChatAppPaySecondResponse weChatAppPayResponseDto = WeChatAppPaySecondResponse.builder()
.appId(response.getAppId())
.partnerId(response.getMchId())
.prepayId(response.getPrepayId())
.pack(WeChatProperty.PACKAGE)
.nonceStr(WeChatPayUtil.generateNonceStr())
.timestamp(WeChatPayUtil.generateTimestamp())
.result(ResultCode.SUCCESS)
.build();
weChatAppPayResponseDto.setSign(WeChatPayUtil.generateSign(weChatAppPayResponseDto));//将参数再次签名传输给APP发起支付
return weChatAppPayResponseDto;
}
- 3.5 支付结果通知异步处理回调
参考:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
- 3.6 微信支付接口的调用
@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
WeChatAppPayResponse appOrderPayInfo(@RequestBody WeChatPayRequest request);
}
总结
微信APP支付拉起支付接口过于繁琐,需两次构造参数统一下单+调起支付接口,没有支付宝处理的好。而且调取接口以RPC形式过于麻烦。