21 files added
1 files modified
New file |
| | |
| | | package com.kidgrow.oprationcenter.model; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
| | | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import lombok.NoArgsConstructor; |
| | | import org.hibernate.validator.constraints.NotEmpty; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br> |
| | | * |
| | | * @version 1.0 |
| | | * @Description: <br> |
| | | * @Project: 用户中心<br> |
| | | * @CreateDate: Created in 2020-09-17 17:24:49 <br> |
| | | * @Author: <a href="4345453@kidgrow.com">liuke</a> |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = false) |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @TableName("saas_client_pay") |
| | | public class SaasClientPay { |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | private String id; |
| | | /** |
| | | * 诊断医生所在科室 |
| | | */ |
| | | private String createHospitalDepartment; |
| | | /** |
| | | * 科室的唯一标识 |
| | | */ |
| | | @NotEmpty(message = "科室的唯一标识不能为空") |
| | | private String createHospitalDepartid; |
| | | /** |
| | | * 创建医院的唯一标识 |
| | | */ |
| | | @NotEmpty(message = "创建医院的唯一标识不能为空") |
| | | private Long createHospitalId; |
| | | /** |
| | | * 创建医院名称 |
| | | */ |
| | | private String createHospitalName; |
| | | /** |
| | | * 金额 (单位为分) |
| | | */ |
| | | @NotEmpty(message = "金额 (单位为分)不能为空") |
| | | private Integer payPrice; |
| | | /** |
| | | * 内部订单号 |
| | | */ |
| | | @NotEmpty(message = "内部订单号不能为空") |
| | | private String outTradeNo; |
| | | /** |
| | | * 支付完成时间 |
| | | */ |
| | | private Date payTime; |
| | | /** |
| | | * 支付状态0订单创建1订单提交到网关2订单支付成功-1订单支付失败 |
| | | */ |
| | | private Integer payStatus; |
| | | /** |
| | | * 支付方式,暂时只用微信和支付宝0为微信1为支付宝 |
| | | */ |
| | | private Integer payMethod; |
| | | /** |
| | | * 关联诊断记录ID |
| | | */ |
| | | @NotEmpty(message = "关联诊断记录ID不能为空") |
| | | private String diaId; |
| | | /** |
| | | * 支付宝交易号 微信支付订单号 |
| | | */ |
| | | private String tradeNo; |
| | | /** |
| | | * 微信生成的预支付会话标识 |
| | | */ |
| | | private String prepayId; |
| | | /** |
| | | * 支付给喜高的费用 |
| | | */ |
| | | private String payKidgrow; |
| | | /** |
| | | * 支付给客户的费用 |
| | | */ |
| | | private String payCustom; |
| | | /** |
| | | * 支付状态 |
| | | */ |
| | | private Boolean type; |
| | | |
| | | @JsonSerialize( |
| | | using = ToStringSerializer.class |
| | | ) |
| | | @TableId( |
| | | type = IdType.ASSIGN_ID |
| | | ) |
| | | @DateTimeFormat( |
| | | pattern = "yyyy-MM-dd HH:mm:ss" |
| | | ) |
| | | @JsonFormat( |
| | | timezone = "GMT+8", |
| | | pattern = "yyyy-MM-dd HH:mm:ss" |
| | | ) |
| | | @TableField( |
| | | fill = FieldFill.INSERT |
| | | ) |
| | | private Date createTime; |
| | | @DateTimeFormat( |
| | | pattern = "yyyy-MM-dd HH:mm:ss" |
| | | ) |
| | | @JsonFormat( |
| | | timezone = "GMT+8", |
| | | pattern = "yyyy-MM-dd HH:mm:ss" |
| | | ) |
| | | @TableField( |
| | | fill = FieldFill.INSERT_UPDATE |
| | | ) |
| | | private Date updateTime; |
| | | @JsonSerialize( |
| | | using = ToStringSerializer.class |
| | | ) |
| | | @TableField( |
| | | fill = FieldFill.INSERT |
| | | ) |
| | | private long createUserId; |
| | | @TableField( |
| | | fill = FieldFill.INSERT |
| | | ) |
| | | private String createUserName; |
| | | @JsonSerialize( |
| | | using = ToStringSerializer.class |
| | | ) |
| | | @TableField( |
| | | fill = FieldFill.INSERT_UPDATE |
| | | ) |
| | | private long updateUserId; |
| | | @TableField( |
| | | fill = FieldFill.INSERT_UPDATE |
| | | ) |
| | | private String updateUserName; |
| | | } |
| | |
| | | <groupId>org.springframework.cloud</groupId> |
| | | <artifactId>spring-cloud-starter-bus-amqp</artifactId> |
| | | </dependency> |
| | | <!-- 支付宝--> |
| | | <dependency> |
| | | <groupId>com.alipay.sdk</groupId> |
| | | <artifactId>alipay-sdk-java</artifactId> |
| | | <version>4.10.124.ALL</version> |
| | | </dependency> |
| | | </dependencies> |
| | | </project> |
New file |
| | |
| | | package com.kidgrow.oprationcenter.mapper; |
| | | |
| | | import com.kidgrow.db.mapper.SuperMapper; |
| | | import com.kidgrow.oprationcenter.model.SaasClientPay; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br> |
| | | * @Description: <br> |
| | | * @Project: 用户中心<br> |
| | | * @CreateDate: Created in 2020-09-17 16:21:03 <br> |
| | | * @Author: <a href="4345453@kidgrow.com">liuke</a> |
| | | * @version 1.0 |
| | | */ |
| | | public interface SaasClientPayMapper extends SuperMapper<SaasClientPay> { |
| | | /** |
| | | * 分页查询列表 |
| | | * @param page |
| | | * @param params |
| | | * @return |
| | | */ |
| | | List<SaasClientPay> findList(Page<SaasClientPay> page, @Param("p") Map<String, Object> params); |
| | | |
| | | /** |
| | | * 根据SaasClientPay对象当做查询条件进行查询 |
| | | * @param |
| | | * @return SaasClientPay对象 |
| | | */ |
| | | SaasClientPay findByObject(@Param("p") SaasClientPay saasClientPay); |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.service; |
| | | |
| | | |
| | | import com.kidgrow.common.model.PageResult; |
| | | import com.kidgrow.common.service.ISuperService; |
| | | import com.kidgrow.oprationcenter.model.SaasClientPay; |
| | | |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br> |
| | | * @Description: <br> |
| | | * @Project: 用户中心<br> |
| | | * @CreateDate: Created in 2020-09-17 16:21:03 <br> |
| | | * @Author: <a href="4345453@kidgrow.com">liuke</a> |
| | | * @version 1.0 |
| | | */ |
| | | public interface ISaasClientPayService extends ISuperService<SaasClientPay> { |
| | | /** |
| | | * 列表 |
| | | * @param params |
| | | * @return |
| | | */ |
| | | PageResult<SaasClientPay> findList(Map<String, Object> params); |
| | | |
| | | |
| | | /** |
| | | * 根据SaasClientPay对象当做查询条件进行查询 |
| | | * @param saasClientPay |
| | | * @return SaasClientPay对象 |
| | | */ |
| | | SaasClientPay findByObject(SaasClientPay saasClientPay); |
| | | } |
| | | |
New file |
| | |
| | | package com.kidgrow.oprationcenter.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.kidgrow.common.model.PageResult; |
| | | import com.kidgrow.common.service.impl.SuperServiceImpl; |
| | | import com.kidgrow.oprationcenter.mapper.SaasClientPayMapper; |
| | | import com.kidgrow.oprationcenter.model.SaasClientPay; |
| | | import com.kidgrow.oprationcenter.service.ISaasClientPayService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections4.MapUtils; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | |
| | | /** |
| | | * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br> |
| | | * @Description: <br> |
| | | * @Project: 用户中心<br> |
| | | * @CreateDate: Created in 2020-09-17 16:21:03 <br> |
| | | * @Author: <a href="4345453@kidgrow.com">liuke</a> |
| | | * @version 1.0 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class SaasClientPayServiceImpl extends SuperServiceImpl<SaasClientPayMapper, SaasClientPay> implements ISaasClientPayService { |
| | | /** |
| | | * 列表 |
| | | * @param params |
| | | * @return |
| | | */ |
| | | @Override |
| | | public PageResult<SaasClientPay> findList(Map<String, Object> params){ |
| | | Page<SaasClientPay> page = new Page<>(MapUtils.getInteger(params, "page"), MapUtils.getInteger(params, "limit")); |
| | | List<SaasClientPay> list = baseMapper.findList(page, params); |
| | | return PageResult.<SaasClientPay>builder().data(list).code(0).count(page.getTotal()).build(); |
| | | } |
| | | |
| | | /** |
| | | * 根据SaasClientPay对象当做查询条件进行查询 |
| | | * @param saasClientPay |
| | | * @return SaasClientPay |
| | | */ |
| | | @Override |
| | | public SaasClientPay findByObject(SaasClientPay saasClientPay){ |
| | | return baseMapper.findByObject(saasClientPay); |
| | | } |
| | | } |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <!----> |
| | | <mapper namespace="com.kidgrow.oprationcenter.mapper.SaasClientPayMapper"> |
| | | <!--定义查询列--> |
| | | <sql id="Column_List"> |
| | | * |
| | | </sql> |
| | | |
| | | <!--sql查询片段--> |
| | | <sql id="where"> |
| | | <where> |
| | | <!--查询条件自行添加--> |
| | | is_del=0 |
| | | <if test="p.id != null and p.id !=''"> |
| | | and id = #{p.id} |
| | | </if> |
| | | <if test="p.createHospitalDepartment != null and p.createHospitalDepartment !=''"> |
| | | and create_hospital_department = #{p.createHospitalDepartment} |
| | | </if> |
| | | <if test="p.createHospitalDepartid != null and p.createHospitalDepartid !=''"> |
| | | and create_hospital_departid = #{p.createHospitalDepartid} |
| | | </if> |
| | | <if test="p.createHospitalId != null and p.createHospitalId !=''"> |
| | | and create_hospital_id = #{p.createHospitalId} |
| | | </if> |
| | | <if test="p.createHospitalName != null and p.createHospitalName !=''"> |
| | | and create_hospital_name = #{p.createHospitalName} |
| | | </if> |
| | | <if test="p.payPrice != null and p.payPrice !=''"> |
| | | and pay_price = #{p.payPrice} |
| | | </if> |
| | | <if test="p.outTradeNo != null and p.outTradeNo !=''"> |
| | | and out_trade_no = #{p.outTradeNo} |
| | | </if> |
| | | <if test="p.payTime != null and p.payTime !=''"> |
| | | and pay_time = #{p.payTime} |
| | | </if> |
| | | <if test="p.payStatus != null and p.payStatus !=''"> |
| | | and pay_status = #{p.payStatus} |
| | | </if> |
| | | <if test="p.payMethod != null and p.payMethod !=''"> |
| | | and pay_method = #{p.payMethod} |
| | | </if> |
| | | <if test="p.diaId != null and p.diaId !=''"> |
| | | and dia_id = #{p.diaId} |
| | | </if> |
| | | <if test="p.tradeNo != null and p.tradeNo !=''"> |
| | | and trade_no = #{p.tradeNo} |
| | | </if> |
| | | <if test="p.prepayId != null and p.prepayId !=''"> |
| | | and prepay_id = #{p.prepayId} |
| | | </if> |
| | | <if test="p.createUserId != null and p.createUserId !=''"> |
| | | and create_user_id = #{p.createUserId} |
| | | </if> |
| | | <if test="p.createUserName != null and p.createUserName !=''"> |
| | | and create_user_name = #{p.createUserName} |
| | | </if> |
| | | <if test="p.updateUserId != null and p.updateUserId !=''"> |
| | | and update_user_id = #{p.updateUserId} |
| | | </if> |
| | | <if test="p.updateUserName != null and p.updateUserName !=''"> |
| | | and update_user_name = #{p.updateUserName} |
| | | </if> |
| | | <if test="p.updateTime != null and p.updateTime !=''"> |
| | | and update_time = #{p.updateTime} |
| | | </if> |
| | | </where> |
| | | </sql> |
| | | |
| | | <!--定义根据-SaasClientPay当作查询条件返回对象--> |
| | | <select id="findByObject" resultType="com.kidgrow.oprationcenter.model.SaasClientPay"> |
| | | select <include refid="Column_List"/> |
| | | from saas_client_pay |
| | | <include refid="where"/> |
| | | order by id desc |
| | | limit 1 |
| | | </select> |
| | | |
| | | <!--定义根据-SaasClientPay当作查询条件返回对象集合--> |
| | | <select id="findList" resultType="com.kidgrow.oprationcenter.model.SaasClientPay"> |
| | | select <include refid="Column_List"/> |
| | | from saas_client_pay |
| | | <include refid="where"/> |
| | | order by id desc |
| | | </select> |
| | | </mapper> |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.alipay; |
| | | |
| | | import com.alipay.api.AlipayClient; |
| | | import com.alipay.api.DefaultAlipayClient; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | @Configuration |
| | | public class AlipayConfig { |
| | | |
| | | @Resource |
| | | private AlipayProperties alipayProperties; |
| | | |
| | | @Bean |
| | | public AlipayClient initAlipayClient(){ |
| | | return new DefaultAlipayClient(alipayProperties.getGatewayUrl(), |
| | | alipayProperties.getAppId(),alipayProperties.getAppPrivateKey(), |
| | | alipayProperties.getFormat(),alipayProperties.getCharset(), |
| | | alipayProperties.getAlipayPublicKey(),alipayProperties.getSignType()); |
| | | } |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.alipay; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | @Data |
| | | @Component |
| | | @ConfigurationProperties("pay.alipay") |
| | | public class AlipayProperties { |
| | | |
| | | private String appId; |
| | | private String gatewayUrl; |
| | | private String format; |
| | | private String charset; |
| | | private String signType; |
| | | private String appPrivateKey; |
| | | private String alipayPublicKey; |
| | | |
| | | private String returnUrl; |
| | | private String notifyUrl; |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | /** |
| | | * 域名管理,实现主备域名自动切换 |
| | | */ |
| | | public abstract interface IWXPayDomain { |
| | | /** |
| | | * 上报域名网络状况 |
| | | * @param domain 域名。 比如:api.mch.weixin.qq.com |
| | | * @param elapsedTimeMillis 耗时 |
| | | * @param ex 网络请求中出现的异常。 |
| | | * null表示没有异常 |
| | | * ConnectTimeoutException,表示建立网络连接异常 |
| | | * UnknownHostException, 表示dns解析异常 |
| | | */ |
| | | abstract void report(final String domain, long elapsedTimeMillis, final Exception ex); |
| | | |
| | | /** |
| | | * 获取域名 |
| | | * @param config 配置 |
| | | * @return 域名 |
| | | */ |
| | | abstract DomainInfo getDomain(final WXPayConfig config); |
| | | |
| | | static class DomainInfo{ |
| | | public String domain; //域名 |
| | | public boolean primaryDomain; //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名 |
| | | public DomainInfo(String domain, boolean primaryDomain) { |
| | | this.domain = domain; |
| | | this.primaryDomain = primaryDomain; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "DomainInfo{" + |
| | | "domain='" + domain + '\'' + |
| | | ", primaryDomain=" + primaryDomain + |
| | | '}'; |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.InputStream; |
| | | |
| | | public class MyConfig extends WXPayConfig{ |
| | | |
| | | public MyConfig(WeiXinOfficPayProperties weiXinOfficPayProperties) { |
| | | this.weiXinOfficPayProperties = weiXinOfficPayProperties; |
| | | } |
| | | |
| | | private WeiXinOfficPayProperties weiXinOfficPayProperties; |
| | | |
| | | private byte[] certData; |
| | | |
| | | public MyConfig() throws Exception { |
| | | String certPath = "D:\\develop\\chengxu\\houtai\\3cc.txt"; |
| | | File file = new File(certPath); |
| | | InputStream certStream = new FileInputStream(file); |
| | | this.certData = new byte[(int) file.length()]; |
| | | certStream.read(this.certData); |
| | | certStream.close(); |
| | | } |
| | | //wx84c77dcda51c612c |
| | | @Override |
| | | public String getAppID() { |
| | | return weiXinOfficPayProperties.getAppID(); |
| | | // return "wx84c77dcda51c612c"; |
| | | } |
| | | @Override |
| | | //"1386873502" |
| | | public String getMchID() { |
| | | return weiXinOfficPayProperties.getMchID(); |
| | | // return "1386873502"; |
| | | } |
| | | @Override |
| | | //"GSFcX6WdgRTAS6154EW14WE3SGBSER49" |
| | | public String getKey() { |
| | | return weiXinOfficPayProperties.getKey(); |
| | | // return "GSFcX6WdgRTAS6154EW14WE3SGBSER49"; |
| | | } |
| | | @Override |
| | | public InputStream getCertStream() { |
| | | ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); |
| | | return certBis; |
| | | } |
| | | @Override |
| | | public int getHttpConnectTimeoutMs() { |
| | | return 8000; |
| | | } |
| | | @Override |
| | | public int getHttpReadTimeoutMs() { |
| | | return 10000; |
| | | } |
| | | |
| | | @Override |
| | | IWXPayDomain getWXPayDomain() throws Exception { |
| | | MyIWXPayDomain myIWXPayDomain=new MyIWXPayDomain(); |
| | | MyConfig myConfig = new MyConfig(); |
| | | System.out.println(myConfig.getDomain()); |
| | | myIWXPayDomain.getDomain(myConfig); |
| | | return myIWXPayDomain; |
| | | } |
| | | |
| | | @Override |
| | | public String getDomain() { |
| | | return "api.mch.weixin.qq.com"; |
| | | } |
| | | |
| | | public String getPayNotifyUrl() {return weiXinOfficPayProperties.getPayNotifyUrl();} |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | public class MyIWXPayDomain implements IWXPayDomain { |
| | | @Override |
| | | public void report(String domain, long elapsedTimeMillis, Exception ex) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public DomainInfo getDomain(WXPayConfig config) { |
| | | IWXPayDomain.DomainInfo domainInfo=new DomainInfo(config.getDomain(),true); |
| | | return domainInfo; |
| | | } |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | //import com.github.wxpay.sdk.WXPayConstants.SignType; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | public class WXPay { |
| | | |
| | | private WXPayConfig config; |
| | | private WXPayConstants.SignType signType; |
| | | private boolean autoReport; |
| | | private boolean useSandbox; |
| | | private String notifyUrl; |
| | | private WXPayRequest wxPayRequest; |
| | | |
| | | public WXPay(final WXPayConfig config) throws Exception { |
| | | this(config, null, true, false); |
| | | } |
| | | |
| | | public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception { |
| | | this(config, null, autoReport, false); |
| | | } |
| | | |
| | | |
| | | public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{ |
| | | this(config, null, autoReport, useSandbox); |
| | | } |
| | | |
| | | public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception { |
| | | this(config, notifyUrl, true, false); |
| | | } |
| | | |
| | | public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception { |
| | | this(config, notifyUrl, autoReport, false); |
| | | } |
| | | |
| | | public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception { |
| | | this.config = config; |
| | | this.notifyUrl = notifyUrl; |
| | | this.autoReport = autoReport; |
| | | this.useSandbox = useSandbox; |
| | | if (useSandbox) { |
| | | this.signType = WXPayConstants.SignType.MD5; // 沙箱环境 |
| | | } |
| | | else { |
| | | this.signType = WXPayConstants.SignType.HMACSHA256; |
| | | } |
| | | this.wxPayRequest = new WXPayRequest(config); |
| | | } |
| | | |
| | | private void checkWXPayConfig() throws Exception { |
| | | if (this.config == null) { |
| | | throw new Exception("config is null"); |
| | | } |
| | | if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) { |
| | | throw new Exception("appid in config is empty"); |
| | | } |
| | | if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) { |
| | | throw new Exception("appid in config is empty"); |
| | | } |
| | | // if (this.config.getCertStream() == null) { |
| | | // throw new Exception("cert stream in config is empty"); |
| | | // } |
| | | if (this.config.getWXPayDomain() == null){ |
| | | throw new Exception("config.getWXPayDomain() is null"); |
| | | } |
| | | |
| | | if (this.config.getHttpConnectTimeoutMs() < 10) { |
| | | throw new Exception("http connect timeout is too small"); |
| | | } |
| | | if (this.config.getHttpReadTimeoutMs() < 10) { |
| | | throw new Exception("http read timeout is too small"); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br> |
| | | * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口 |
| | | * |
| | | * @param reqData |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception { |
| | | reqData.put("appid", config.getAppID()); |
| | | reqData.put("mch_id", config.getMchID()); |
| | | reqData.put("nonce_str", WXPayUtil.generateNonceStr()); |
| | | if (WXPayConstants.SignType.MD5.equals(this.signType)) { |
| | | reqData.put("sign_type", WXPayConstants.MD5); |
| | | } |
| | | else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) { |
| | | reqData.put("sign_type", WXPayConstants.HMACSHA256); |
| | | } |
| | | reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType)); |
| | | return reqData; |
| | | } |
| | | |
| | | /** |
| | | * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。 |
| | | * |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return 签名是否有效 |
| | | * @throws Exception |
| | | */ |
| | | public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception { |
| | | // 返回数据的签名方式和请求中给定的签名方式是一致的 |
| | | return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType); |
| | | } |
| | | |
| | | /** |
| | | * 判断支付结果通知中的sign是否有效 |
| | | * |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return 签名是否有效 |
| | | * @throws Exception |
| | | */ |
| | | public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception { |
| | | String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE); |
| | | WXPayConstants.SignType signType; |
| | | if (signTypeInData == null) { |
| | | signType = WXPayConstants.SignType.MD5; |
| | | } |
| | | else { |
| | | signTypeInData = signTypeInData.trim(); |
| | | if (signTypeInData.length() == 0) { |
| | | signType = WXPayConstants.SignType.MD5; |
| | | } |
| | | else if (WXPayConstants.MD5.equals(signTypeInData)) { |
| | | signType = WXPayConstants.SignType.MD5; |
| | | } |
| | | else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) { |
| | | signType = WXPayConstants.SignType.HMACSHA256; |
| | | } |
| | | else { |
| | | throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData)); |
| | | } |
| | | } |
| | | return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 不需要证书的请求 |
| | | * @param urlSuffix String |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public String requestWithoutCert(String urlSuffix, Map<String, String> reqData, |
| | | int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String msgUUID = reqData.get("nonce_str"); |
| | | String reqBody = WXPayUtil.mapToXml(reqData); |
| | | |
| | | String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport); |
| | | return resp; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 需要证书的请求 |
| | | * @param urlSuffix String |
| | | * @param reqData 向wxpay post的请求数据 Map |
| | | * @param connectTimeoutMs 超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public String requestWithCert(String urlSuffix, Map<String, String> reqData, |
| | | int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String msgUUID= reqData.get("nonce_str"); |
| | | String reqBody = WXPayUtil.mapToXml(reqData); |
| | | |
| | | String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport); |
| | | return resp; |
| | | } |
| | | |
| | | /** |
| | | * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。 |
| | | * @param xmlStr API返回的XML格式数据 |
| | | * @return Map类型数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> processResponseXml(String xmlStr) throws Exception { |
| | | String RETURN_CODE = "return_code"; |
| | | String return_code; |
| | | Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr); |
| | | if (respData.containsKey(RETURN_CODE)) { |
| | | return_code = respData.get(RETURN_CODE); |
| | | } |
| | | else { |
| | | throw new Exception(String.format("No `return_code` in XML: %s", xmlStr)); |
| | | } |
| | | |
| | | if (return_code.equals(WXPayConstants.FAIL)) { |
| | | return respData; |
| | | } |
| | | else if (return_code.equals(WXPayConstants.SUCCESS)) { |
| | | if (this.isResponseSignatureValid(respData)) { |
| | | return respData; |
| | | } |
| | | else { |
| | | throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr)); |
| | | } |
| | | } |
| | | else { |
| | | throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 作用:提交刷卡支付<br> |
| | | * 场景:刷卡支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> microPay(Map<String, String> reqData) throws Exception { |
| | | return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:提交刷卡支付<br> |
| | | * 场景:刷卡支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.MICROPAY_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | /** |
| | | * 提交刷卡支付,针对软POS,尽可能做成功 |
| | | * 内置重试机制,最多60s |
| | | * @param reqData |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception { |
| | | return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs()); |
| | | } |
| | | |
| | | /** |
| | | * 提交刷卡支付,针对软POS,尽可能做成功 |
| | | * 内置重试机制,最多60s |
| | | * @param reqData |
| | | * @param connectTimeoutMs |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception { |
| | | int remainingTimeMs = 60*1000; |
| | | long startTimestampMs = 0; |
| | | Map<String, String> lastResult = null; |
| | | Exception lastException = null; |
| | | |
| | | while (true) { |
| | | startTimestampMs = WXPayUtil.getCurrentTimestampMs(); |
| | | int readTimeoutMs = remainingTimeMs - connectTimeoutMs; |
| | | if (readTimeoutMs > 1000) { |
| | | try { |
| | | lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs); |
| | | String returnCode = lastResult.get("return_code"); |
| | | if (returnCode.equals("SUCCESS")) { |
| | | String resultCode = lastResult.get("result_code"); |
| | | String errCode = lastResult.get("err_code"); |
| | | if (resultCode.equals("SUCCESS")) { |
| | | break; |
| | | } |
| | | else { |
| | | // 看错误码,若支付结果未知,则重试提交刷卡支付 |
| | | if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) { |
| | | remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs); |
| | | if (remainingTimeMs <= 100) { |
| | | break; |
| | | } |
| | | else { |
| | | WXPayUtil.getLogger().info("microPayWithPos: try micropay again"); |
| | | if (remainingTimeMs > 5*1000) { |
| | | Thread.sleep(5*1000); |
| | | } |
| | | else { |
| | | Thread.sleep(1*1000); |
| | | } |
| | | continue; |
| | | } |
| | | } |
| | | else { |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | else { |
| | | break; |
| | | } |
| | | } |
| | | catch (Exception ex) { |
| | | lastResult = null; |
| | | lastException = ex; |
| | | } |
| | | } |
| | | else { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (lastResult == null) { |
| | | throw lastException; |
| | | } |
| | | else { |
| | | return lastResult; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 作用:统一下单<br> |
| | | * 场景:公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception { |
| | | return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:统一下单<br> |
| | | * 场景:公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> unifiedOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX; |
| | | } |
| | | if(this.notifyUrl != null) { |
| | | reqData.put("notify_url", this.notifyUrl); |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:查询订单<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception { |
| | | return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:查询订单<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 int |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.ORDERQUERY_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:撤销订单<br> |
| | | * 场景:刷卡支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> reverse(Map<String, String> reqData) throws Exception { |
| | | return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:撤销订单<br> |
| | | * 场景:刷卡支付<br> |
| | | * 其他:需要证书 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.REVERSE_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:关闭订单<br> |
| | | * 场景:公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception { |
| | | return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:关闭订单<br> |
| | | * 场景:公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> closeOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.CLOSEORDER_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:申请退款<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> refund(Map<String, String> reqData) throws Exception { |
| | | return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:申请退款<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br> |
| | | * 其他:需要证书 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.REFUND_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:退款查询<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception { |
| | | return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:退款查询<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.REFUNDQUERY_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception { |
| | | return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:对账单下载<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br> |
| | | * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data, |
| | | * 其中return_code为`SUCCESS`,data为对账单数据。 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return 经过封装的API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX; |
| | | } |
| | | String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim(); |
| | | Map<String, String> ret; |
| | | // 出现错误,返回XML数据 |
| | | if (respStr.indexOf("<") == 0) { |
| | | ret = WXPayUtil.xmlToMap(respStr); |
| | | } |
| | | else { |
| | | // 正常返回csv数据 |
| | | ret = new HashMap<String, String>(); |
| | | ret.put("return_code", WXPayConstants.SUCCESS); |
| | | ret.put("return_msg", "ok"); |
| | | ret.put("data", respStr); |
| | | } |
| | | return ret; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:交易保障<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> report(Map<String, String> reqData) throws Exception { |
| | | return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:交易保障<br> |
| | | * 场景:刷卡支付、公共号支付、扫码支付、APP支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.REPORT_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return WXPayUtil.xmlToMap(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:转换短链接<br> |
| | | * 场景:刷卡支付、扫码支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception { |
| | | return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:转换短链接<br> |
| | | * 场景:刷卡支付、扫码支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.SHORTURL_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:授权码查询OPENID接口<br> |
| | | * 场景:刷卡支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception { |
| | | return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 作用:授权码查询OPENID接口<br> |
| | | * 场景:刷卡支付 |
| | | * @param reqData 向wxpay post的请求数据 |
| | | * @param connectTimeoutMs 连接超时时间,单位是毫秒 |
| | | * @param readTimeoutMs 读超时时间,单位是毫秒 |
| | | * @return API返回数据 |
| | | * @throws Exception |
| | | */ |
| | | public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception { |
| | | String url; |
| | | if (this.useSandbox) { |
| | | url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX; |
| | | } |
| | | else { |
| | | url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX; |
| | | } |
| | | String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs); |
| | | return this.processResponseXml(respXml); |
| | | } |
| | | |
| | | |
| | | } // end class |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import java.io.InputStream; |
| | | |
| | | public abstract class WXPayConfig { |
| | | |
| | | |
| | | |
| | | /** |
| | | * 获取 App ID |
| | | * |
| | | * @return App ID |
| | | */ |
| | | abstract String getAppID(); |
| | | |
| | | |
| | | /** |
| | | * 获取 Mch ID |
| | | * |
| | | * @return Mch ID |
| | | */ |
| | | abstract String getMchID(); |
| | | |
| | | |
| | | /** |
| | | * 获取 API 密钥 |
| | | * |
| | | * @return API密钥 |
| | | */ |
| | | abstract String getKey(); |
| | | |
| | | |
| | | /** |
| | | * 获取商户证书内容 |
| | | * |
| | | * @return 商户证书内容 |
| | | */ |
| | | abstract InputStream getCertStream(); |
| | | |
| | | /** |
| | | * HTTP(S) 连接超时时间,单位毫秒 |
| | | * |
| | | * @return |
| | | */ |
| | | public int getHttpConnectTimeoutMs() { |
| | | return 6*1000; |
| | | } |
| | | |
| | | /** |
| | | * HTTP(S) 读数据超时时间,单位毫秒 |
| | | * |
| | | * @return |
| | | */ |
| | | public int getHttpReadTimeoutMs() { |
| | | return 8*1000; |
| | | } |
| | | |
| | | /** |
| | | * 获取WXPayDomain, 用于多域名容灾自动切换 |
| | | * @return |
| | | */ |
| | | abstract IWXPayDomain getWXPayDomain()throws Exception ; |
| | | |
| | | /** |
| | | * 是否自动上报。 |
| | | * 若要关闭自动上报,子类中实现该函数返回 false 即可。 |
| | | * |
| | | * @return |
| | | */ |
| | | public boolean shouldAutoReport() { |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 进行健康上报的线程的数量 |
| | | * |
| | | * @return |
| | | */ |
| | | public int getReportWorkerNum() { |
| | | return 6; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 健康上报缓存消息的最大数量。会有线程去独立上报 |
| | | * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受 |
| | | * |
| | | * @return |
| | | */ |
| | | public int getReportQueueMaxSize() { |
| | | return 10000; |
| | | } |
| | | |
| | | /** |
| | | * 批量上报,一次最多上报多个数据 |
| | | * |
| | | * @return |
| | | */ |
| | | public int getReportBatchSize() { |
| | | return 10; |
| | | } |
| | | |
| | | public abstract String getDomain(); |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import org.apache.http.client.HttpClient; |
| | | |
| | | /** |
| | | * 常量 |
| | | */ |
| | | public class WXPayConstants { |
| | | |
| | | public enum SignType { |
| | | MD5, HMACSHA256 |
| | | } |
| | | |
| | | public static final String DOMAIN_API = "api.mch.weixin.qq.com"; |
| | | public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com"; |
| | | public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com"; |
| | | public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com"; |
| | | |
| | | |
| | | public static final String FAIL = "FAIL"; |
| | | public static final String SUCCESS = "SUCCESS"; |
| | | public static final String HMACSHA256 = "HMAC-SHA256"; |
| | | public static final String MD5 = "MD5"; |
| | | |
| | | public static final String FIELD_SIGN = "sign"; |
| | | public static final String FIELD_SIGN_TYPE = "sign_type"; |
| | | |
| | | public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9"; |
| | | public static final String USER_AGENT = WXPAYSDK_VERSION + |
| | | " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") + |
| | | ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion(); |
| | | |
| | | public static final String MICROPAY_URL_SUFFIX = "/pay/micropay"; |
| | | public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder"; |
| | | public static final String ORDERQUERY_URL_SUFFIX = "/pay/orderquery"; |
| | | public static final String REVERSE_URL_SUFFIX = "/secapi/pay/reverse"; |
| | | public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder"; |
| | | public static final String REFUND_URL_SUFFIX = "/secapi/pay/refund"; |
| | | public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery"; |
| | | public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill"; |
| | | public static final String REPORT_URL_SUFFIX = "/payitil/report"; |
| | | public static final String SHORTURL_URL_SUFFIX = "/tools/shorturl"; |
| | | public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid"; |
| | | |
| | | // sandbox |
| | | public static final String SANDBOX_MICROPAY_URL_SUFFIX = "/sandboxnew/pay/micropay"; |
| | | public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder"; |
| | | public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery"; |
| | | public static final String SANDBOX_REVERSE_URL_SUFFIX = "/sandboxnew/secapi/pay/reverse"; |
| | | public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder"; |
| | | public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund"; |
| | | public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery"; |
| | | public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill"; |
| | | public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report"; |
| | | public static final String SANDBOX_SHORTURL_URL_SUFFIX = "/sandboxnew/tools/shorturl"; |
| | | public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid"; |
| | | |
| | | } |
| | | |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import org.apache.http.HttpEntity; |
| | | import org.apache.http.HttpResponse; |
| | | import org.apache.http.client.HttpClient; |
| | | import org.apache.http.client.config.RequestConfig; |
| | | import org.apache.http.client.methods.HttpPost; |
| | | import org.apache.http.config.RegistryBuilder; |
| | | import org.apache.http.conn.socket.ConnectionSocketFactory; |
| | | import org.apache.http.conn.socket.PlainConnectionSocketFactory; |
| | | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; |
| | | import org.apache.http.entity.StringEntity; |
| | | import org.apache.http.impl.client.HttpClientBuilder; |
| | | import org.apache.http.impl.conn.BasicHttpClientConnectionManager; |
| | | import org.apache.http.util.EntityUtils; |
| | | |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.ThreadFactory; |
| | | |
| | | /** |
| | | * 交易保障 |
| | | */ |
| | | public class WXPayReport { |
| | | |
| | | public static class ReportInfo { |
| | | |
| | | /** |
| | | * 布尔变量使用int。0为false, 1为true。 |
| | | */ |
| | | |
| | | // 基本信息 |
| | | private String version = "v1"; |
| | | private String sdk = WXPayConstants.WXPAYSDK_VERSION; |
| | | private String uuid; // 交易的标识 |
| | | private long timestamp; // 上报时的时间戳,单位秒 |
| | | private long elapsedTimeMillis; // 耗时,单位 毫秒 |
| | | |
| | | // 针对主域名 |
| | | private String firstDomain; // 第1次请求的域名 |
| | | private boolean primaryDomain; //是否主域名 |
| | | private int firstConnectTimeoutMillis; // 第1次请求设置的连接超时时间,单位 毫秒 |
| | | private int firstReadTimeoutMillis; // 第1次请求设置的读写超时时间,单位 毫秒 |
| | | private int firstHasDnsError; // 第1次请求是否出现dns问题 |
| | | private int firstHasConnectTimeout; // 第1次请求是否出现连接超时 |
| | | private int firstHasReadTimeout; // 第1次请求是否出现连接超时 |
| | | |
| | | public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) { |
| | | this.uuid = uuid; |
| | | this.timestamp = timestamp; |
| | | this.elapsedTimeMillis = elapsedTimeMillis; |
| | | this.firstDomain = firstDomain; |
| | | this.primaryDomain = primaryDomain; |
| | | this.firstConnectTimeoutMillis = firstConnectTimeoutMillis; |
| | | this.firstReadTimeoutMillis = firstReadTimeoutMillis; |
| | | this.firstHasDnsError = firstHasDnsError?1:0; |
| | | this.firstHasConnectTimeout = firstHasConnectTimeout?1:0; |
| | | this.firstHasReadTimeout = firstHasReadTimeout?1:0; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "ReportInfo{" + |
| | | "version='" + version + '\'' + |
| | | ", sdk='" + sdk + '\'' + |
| | | ", uuid='" + uuid + '\'' + |
| | | ", timestamp=" + timestamp + |
| | | ", elapsedTimeMillis=" + elapsedTimeMillis + |
| | | ", firstDomain='" + firstDomain + '\'' + |
| | | ", primaryDomain=" + primaryDomain + |
| | | ", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis + |
| | | ", firstReadTimeoutMillis=" + firstReadTimeoutMillis + |
| | | ", firstHasDnsError=" + firstHasDnsError + |
| | | ", firstHasConnectTimeout=" + firstHasConnectTimeout + |
| | | ", firstHasReadTimeout=" + firstHasReadTimeout + |
| | | '}'; |
| | | } |
| | | |
| | | /** |
| | | * 转换成 csv 格式 |
| | | * |
| | | * @return |
| | | */ |
| | | public String toLineString(String key) { |
| | | String separator = ","; |
| | | Object[] objects = new Object[] { |
| | | version, sdk, uuid, timestamp, elapsedTimeMillis, |
| | | firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis, |
| | | firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout |
| | | }; |
| | | StringBuffer sb = new StringBuffer(); |
| | | for(Object obj: objects) { |
| | | sb.append(obj).append(separator); |
| | | } |
| | | try { |
| | | String sign = WXPayUtil.HMACSHA256(sb.toString(), key); |
| | | sb.append(sign); |
| | | return sb.toString(); |
| | | } |
| | | catch (Exception ex) { |
| | | return null; |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default"; |
| | | // private static final String REPORT_URL = "http://127.0.0.1:5000/test"; |
| | | |
| | | |
| | | private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000; |
| | | private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000; |
| | | |
| | | private LinkedBlockingQueue<String> reportMsgQueue = null; |
| | | private WXPayConfig config; |
| | | private ExecutorService executorService; |
| | | |
| | | private volatile static WXPayReport INSTANCE; |
| | | |
| | | private WXPayReport(final WXPayConfig config) { |
| | | this.config = config; |
| | | reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize()); |
| | | |
| | | // 添加处理线程 |
| | | executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() { |
| | | public Thread newThread(Runnable r) { |
| | | Thread t = Executors.defaultThreadFactory().newThread(r); |
| | | t.setDaemon(true); |
| | | return t; |
| | | } |
| | | }); |
| | | |
| | | if (config.shouldAutoReport()) { |
| | | WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum()); |
| | | for (int i = 0; i < config.getReportWorkerNum(); ++i) { |
| | | executorService.execute(new Runnable() { |
| | | public void run() { |
| | | while (true) { |
| | | // 先用 take 获取数据 |
| | | try { |
| | | StringBuffer sb = new StringBuffer(); |
| | | String firstMsg = reportMsgQueue.take(); |
| | | WXPayUtil.getLogger().info("get first report msg: {}", firstMsg); |
| | | String msg = null; |
| | | sb.append(firstMsg); //会阻塞至有消息 |
| | | int remainNum = config.getReportBatchSize() - 1; |
| | | for (int j=0; j<remainNum; ++j) { |
| | | WXPayUtil.getLogger().info("try get remain report msg"); |
| | | // msg = reportMsgQueue.poll(); // 不阻塞了 |
| | | msg = reportMsgQueue.take(); |
| | | WXPayUtil.getLogger().info("get remain report msg: {}", msg); |
| | | if (msg == null) { |
| | | break; |
| | | } |
| | | else { |
| | | sb.append("\n"); |
| | | sb.append(msg); |
| | | } |
| | | } |
| | | // 上报 |
| | | WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); |
| | | } |
| | | catch (Exception ex) { |
| | | WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 单例,双重校验,请在 JDK 1.5及更高版本中使用 |
| | | * |
| | | * @param config |
| | | * @return |
| | | */ |
| | | public static WXPayReport getInstance(WXPayConfig config) { |
| | | if (INSTANCE == null) { |
| | | synchronized (WXPayReport.class) { |
| | | if (INSTANCE == null) { |
| | | INSTANCE = new WXPayReport(config); |
| | | } |
| | | } |
| | | } |
| | | return INSTANCE; |
| | | } |
| | | |
| | | public void report(String uuid, long elapsedTimeMillis, |
| | | String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, |
| | | boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) { |
| | | long currentTimestamp = WXPayUtil.getCurrentTimestamp(); |
| | | ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis, |
| | | firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis, |
| | | firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout); |
| | | String data = reportInfo.toLineString(config.getKey()); |
| | | WXPayUtil.getLogger().info("report {}", data); |
| | | if (data != null) { |
| | | reportMsgQueue.offer(data); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Deprecated |
| | | private void reportSync(final String data) throws Exception { |
| | | httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); |
| | | } |
| | | |
| | | @Deprecated |
| | | private void reportAsync(final String data) throws Exception { |
| | | new Thread(new Runnable() { |
| | | public void run() { |
| | | try { |
| | | httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); |
| | | } |
| | | catch (Exception ex) { |
| | | WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage()); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | |
| | | /** |
| | | * http 请求 |
| | | * @param data |
| | | * @param connectTimeoutMs |
| | | * @param readTimeoutMs |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception{ |
| | | BasicHttpClientConnectionManager connManager; |
| | | connManager = new BasicHttpClientConnectionManager( |
| | | RegistryBuilder.<ConnectionSocketFactory>create() |
| | | .register("http", PlainConnectionSocketFactory.getSocketFactory()) |
| | | .register("https", SSLConnectionSocketFactory.getSocketFactory()) |
| | | .build(), |
| | | null, |
| | | null, |
| | | null |
| | | ); |
| | | HttpClient httpClient = HttpClientBuilder.create() |
| | | .setConnectionManager(connManager) |
| | | .build(); |
| | | |
| | | HttpPost httpPost = new HttpPost(REPORT_URL); |
| | | |
| | | RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build(); |
| | | httpPost.setConfig(requestConfig); |
| | | |
| | | StringEntity postEntity = new StringEntity(data, "UTF-8"); |
| | | httpPost.addHeader("Content-Type", "text/xml"); |
| | | httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT); |
| | | httpPost.setEntity(postEntity); |
| | | |
| | | HttpResponse httpResponse = httpClient.execute(httpPost); |
| | | HttpEntity httpEntity = httpResponse.getEntity(); |
| | | return EntityUtils.toString(httpEntity, "UTF-8"); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import org.apache.http.HttpEntity; |
| | | import org.apache.http.HttpResponse; |
| | | import org.apache.http.client.HttpClient; |
| | | import org.apache.http.client.config.RequestConfig; |
| | | import org.apache.http.client.methods.HttpPost; |
| | | import org.apache.http.config.RegistryBuilder; |
| | | import org.apache.http.conn.ConnectTimeoutException; |
| | | import org.apache.http.conn.socket.ConnectionSocketFactory; |
| | | import org.apache.http.conn.socket.PlainConnectionSocketFactory; |
| | | import org.apache.http.conn.ssl.DefaultHostnameVerifier; |
| | | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; |
| | | import org.apache.http.entity.StringEntity; |
| | | import org.apache.http.impl.client.HttpClientBuilder; |
| | | import org.apache.http.impl.conn.BasicHttpClientConnectionManager; |
| | | import org.apache.http.util.EntityUtils; |
| | | |
| | | import javax.net.ssl.KeyManagerFactory; |
| | | import javax.net.ssl.SSLContext; |
| | | import java.io.InputStream; |
| | | import java.net.SocketTimeoutException; |
| | | import java.net.UnknownHostException; |
| | | import java.security.KeyStore; |
| | | import java.security.SecureRandom; |
| | | |
| | | //import static com.github.wxpay.sdk.WXPayConstants.USER_AGENT; |
| | | |
| | | public class WXPayRequest { |
| | | private WXPayConfig config; |
| | | public WXPayRequest(WXPayConfig config) throws Exception{ |
| | | |
| | | this.config = config; |
| | | } |
| | | |
| | | /** |
| | | * 请求,只请求一次,不做重试 |
| | | * @param domain |
| | | * @param urlSuffix |
| | | * @param uuid |
| | | * @param data |
| | | * @param connectTimeoutMs |
| | | * @param readTimeoutMs |
| | | * @param useCert 是否使用证书,针对退款、撤销等操作 |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception { |
| | | BasicHttpClientConnectionManager connManager; |
| | | if (useCert) { |
| | | // 证书 |
| | | char[] password = config.getMchID().toCharArray(); |
| | | InputStream certStream = config.getCertStream(); |
| | | KeyStore ks = KeyStore.getInstance("PKCS12"); |
| | | ks.load(certStream, password); |
| | | |
| | | // 实例化密钥库 & 初始化密钥工厂 |
| | | KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); |
| | | kmf.init(ks, password); |
| | | |
| | | // 创建 SSLContext |
| | | SSLContext sslContext = SSLContext.getInstance("TLS"); |
| | | sslContext.init(kmf.getKeyManagers(), null, new SecureRandom()); |
| | | |
| | | SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( |
| | | sslContext, |
| | | new String[]{"TLSv1"}, |
| | | null, |
| | | new DefaultHostnameVerifier()); |
| | | |
| | | connManager = new BasicHttpClientConnectionManager( |
| | | RegistryBuilder.<ConnectionSocketFactory>create() |
| | | .register("http", PlainConnectionSocketFactory.getSocketFactory()) |
| | | .register("https", sslConnectionSocketFactory) |
| | | .build(), |
| | | null, |
| | | null, |
| | | null |
| | | ); |
| | | } |
| | | else { |
| | | connManager = new BasicHttpClientConnectionManager( |
| | | RegistryBuilder.<ConnectionSocketFactory>create() |
| | | .register("http", PlainConnectionSocketFactory.getSocketFactory()) |
| | | .register("https", SSLConnectionSocketFactory.getSocketFactory()) |
| | | .build(), |
| | | null, |
| | | null, |
| | | null |
| | | ); |
| | | } |
| | | |
| | | HttpClient httpClient = HttpClientBuilder.create() |
| | | .setConnectionManager(connManager) |
| | | .build(); |
| | | |
| | | String url = "https://" + domain + urlSuffix; |
| | | HttpPost httpPost = new HttpPost(url); |
| | | |
| | | RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build(); |
| | | httpPost.setConfig(requestConfig); |
| | | |
| | | StringEntity postEntity = new StringEntity(data, "UTF-8"); |
| | | httpPost.addHeader("Content-Type", "text/xml"); |
| | | httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + config.getMchID()); |
| | | httpPost.setEntity(postEntity); |
| | | |
| | | HttpResponse httpResponse = httpClient.execute(httpPost); |
| | | HttpEntity httpEntity = httpResponse.getEntity(); |
| | | return EntityUtils.toString(httpEntity, "UTF-8"); |
| | | |
| | | } |
| | | |
| | | |
| | | private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception { |
| | | Exception exception = null; |
| | | long elapsedTimeMillis = 0; |
| | | long startTimestampMs = WXPayUtil.getCurrentTimestampMs(); |
| | | boolean firstHasDnsErr = false; |
| | | boolean firstHasConnectTimeout = false; |
| | | boolean firstHasReadTimeout = false; |
| | | IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config); |
| | | if(domainInfo == null){ |
| | | throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null"); |
| | | } |
| | | try { |
| | | String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert); |
| | | elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs; |
| | | config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null); |
| | | WXPayReport.getInstance(config).report( |
| | | uuid, |
| | | elapsedTimeMillis, |
| | | domainInfo.domain, |
| | | domainInfo.primaryDomain, |
| | | connectTimeoutMs, |
| | | readTimeoutMs, |
| | | firstHasDnsErr, |
| | | firstHasConnectTimeout, |
| | | firstHasReadTimeout); |
| | | return result; |
| | | } |
| | | catch (UnknownHostException ex) { // dns 解析错误,或域名不存在 |
| | | exception = ex; |
| | | firstHasDnsErr = true; |
| | | elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs; |
| | | WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo); |
| | | WXPayReport.getInstance(config).report( |
| | | uuid, |
| | | elapsedTimeMillis, |
| | | domainInfo.domain, |
| | | domainInfo.primaryDomain, |
| | | connectTimeoutMs, |
| | | readTimeoutMs, |
| | | firstHasDnsErr, |
| | | firstHasConnectTimeout, |
| | | firstHasReadTimeout |
| | | ); |
| | | } |
| | | catch (ConnectTimeoutException ex) { |
| | | exception = ex; |
| | | firstHasConnectTimeout = true; |
| | | elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs; |
| | | WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo); |
| | | WXPayReport.getInstance(config).report( |
| | | uuid, |
| | | elapsedTimeMillis, |
| | | domainInfo.domain, |
| | | domainInfo.primaryDomain, |
| | | connectTimeoutMs, |
| | | readTimeoutMs, |
| | | firstHasDnsErr, |
| | | firstHasConnectTimeout, |
| | | firstHasReadTimeout |
| | | ); |
| | | } |
| | | catch (SocketTimeoutException ex) { |
| | | exception = ex; |
| | | firstHasReadTimeout = true; |
| | | elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs; |
| | | WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo); |
| | | WXPayReport.getInstance(config).report( |
| | | uuid, |
| | | elapsedTimeMillis, |
| | | domainInfo.domain, |
| | | domainInfo.primaryDomain, |
| | | connectTimeoutMs, |
| | | readTimeoutMs, |
| | | firstHasDnsErr, |
| | | firstHasConnectTimeout, |
| | | firstHasReadTimeout); |
| | | } |
| | | catch (Exception ex) { |
| | | exception = ex; |
| | | elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs; |
| | | WXPayReport.getInstance(config).report( |
| | | uuid, |
| | | elapsedTimeMillis, |
| | | domainInfo.domain, |
| | | domainInfo.primaryDomain, |
| | | connectTimeoutMs, |
| | | readTimeoutMs, |
| | | firstHasDnsErr, |
| | | firstHasConnectTimeout, |
| | | firstHasReadTimeout); |
| | | } |
| | | config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception); |
| | | throw exception; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 可重试的,非双向认证的请求 |
| | | * @param urlSuffix |
| | | * @param uuid |
| | | * @param data |
| | | * @return |
| | | */ |
| | | public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception { |
| | | return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport); |
| | | } |
| | | |
| | | /** |
| | | * 可重试的,非双向认证的请求 |
| | | * @param urlSuffix |
| | | * @param uuid |
| | | * @param data |
| | | * @param connectTimeoutMs |
| | | * @param readTimeoutMs |
| | | * @return |
| | | */ |
| | | public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception { |
| | | return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport); |
| | | } |
| | | |
| | | /** |
| | | * 可重试的,双向认证的请求 |
| | | * @param urlSuffix |
| | | * @param uuid |
| | | * @param data |
| | | * @return |
| | | */ |
| | | public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception { |
| | | return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport); |
| | | } |
| | | |
| | | /** |
| | | * 可重试的,双向认证的请求 |
| | | * @param urlSuffix |
| | | * @param uuid |
| | | * @param data |
| | | * @param connectTimeoutMs |
| | | * @param readTimeoutMs |
| | | * @return |
| | | */ |
| | | public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception { |
| | | return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport); |
| | | } |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | //import com.github.wxpay.sdk.WXPayConstants.SignType; |
| | | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.w3c.dom.Node; |
| | | import org.w3c.dom.NodeList; |
| | | |
| | | import javax.crypto.Mac; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import javax.xml.parsers.DocumentBuilder; |
| | | import javax.xml.transform.OutputKeys; |
| | | import javax.xml.transform.Transformer; |
| | | import javax.xml.transform.TransformerFactory; |
| | | import javax.xml.transform.dom.DOMSource; |
| | | import javax.xml.transform.stream.StreamResult; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.InputStream; |
| | | import java.io.StringWriter; |
| | | import java.security.MessageDigest; |
| | | import java.security.SecureRandom; |
| | | import java.util.*; |
| | | |
| | | |
| | | public class WXPayUtil { |
| | | |
| | | private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| | | |
| | | private static final Random RANDOM = new SecureRandom(); |
| | | |
| | | /** |
| | | * XML格式字符串转换为Map |
| | | * |
| | | * @param strXML XML字符串 |
| | | * @return XML数据转换后的Map |
| | | * @throws Exception |
| | | */ |
| | | public static Map<String, String> xmlToMap(String strXML) throws Exception { |
| | | try { |
| | | Map<String, String> data = new HashMap<String, String>(); |
| | | DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder(); |
| | | InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); |
| | | org.w3c.dom.Document doc = documentBuilder.parse(stream); |
| | | doc.getDocumentElement().normalize(); |
| | | NodeList nodeList = doc.getDocumentElement().getChildNodes(); |
| | | for (int idx = 0; idx < nodeList.getLength(); ++idx) { |
| | | Node node = nodeList.item(idx); |
| | | if (node.getNodeType() == Node.ELEMENT_NODE) { |
| | | org.w3c.dom.Element element = (org.w3c.dom.Element) node; |
| | | data.put(element.getNodeName(), element.getTextContent()); |
| | | } |
| | | } |
| | | try { |
| | | stream.close(); |
| | | } catch (Exception ex) { |
| | | // do nothing |
| | | } |
| | | return data; |
| | | } catch (Exception ex) { |
| | | WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML); |
| | | throw ex; |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 将Map转换为XML格式的字符串 |
| | | * |
| | | * @param data Map类型数据 |
| | | * @return XML格式的字符串 |
| | | * @throws Exception |
| | | */ |
| | | public static String mapToXml(Map<String, String> data) throws Exception { |
| | | org.w3c.dom.Document document = WXPayXmlUtil.newDocument(); |
| | | org.w3c.dom.Element root = document.createElement("xml"); |
| | | document.appendChild(root); |
| | | for (String key: data.keySet()) { |
| | | String value = data.get(key); |
| | | if (value == null) { |
| | | value = ""; |
| | | } |
| | | value = value.trim(); |
| | | org.w3c.dom.Element filed = document.createElement(key); |
| | | filed.appendChild(document.createTextNode(value)); |
| | | root.appendChild(filed); |
| | | } |
| | | TransformerFactory tf = TransformerFactory.newInstance(); |
| | | Transformer transformer = tf.newTransformer(); |
| | | DOMSource source = new DOMSource(document); |
| | | transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); |
| | | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); |
| | | StringWriter writer = new StringWriter(); |
| | | StreamResult result = new StreamResult(writer); |
| | | transformer.transform(source, result); |
| | | String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); |
| | | try { |
| | | writer.close(); |
| | | } |
| | | catch (Exception ex) { |
| | | } |
| | | return output; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 生成带有 sign 的 XML 格式字符串 |
| | | * |
| | | * @param data Map类型数据 |
| | | * @param key API密钥 |
| | | * @return 含有sign字段的XML |
| | | */ |
| | | public static String generateSignedXml(final Map<String, String> data, String key) throws Exception { |
| | | return generateSignedXml(data, key, WXPayConstants.SignType.MD5); |
| | | } |
| | | |
| | | /** |
| | | * 生成带有 sign 的 XML 格式字符串 |
| | | * |
| | | * @param data Map类型数据 |
| | | * @param key API密钥 |
| | | * @param signType 签名类型 |
| | | * @return 含有sign字段的XML |
| | | */ |
| | | public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { |
| | | String sign = generateSignature(data, key, signType); |
| | | data.put(WXPayConstants.FIELD_SIGN, sign); |
| | | return mapToXml(data); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 判断签名是否正确 |
| | | * |
| | | * @param xmlStr XML格式数据 |
| | | * @param key API密钥 |
| | | * @return 签名是否正确 |
| | | * @throws Exception |
| | | */ |
| | | public static boolean isSignatureValid(String xmlStr, String key) throws Exception { |
| | | Map<String, String> data = xmlToMap(xmlStr); |
| | | if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { |
| | | return false; |
| | | } |
| | | String sign = data.get(WXPayConstants.FIELD_SIGN); |
| | | return generateSignature(data, key).equals(sign); |
| | | } |
| | | |
| | | /** |
| | | * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。 |
| | | * |
| | | * @param data Map类型数据 |
| | | * @param key API密钥 |
| | | * @return 签名是否正确 |
| | | * @throws Exception |
| | | */ |
| | | public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception { |
| | | return isSignatureValid(data, key, WXPayConstants.SignType.MD5); |
| | | } |
| | | |
| | | /** |
| | | * 判断签名是否正确,必须包含sign字段,否则返回false。 |
| | | * |
| | | * @param data Map类型数据 |
| | | * @param key API密钥 |
| | | * @param signType 签名方式 |
| | | * @return 签名是否正确 |
| | | * @throws Exception |
| | | */ |
| | | public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { |
| | | if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { |
| | | return false; |
| | | } |
| | | String sign = data.get(WXPayConstants.FIELD_SIGN); |
| | | return generateSignature(data, key, signType).equals(sign); |
| | | } |
| | | |
| | | /** |
| | | * 生成签名 |
| | | * |
| | | * @param data 待签名数据 |
| | | * @param key API密钥 |
| | | * @return 签名 |
| | | */ |
| | | public static String generateSignature(final Map<String, String> data, String key) throws Exception { |
| | | return generateSignature(data, key, WXPayConstants.SignType.MD5); |
| | | } |
| | | |
| | | /** |
| | | * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。 |
| | | * |
| | | * @param data 待签名数据 |
| | | * @param key API密钥 |
| | | * @param signType 签名方式 |
| | | * @return 签名 |
| | | */ |
| | | public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { |
| | | Set<String> keySet = data.keySet(); |
| | | String[] keyArray = keySet.toArray(new String[keySet.size()]); |
| | | Arrays.sort(keyArray); |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String k : keyArray) { |
| | | if (k.equals(WXPayConstants.FIELD_SIGN)) { |
| | | continue; |
| | | } |
| | | if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 |
| | | sb.append(k).append("=").append(data.get(k).trim()).append("&"); |
| | | } |
| | | sb.append("key=").append(key); |
| | | if (WXPayConstants.SignType.MD5.equals(signType)) { |
| | | return MD5(sb.toString()).toUpperCase(); |
| | | } |
| | | else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) { |
| | | return HMACSHA256(sb.toString(), key); |
| | | } |
| | | else { |
| | | throw new Exception(String.format("Invalid sign_type: %s", signType)); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取随机字符串 Nonce Str |
| | | * |
| | | * @return String 随机字符串 |
| | | */ |
| | | public static String generateNonceStr() { |
| | | char[] nonceChars = new char[32]; |
| | | for (int index = 0; index < nonceChars.length; ++index) { |
| | | nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); |
| | | } |
| | | return new String(nonceChars); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 生成 MD5 |
| | | * |
| | | * @param data 待处理数据 |
| | | * @return MD5结果 |
| | | */ |
| | | public static String MD5(String data) throws Exception { |
| | | MessageDigest md = MessageDigest.getInstance("MD5"); |
| | | byte[] array = md.digest(data.getBytes("UTF-8")); |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (byte item : array) { |
| | | sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); |
| | | } |
| | | return sb.toString().toUpperCase(); |
| | | } |
| | | |
| | | /** |
| | | * 生成 HMACSHA256 |
| | | * @param data 待处理数据 |
| | | * @param key 密钥 |
| | | * @return 加密结果 |
| | | * @throws Exception |
| | | */ |
| | | public static String HMACSHA256(String data, String key) throws Exception { |
| | | Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); |
| | | SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); |
| | | sha256_HMAC.init(secret_key); |
| | | byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (byte item : array) { |
| | | sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); |
| | | } |
| | | return sb.toString().toUpperCase(); |
| | | } |
| | | |
| | | /** |
| | | * 日志 |
| | | * @return |
| | | */ |
| | | public static Logger getLogger() { |
| | | Logger logger = LoggerFactory.getLogger("wxpay java sdk"); |
| | | return logger; |
| | | } |
| | | |
| | | /** |
| | | * 获取当前时间戳,单位秒 |
| | | * @return |
| | | */ |
| | | public static long getCurrentTimestamp() { |
| | | return System.currentTimeMillis()/1000; |
| | | } |
| | | |
| | | /** |
| | | * 获取当前时间戳,单位毫秒 |
| | | * @return |
| | | */ |
| | | public static long getCurrentTimestampMs() { |
| | | return System.currentTimeMillis(); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import org.w3c.dom.Document; |
| | | |
| | | import javax.xml.XMLConstants; |
| | | import javax.xml.parsers.DocumentBuilder; |
| | | import javax.xml.parsers.DocumentBuilderFactory; |
| | | import javax.xml.parsers.ParserConfigurationException; |
| | | |
| | | /** |
| | | * 2018/7/3 |
| | | */ |
| | | public final class WXPayXmlUtil { |
| | | public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { |
| | | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); |
| | | documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); |
| | | documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); |
| | | documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); |
| | | documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); |
| | | documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); |
| | | documentBuilderFactory.setXIncludeAware(false); |
| | | documentBuilderFactory.setExpandEntityReferences(false); |
| | | |
| | | return documentBuilderFactory.newDocumentBuilder(); |
| | | } |
| | | |
| | | public static Document newDocument() throws ParserConfigurationException { |
| | | return newDocumentBuilder().newDocument(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.config.weixin; |
| | | |
| | | import lombok.Data; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | @Component |
| | | @ConfigurationProperties("pay.wxpay") |
| | | @Data |
| | | public class WeiXinOfficPayProperties { |
| | | /** |
| | | * appID |
| | | */ |
| | | private String appID; |
| | | |
| | | /** |
| | | * 商户号 |
| | | */ |
| | | private String mchID; |
| | | |
| | | /** |
| | | * API 密钥 |
| | | */ |
| | | private String key; |
| | | |
| | | /** |
| | | * API证书绝对路径 (本项目放在了 resources/cert/wxpay/apiclient_cert.p12") |
| | | */ |
| | | private String certPath; |
| | | |
| | | /** |
| | | * HTTP(S) 连接超时时间,单位毫秒 |
| | | */ |
| | | private int httpConnectTimeoutMs = 8000; |
| | | |
| | | /** |
| | | * HTTP(S) 读数据超时时间,单位毫秒 |
| | | */ |
| | | private int httpReadTimeoutMs = 10000; |
| | | |
| | | /** |
| | | * 微信支付异步通知地址 |
| | | */ |
| | | private String payNotifyUrl; |
| | | |
| | | /** |
| | | * 微信退款异步通知地址 |
| | | */ |
| | | private String refundNotifyUrl; |
| | | |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.controller; |
| | | |
| | | import com.kidgrow.common.controller.BaseController; |
| | | import com.kidgrow.common.model.PageResult; |
| | | import com.kidgrow.common.model.ResultBody; |
| | | import com.kidgrow.oprationcenter.model.SaasClientPay; |
| | | import com.kidgrow.oprationcenter.service.ISaasClientPayService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.validation.BindingResult; |
| | | import org.springframework.validation.ObjectError; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.validation.Valid; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | |
| | | /** |
| | | * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br> |
| | | * @Description: |
| | | * @Project: 用户中心 |
| | | * @CreateDate: Created in 2020-09-17 16:21:03 <br> |
| | | * @Author: <a href="4345453@kidgrow.com">liuke</a> |
| | | * @version: 1.0 |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/saasclientpay") |
| | | @Api(tags = "") |
| | | public class SaasClientPayController extends BaseController { |
| | | @Autowired |
| | | private ISaasClientPayService saasClientPayService; |
| | | |
| | | /** |
| | | * 列表 |
| | | */ |
| | | @ApiOperation(value = "查询列表") |
| | | @ApiImplicitParams({ |
| | | @ApiImplicitParam(name = "page", value = "分页起始位置", required = true, dataType = "Integer"), |
| | | @ApiImplicitParam(name = "limit", value = "分页结束位置", required = true, dataType = "Integer") |
| | | }) |
| | | @GetMapping |
| | | public ResultBody<PageResult> list(@RequestParam Map<String, Object> params) { |
| | | if(params.size()==0){ |
| | | params.put("page",1); |
| | | params.put("limit",10); |
| | | } |
| | | return ResultBody.ok().data(saasClientPayService.findList(params)); |
| | | } |
| | | |
| | | /** |
| | | * 查询 |
| | | */ |
| | | @ApiOperation(value = "查询") |
| | | @GetMapping("/{id}") |
| | | public ResultBody findById(@PathVariable Long id) { |
| | | SaasClientPay model = saasClientPayService.getById(id); |
| | | return ResultBody.ok().data(model).msg("查询成功"); |
| | | } |
| | | |
| | | /** |
| | | * 根据SaasClientPay当做查询条件进行查询 |
| | | */ |
| | | @ApiOperation(value = "根据SaasClientPay当做查询条件进行查询") |
| | | @PostMapping("/query") |
| | | public ResultBody findByObject(@RequestBody SaasClientPay saasClientPay) { |
| | | SaasClientPay model = saasClientPayService.findByObject(saasClientPay); |
| | | return ResultBody.ok().data(model).msg("查询成功"); |
| | | } |
| | | |
| | | /** |
| | | * 新增or更新 |
| | | */ |
| | | @ApiOperation(value = "保存") |
| | | @PostMapping |
| | | public ResultBody save(@Valid @RequestBody SaasClientPay saasClientPay, BindingResult bindingResult) { |
| | | List<String> errMsg= new ArrayList<>(); |
| | | if (bindingResult.hasErrors()) { |
| | | for (ObjectError error : bindingResult.getAllErrors()) { |
| | | errMsg.add(error.getDefaultMessage()); |
| | | } |
| | | return ResultBody.failed().msg(errMsg.toString()); |
| | | } else { |
| | | boolean v= saasClientPayService.saveOrUpdate(saasClientPay); |
| | | if(v) { |
| | | return ResultBody.ok().data(saasClientPay).msg("保存成功"); |
| | | } |
| | | else { |
| | | return ResultBody.failed().msg("保存失败"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 删除 |
| | | */ |
| | | @ApiOperation(value = "删除") |
| | | @DeleteMapping("/{id}") |
| | | public ResultBody delete(@PathVariable Long id) { |
| | | boolean v= saasClientPayService.removeById(id); |
| | | if(v) { |
| | | return ResultBody.ok().msg("删除成功"); |
| | | } |
| | | else { |
| | | return ResultBody.failed().msg("删除失败"); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.controller.alipay; |
| | | |
| | | import com.alipay.api.AlipayClient; |
| | | import com.alipay.api.domain.AlipayTradeCancelModel; |
| | | import com.alipay.api.domain.AlipayTradePrecreateModel; |
| | | import com.alipay.api.internal.util.AlipaySignature; |
| | | import com.alipay.api.request.AlipayTradeCancelRequest; |
| | | import com.alipay.api.request.AlipayTradePrecreateRequest; |
| | | import com.alipay.api.response.AlipayTradeCancelResponse; |
| | | import com.alipay.api.response.AlipayTradePrecreateResponse; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.kidgrow.common.model.ResultBody; |
| | | import com.kidgrow.common.utils.DateUtils; |
| | | import com.kidgrow.common.utils.QRCodeUtil; |
| | | import com.kidgrow.common.utils.StringUtils; |
| | | import com.kidgrow.oprationcenter.config.alipay.AlipayProperties; |
| | | import com.kidgrow.oprationcenter.model.SaasClientPay; |
| | | import com.kidgrow.oprationcenter.service.ISaasClientPayService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.time.Instant; |
| | | import java.util.*; |
| | | |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("alipay") |
| | | @Api(tags = "支付宝的支付controller") |
| | | public class AlipayController { |
| | | |
| | | @Resource |
| | | private AlipayClient alipayClient; |
| | | @Resource |
| | | private AlipayProperties alipayProperties; |
| | | @Autowired |
| | | private ISaasClientPayService saasClientPayService; |
| | | |
| | | @ApiOperation(value = "调用预支付的接口,生成二维码") |
| | | @PostMapping("/precreate") |
| | | public ResultBody precreate(HttpServletResponse response) throws Exception{ //商户预创建支付订单,生成二维码 |
| | | |
| | | String outTradeNo = UUID.randomUUID().toString().replaceAll("_", ""); |
| | | QueryWrapper queryWrapper = new QueryWrapper(); |
| | | queryWrapper.eq("dia_id","1"); |
| | | queryWrapper.eq("pay_status",0); |
| | | AlipayTradePrecreateModel model=new AlipayTradePrecreateModel(); |
| | | model.setProductCode("FACE_TO_FACE_PAYMENT"); //销售产品码 |
| | | List<SaasClientPay> list = saasClientPayService.list(queryWrapper); |
| | | if (!list.isEmpty()) { |
| | | SaasClientPay saasClientPay = list.get(0); |
| | | model.setOutTradeNo(outTradeNo); //商户订单号 |
| | | model.setSubject("海贼王"); //订单标题 |
| | | model.setTotalAmount(Double.valueOf(saasClientPay.getPayPrice())/100+""); //订单总金额 |
| | | }else { |
| | | model.setOutTradeNo(outTradeNo); //商户订单号 |
| | | model.setSubject("海贼王"); //订单标题 |
| | | model.setTotalAmount("0.01"); //订单总金额 |
| | | SaasClientPay saasClientPay = this.fengData(model); |
| | | boolean b = saasClientPayService.saveOrUpdate(saasClientPay); |
| | | } |
| | | AlipayTradePrecreateRequest request=new AlipayTradePrecreateRequest(); |
| | | request.setBizModel(model); |
| | | //兼容pkcs1 编码; |
| | | java.security.Security.addProvider( |
| | | new org.bouncycastle.jce.provider.BouncyCastleProvider() |
| | | ); |
| | | AlipayTradePrecreateResponse alipayTradePrecreateResponse=alipayClient.execute(request); |
| | | if (StringUtils.isBlank(alipayTradePrecreateResponse.getQrCode())) { |
| | | return ResultBody.failed().data("生成二维码失败"); |
| | | } |
| | | String content=alipayTradePrecreateResponse.getQrCode(); |
| | | String base64 = QRCodeUtil.creatRrCode(content, 200, 200, 0); |
| | | System.out.println(base64); |
| | | return ResultBody.ok().data(base64); |
| | | } |
| | | //数据封装 |
| | | public SaasClientPay fengData(AlipayTradePrecreateModel model){ |
| | | SaasClientPay saasClientPay = new SaasClientPay(); |
| | | saasClientPay.setCreateHospitalDepartment(""); |
| | | saasClientPay.setCreateHospitalDepartid(""); |
| | | saasClientPay.setCreateHospitalId(Long.valueOf(1L)); |
| | | saasClientPay.setPayPrice(1); |
| | | saasClientPay.setOutTradeNo(model.getOutTradeNo()); |
| | | saasClientPay.setPayStatus(0); |
| | | saasClientPay.setPayMethod(1); |
| | | saasClientPay.setDiaId("1"); |
| | | saasClientPay.setId( UUID.randomUUID().toString().replaceAll("_", "")); |
| | | saasClientPay.setCreateTime(new Date()); |
| | | saasClientPay.setCreateUserId(Long.valueOf(1L)); |
| | | saasClientPay.setCreateUserName(""); |
| | | return saasClientPay; |
| | | } |
| | | @ApiOperation(value = "取消订单,支付超时、支付结果未知是可撤销,超过24小时不可撤销-------暂时没有配置") |
| | | @PostMapping("/cancel") |
| | | public ResultBody cancel() throws Exception{ //取消订单,支付超时、支付结果未知是可撤销,超过24小时不可撤销 |
| | | AlipayTradeCancelModel model=new AlipayTradeCancelModel(); |
| | | model.setOutTradeNo("300"); |
| | | |
| | | AlipayTradeCancelRequest request=new AlipayTradeCancelRequest(); |
| | | request.setBizModel(model); |
| | | |
| | | AlipayTradeCancelResponse response=alipayClient.execute(request); |
| | | return ResultBody.ok().data(response.getBody()); |
| | | } |
| | | |
| | | @ApiOperation(value = "trade_success状态下异步通知接口,端口异常的接口") |
| | | @PostMapping("/notify") |
| | | public ResultBody notify(HttpServletRequest request) throws Exception{ //trade_success状态下异步通知接口 |
| | | if (check(request.getParameterMap())){ |
| | | System.out.println(request.getParameter("trade_status")); |
| | | Map<String, String> requestMap = this.getRequestMap(request.getParameterMap()); |
| | | log.error("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); |
| | | log.error(request.toString()); |
| | | log.error("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); |
| | | System.out.println("异步通知 "+ Instant.now()); |
| | | }else { |
| | | System.out.println("验签失败"); |
| | | } |
| | | return ResultBody.ok().data("订单失败"); |
| | | } |
| | | @ApiOperation(value = "订单支付成功后同步返回地址") |
| | | @PostMapping("/return") |
| | | public ResultBody returnUrl(HttpServletRequest request, Map<String,Object> map) throws Exception{ //订单支付成功后同步返回地址 |
| | | if (check(request.getParameterMap())){ |
| | | Map<String, String> requestMap = this.getRequestMap(request.getParameterMap()); |
| | | log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"); |
| | | log.error(request.toString()); |
| | | log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"); |
| | | log.error(map.toString()); |
| | | log.error("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); |
| | | String trade_status = requestMap.get("trade_status"); |
| | | if(StringUtils.isNotBlank(trade_status)){ |
| | | //支付成功 |
| | | if(trade_status.equals("TRADE_SUCCESS")){ |
| | | //更新状态 out_trade_no |
| | | QueryWrapper queryWrapper = new QueryWrapper(); |
| | | queryWrapper.eq("out_trade_no",requestMap.get("out_trade_no")); |
| | | queryWrapper.eq("pay_status",0); |
| | | List<SaasClientPay> list = saasClientPayService.list(queryWrapper); |
| | | if(!list.isEmpty()){ |
| | | SaasClientPay saasClientPay = list.get(0); |
| | | saasClientPay.setPayTime(DateUtils.parseDate(requestMap.get("gmt_payment"))); |
| | | saasClientPay.setPayStatus(2); |
| | | saasClientPay.setTradeNo(requestMap.get("trade_no")); |
| | | boolean b = saasClientPayService.saveOrUpdate(saasClientPay); |
| | | } |
| | | } |
| | | } |
| | | return ResultBody.ok().data("success"); |
| | | }else { |
| | | return ResultBody.failed().data("false"); |
| | | } |
| | | } |
| | | private Map<String,String> getRequestMap(Map<String,String[]> requestParams){ |
| | | Map<String,String> params = new HashMap<>(); |
| | | for (String name : requestParams.keySet()) { |
| | | String[] values = requestParams.get(name); |
| | | String valueStr = ""; |
| | | for (int i = 0; i < values.length; i++) { |
| | | valueStr = (i == values.length - 1) ? valueStr + values[i] |
| | | : valueStr + values[i] + ","; |
| | | } |
| | | params.put(name, valueStr); |
| | | System.out.println(name+" ==> "+valueStr); |
| | | } |
| | | return params; |
| | | } |
| | | private boolean check(Map<String,String[]> requestParams) throws Exception{ //对return、notify参数进行验签 |
| | | Map<String, String> requestMap = this.getRequestMap(requestParams); |
| | | return AlipaySignature.rsaCheckV1(requestMap, alipayProperties.getAlipayPublicKey(), |
| | | alipayProperties.getCharset(), alipayProperties.getSignType()); //调用SDK验证签名 |
| | | } |
| | | } |
New file |
| | |
| | | package com.kidgrow.oprationcenter.controller.weixin; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.kidgrow.common.model.ResultBody; |
| | | import com.kidgrow.common.utils.DateUtils; |
| | | import com.kidgrow.common.utils.QRCodeUtil; |
| | | import com.kidgrow.common.utils.StringUtils; |
| | | import com.kidgrow.oprationcenter.config.weixin.MyConfig; |
| | | import com.kidgrow.oprationcenter.config.weixin.WXPay; |
| | | import com.kidgrow.oprationcenter.config.weixin.WXPayUtil; |
| | | import com.kidgrow.oprationcenter.config.weixin.WeiXinOfficPayProperties; |
| | | import com.kidgrow.oprationcenter.model.SaasClientPay; |
| | | import com.kidgrow.oprationcenter.service.ISaasClientPayService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.collections.MapUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.util.*; |
| | | |
| | | |
| | | @Slf4j |
| | | @RestController |
| | | @RequestMapping("/wxController") |
| | | @Api(tags = "复诊相关操作") |
| | | public class WxController { |
| | | /** |
| | | * @param map |
| | | * @return |
| | | */ |
| | | @Autowired |
| | | ISaasClientPayService iSaasClientPayService; |
| | | @Resource |
| | | WeiXinOfficPayProperties weiXinOfficPayProperties; |
| | | |
| | | @ApiOperation(value = "保存") |
| | | @PostMapping("pay") |
| | | public ResultBody save(@RequestBody Map<String,Object> map) throws Exception { |
| | | Long hospitalId = MapUtils.getLong(map, "hospitalId"); |
| | | Long doctorId = MapUtils.getLong(map, "doctorId"); |
| | | Long diaId = MapUtils.getLong(map, "diaId"); |
| | | if (hospitalId!=null) { |
| | | return ResultBody.failed().data("请输入医院id"); |
| | | } |
| | | if (doctorId!=null) { |
| | | return ResultBody.failed().data("请输入医生id"); |
| | | } |
| | | if (diaId!=null) { |
| | | return ResultBody.failed().data("请输入诊断记录ID"); |
| | | } |
| | | Map<String,String> jsonObject=new HashMap<>(); |
| | | Map<String,Object> mapto=new HashMap<>(); |
| | | jsonObject.put("body","腾讯");//商品描述 |
| | | jsonObject.put("out_trade_no","20150806123434");//商户订单号 |
| | | jsonObject.put("total_fee","1");//标价金额 |
| | | jsonObject.put("spbill_create_ip","192.168.2.240");//终端IP |
| | | // jsonObject.put("notify_url",myConfig.getPayNotifyUrl());//通知地址 |
| | | jsonObject.put("notify_url","http://open.zuul.kidgrow.cloud/api-record/wxController/callback");//通知地址 |
| | | jsonObject.put("trade_type","NATIVE");//交易类型 |
| | | MyConfig wxPayConfig= new MyConfig(weiXinOfficPayProperties); |
| | | |
| | | WXPay wxPay=new WXPay(wxPayConfig); |
| | | Map<String, String> stringStringMap = wxPay.fillRequestData(jsonObject); |
| | | Map<String, String> result = wxPay.unifiedOrder(stringStringMap); |
| | | String resultStr = result.get("code_url"); |
| | | if(result.get("code_url")==null|| StringUtils.isBlank(resultStr)){ |
| | | return ResultBody.failed().data("生成二维码失败"); |
| | | } |
| | | SaasClientPay saasClientPay=new SaasClientPay(); |
| | | saasClientPay.setId(UUID.randomUUID().toString().replaceAll("_", "")); |
| | | saasClientPay.setCreateHospitalDepartment(""); |
| | | saasClientPay.setCreateHospitalDepartid(""); |
| | | saasClientPay.setCreateHospitalId(Long.valueOf(1L)); |
| | | saasClientPay.setPayPrice(1); |
| | | saasClientPay.setCreateHospitalName(""); |
| | | saasClientPay.setOutTradeNo(jsonObject.get("out_trade_no")); |
| | | saasClientPay.setPayTime(new Date()); |
| | | saasClientPay.setPayStatus(0); |
| | | saasClientPay.setPayMethod(0); |
| | | saasClientPay.setDiaId("1"); |
| | | boolean save = iSaasClientPayService.saveOrUpdate(saasClientPay); |
| | | String s = QRCodeUtil.creatRrCode(resultStr, 200, 200, 0); |
| | | return ResultBody.ok().data(s); |
| | | } |
| | | @RequestMapping("/callback") |
| | | public void OrderCallBack(HttpServletRequest request, HttpServletResponse response,Map<String,Object> map) throws Exception { |
| | | Map<String, String> requestMap = this.getRequestMap(request.getParameterMap()); |
| | | log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"); |
| | | log.error(requestMap.toString()); |
| | | log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"); |
| | | log.error(map.toString()); |
| | | log.error("eeeeeeeeeeeeeeeeeeeeee"); |
| | | if(WXPayUtil.isSignatureValid(requestMap, "GSFcX6WdgRTAS6154EW14WE3SGBSER49")){ |
| | | //支付成功 |
| | | if(requestMap.get("result_code").equals("SUCCESS")){ |
| | | //更新状态 out_trade_no |
| | | QueryWrapper queryWrapper = new QueryWrapper(); |
| | | queryWrapper.eq("out_trade_no",requestMap.get("out_trade_no")); |
| | | queryWrapper.eq("pay_status",0); |
| | | List<SaasClientPay> list = iSaasClientPayService.list(queryWrapper); |
| | | if(!list.isEmpty()){ |
| | | SaasClientPay saasClientPay = list.get(0); |
| | | saasClientPay.setPayTime(DateUtils.parseDate(requestMap.get("time_end"))); |
| | | saasClientPay.setPayStatus(2); |
| | | saasClientPay.setTradeNo(requestMap.get("transaction_id")); |
| | | boolean b = iSaasClientPayService.saveOrUpdate(saasClientPay); |
| | | } |
| | | } |
| | | }else { |
| | | System.out.println("验签失败"); |
| | | } |
| | | } |
| | | private Map<String,String> getRequestMap(Map<String,String[]> requestParams){ |
| | | Map<String,String> params = new HashMap<>(); |
| | | for (String name : requestParams.keySet()) { |
| | | String[] values = requestParams.get(name); |
| | | String valueStr = ""; |
| | | for (int i = 0; i < values.length; i++) { |
| | | valueStr = (i == values.length - 1) ? valueStr + values[i] |
| | | : valueStr + values[i] + ","; |
| | | } |
| | | params.put(name, valueStr); |
| | | System.out.println(name+" ==> "+valueStr); |
| | | } |
| | | return params; |
| | | } |
| | | public static void main(String[] args) throws Exception { |
| | | WxController weixinController=new WxController(); |
| | | Map<String,Object> map=null; |
| | | ResultBody save = weixinController.save(map); |
| | | String data = (String)save.getData(); |
| | | } |
| | | |
| | | } |