本文主要讲解服务商下特约商户的小程序支付。(扫码支付和h5支付一般流程相同,我们知道小程序支付可以快速访问其他支付类型)
注:本文中的支付是指服务商模式。
服务商:具有支付开发能力的第三方提供商,普通商户3360具有开发能力的商户,服务商下的特约商户3360商户,一个商户实体可以申请不同服务商下的特约商户,每个服务商会在这个服务商下给商户实体下一个特约商户号。
普通商户申请费用300元左右,服务商申请特约商户不需要任何费用。
商家实体可以申请普通商家和特殊商家。申请同一商户实体的普通商户独立于服务提供商下申请的特殊商户号。
服务商旗下特约商户的资金流不会直接通过服务商的支付账户,但最终消费者的资金会直接与服务商旗下特约商户沟通,但服务商可以自行查看特约商户的资金流。
服务提供商小程序开发文档
申请注册商,然后登录微信商户平台,进入菜单3360服务商功能-特约商户管理-新商户(即应用服务商下的特约商户)的申请,如果没有问题,三至五天就会通过,然后就可以看到特约商户管理下的服务商的特约商户。我们在开发中需要服务提供商的商户号和这里的商户号。
微信给出的官方业务流程图:
能清楚地了解业务流程的方向。
统一订单请求参数被封装为我们可以处理的对象:
我在这里的名字是:微信统一订购请求。
以下是我在开发中遇到的一些漏洞,主要是微信官方文档给出的参数比较模糊,尤其是小程序支付。
我们需要按照字典顺序对对象进行排序。
第一步是将所有发送或接收的数据设置为集合M,按照参数名的ASCII码(字典顺序)从小到大对集合M中非空参数值的参数进行排序,并使用URL键值对的格式(即key 1=value 1ke 2=value 2…)拼接成字符串stringA。字典中排序后的字符连接到密钥(需要在微信商家平台上配置。建议使用UUID生成32位数字)。获取MD5加密签名,将填有符号的微信请求对象微信请求转换为微信所需的xml类型。发起请求,获得微信统一订购的响应(xml字符格式)。将其解析为一个对象(处理返回的响应封装对象微信统一订单响应),验证返回的对象,通过验证小程序所需的参数并对小程序进行签名,调用支付API小程序成功支付。启动微信的回拨,发送到微信回拨地址
/** *获取用户真实IP *如果有代理,获取真实客户端IP * @ param request * @ return */public静态字符串Get real id(http servlet request){ string xforedforheader=request . getheader(' x-forward-for ');if(xfordeforheader==null){ 0
return request.getRemoteAddr(); }else { return new StringTokenizer(xForwardedForHeader, ",").nextToken().trim(); } }/** * 使用java反射机制,动态获取对象的属性和参数值,排除值为null的情况,并按字典序排序 * @param object * @return */ public static String getSortMap(Object object) throws Exception{ //1.得到属性的名称及值 如果为null不存入map Field [] fields = object.getClass().getDeclaredFields(); Map<String,String> map = new HashMap<>(); for(Field field : fields){ String name = field.getName(); /*String methodName = "get"+name.replaceFirst(name.substring(0, 1), name.substring(0, 1) .toUpperCase());*/ //通过get方法直接获取属性值 field.setAccessible(true); Object value = field.get(object); if (value != null){ map.put(name, value.toString()); } } //排序 Map<String, String> sortMap = new TreeMap<String,String>( new Comparator<String>() { @Override public int compare(String arg0, String arg1) { return arg0.compareTo(arg1); } }); sortMap.putAll(map); StringBuilder sortFeil = new StringBuilder(); //得到键值对的格式(即key1=value1&key2=value2… sortMap.forEach((k,v)-> { sortFeil.append(k+"="+v+"&"); }); //移除最后一个 & sortFeil.deleteCharAt(sortFeil.length()-1); return sortFeil.toString(); }
使用字典序返回的字符连接key,使用MD5进行加密,得到sign
在WechatUnifiedorderRequest对象上使用注解
/** * 微信统一下单请求对象 * * @Author xuelongjiang */@XmlAccessorType(XmlAccessType.FIELD)@XmlRootElement(name = "xml")//xml的根元素public class WechatUnifiedorderRequest implements Serializable{}对象转换为xml字符引入包:import javax.xml.bind.JAXBContext /** * 对象转换为xml * @param object * @return */ public static String objectToXml(Object object){ StringWriter sw = new StringWriter(); try { JAXBContext context = JAXBContext.newInstance(object.getClass()); Marshaller marshaller = context.createMarshaller(); marshaller.marshal(object,sw); }catch (Exception e){ e.printStackTrace(); logger.error("对象解析xml出现异常,对象为"+object.toString()); } return sw.toString(); }
封装对象:WechatUnifiedorderResponse 表示微信统一下单响应的对象。
请求微信统一下单返回示例:
<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <appid><![CDATA[wx2421b1c4370ec43b]]></appid> <sub_appid><![CDATA[wx2421b1c4370ec11b]]></sub_appid> <mch_id><![CDATA[10000100]]></mch_id> <sub_mch_id>![CDATA[10000101]]></appid> <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str> <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign> <result_code><![CDATA[SUCCESS]]></result_code> <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id> <trade_type><![CDATA[JSAPI]]></trade_type></xml>
参数值用XML转义即可,CDATA标签用于说明数据不被XML解析器解析,在转为对象的时候我们需要解析<![CDATA[]]>
WechatUnifiedorderResponse对象使用注解
以上的和WechatUnifiedorderRequest是一样,但是由于需要解析<![CDATA[]]>,我们创建CDataAdapter继承XmlAdapter ,使用注解@XmlJavaTypeAdapter来处理,在WechatUnifiedorderResponse需要处理<![CDATA[]]>的域上使用注解
如下:
@XmlJavaTypeAdapter(CDataAdapter.class)// 解析<![CDATA[]]> private String return_code; //返回状态码
/** * * 注解使用, 对象与xml转换的字段需要有 <![CDATA[]]> * * @Author xuelongjiang */public class CDataAdapter extends XmlAdapter<String,String> { private static Logger logger = LoggerFactory.getLogger(CDataAdapter.class); /** * Do-nothing constructor for the derived classes. */ protected CDataAdapter() { super(); } /** * Convert a value type to a bound type. * * @param v The value to be converted. Can be null. * @throws Exception if there's an error during the conversion. The caller is responsible for * reporting the error to the user through {@link ValidationEventHandler}. */ @Override public String unmarshal(String v) throws Exception { if("<![CDATA[]]>".equals(v)){ return ""; } String v1 = null; String v2 = null; String subStart = "<![CDATA["; int a = v.indexOf(subStart); if(a>= 0){ v1 = v.substring(subStart.length(),v.length()); }else { return v; } String subEnd = "]]>"; int b = v1.indexOf(subEnd); if(b>= 0){ v2 = v1.substring(0,b); } return v2; } /** * Convert a bound type to a value type. * * @param v The value to be convereted. Can be null. * @throws Exception if there's an error during the conversion. The caller is responsible for * reporting the error to the user through {@link ValidationEventHandler}. */ @Override public String marshal(String v) throws Exception { logger.info("对象转换xml:"+"<![CDATA["+ v +"]]>"); return "<![CDATA["+ v +"]]>"; }}
到此为止,我们已经得到微信统一下单的响应值了,后续的处理不是很复杂。按照文档不会有很大的坑。
在做微信支付的时候,难点是以上的:请求参数说明模糊,在经历几次的传参试验及百度谷歌之后,才明白了参数的具体的使用,其实后续在做扫码支付的时候,发现扫码支付解释的比较清楚,小程序的文档确实比较坑。