本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
近期有个需求,需要从企业商户号中给用户提现,比如用户看视频,看够一定时间,然后给用户奖励多少钱,钱够一定的数目是可以提现的。本文所写的是微信商户提现到用户微信零钱。
前提:商户号一定要有最少三十天的流水。详情见微信商户提现API
如果直接去看微信的API,然后写接口,因为微信涉及到很多安全问题,比如签名,各种验证,如果没有第三方jar全自己手写很困难,而且容易出错。
本文找的第三方写好的jar,开发者只需要关注自己的业务,提现逻辑只需要调用个方法就好,很是便捷,基本上半个小时能跑通微信提现到零钱业务。
pom.xml引入源码jar包
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.1.0</version>
</dependency>
配置文件
wx:
pay:
#公众号appid需要用户登录的微信服务号应用的APPID
appId: 111111
#商户号
mchId: 123456
#商户密钥
mchKey: 123456789
#p12证书文件的绝对路径或者以classpath:开头的类路径
keyPath: classpath:test.p12
mchId和mchKey在商户后台都能拿到,p12证书在商户后台去下载,下载的文件放到resources下面就是以classpath:开头的类路径
初始化配置文件
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* <pre>
* 微信支付属性配置类
* Created by Binary Wang on 2019/4/17.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/**
* 设置微信公众号或者小程序等的appid.
*/
private String appId;
/**
* 微信支付商户号.
*/
private String mchId;
/**
* 微信支付商户密钥.
*/
private String mchKey;
/**
* 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除.
*/
private String subAppId;
/**
* 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除.
*/
private String subMchId;
/**
* apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定.
*/
private String keyPath;
/**
* 微信支付分serviceId
*/
private String serviceId;
/**
* 证书序列号
*/
private String certSerialNo;
/**
* apiV3秘钥
*/
private String apiv3Key;
/**
* 微信支付分回调地址
*/
private String payScoreNotifyUrl;
/**
* apiv3 商户apiclient_key.pem
*/
private String privateKeyPath;
/**
* apiv3 商户apiclient_cert.pem
*/
private String privateCertPath;
}
微信支付自动配置
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <pre>
* 微信支付自动配置
* Created by BinaryWang on 2019/4/17.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Configuration
@EnableConfigurationProperties(WxPayProperties.class)
@ConditionalOnClass(WxPayService.class)
@ConditionalOnProperty(prefix = "wx.pay", value = "enabled", matchIfMissing = true)
public class WxPayAutoConfiguration {
private WxPayProperties properties;
@Autowired
public WxPayAutoConfiguration(WxPayProperties properties) {
this.properties = properties;
}
/**
* 构造微信支付服务对象.
*
* @return 微信支付service
*/
@Bean
@ConditionalOnMissingBean(WxPayService.class)
public WxPayService wxPayService() {
final WxPayServiceImpl wxPayService = new WxPayServiceImpl();
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
//以下是apiv3以及支付分相关
payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId()));
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl()));
payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath()));
payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath()));
payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo()));
payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key()));
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
主要的提现逻辑
EntPayRequest entPayRequest = new EntPayRequest();
//商户ID
entPayRequest.setMchId(wxPayService.getConfig().getMchId());
//从配置读的用户登录的微信服务号应用,如果是多个服务号从自己数据库读取,本文只针对一个服务号提现一个应用appid
entPayRequest.setMchAppid(wxPayService.getConfig().getAppId());
//提现到哪个微信服务号关注人的openid
entPayRequest.setOpenid("openid");
//当前提现的订单号-自己创建
entPayRequest.setNonceStr("oooopppxxx");
//保证唯一性幂等性的ID自己创建
entPayRequest.setPartnerTradeNo("xxxxx");
entPayRequest.setCheckName("no_check");
//提现金额单位分
entPayRequest.setAmount(100);
//Ip地址 非必传字段
entPayRequest.setSpbillCreateIp(InetAddress.getLocalHost().getHostAddress());
entPayRequest.setDescription("游戏提现标题");
//调用提现API
EntPayResult entPayResult= wxPayService.getEntPayService().entPay(entPayRequest);
注意:提现的现金单位是分,其他主要参数对照微信官方提现到零钱的API接口。
总结
引入第三方支付jar,配置商户信息,初始化微信支付的两个配置类,然后是提现支付的业务逻辑。
如果是工业级别的提现,因为涉及到现金,好的建议是提现申请和提现到用户零钱异步化。
在提现到零钱的业务中做验证策略,防止被刷,提现到零钱的业务需要做到随身可以停止,当然商户后台也有对用户每天最多提现的次数,和最大提现金额的限制。
本文只是简单的跑通了提现的主要流程,涉及到安全和核心开发,需要自己去做安全的管控。