本文主要針對服務商下特約商戶的小程序支付進行講解。(掃碼支付, h5支付大體流程都差很少,瞭解了小程序支付可以很快接入其餘支付類型)php
說明:本文中的支付都是指在服務商模式下java
一個商家主體能夠在不一樣服務商下申請特約商戶,每一個服務商都會給商家主體在此服務商下一個特約商戶號。小程序
普通商戶申請須要花費大約300RMB,服務商申請特約商戶不須要費用。segmentfault
一個商家主體能夠申請 普通商戶,特約商戶。同一個商戶主體申請的普通商戶與在服務商下申請的特約商戶號是獨立的。api
服務商下的特約商戶的資金流轉不會直接通過服務商的支付帳戶,最終消費者的資金直接和服務商下的特約商戶進行來往,可是服務商能夠查看本身下的特約商戶資金流水。微信
服務商小程序開發文檔app
申請註冊服務商,經過以後登陸微信商戶平臺,進入菜單: 服務商功能 --> 特約商戶管理 -->新增商戶(也就是申請服務商下的特約商戶)
申請若是沒有問題會在三到五天經過,以後能夠在特約商戶管理
下看到服務商本身的特約商戶,咱們在開發中須要 服務商商戶號及這裏的商戶號(特約商戶號)ide
微信官方給的業務流程圖:
post
能夠很清晰的理解業務流程走向。微信支付
統一下單請求參數封裝爲咱們能夠處理的對象:
此處個人命名是: WechatUnifiedorderRequest
如下是我開發中遇到一些坑,主要是因爲微信官方的文檔給的參數很模糊,特別是小程序支付。
名稱 | 描述 |
---|---|
appid | 公衆號appid |
mch_id | 服務商號 |
sub_appid | 小程序的appId |
notify-url | 微信回調地址 |
trade_type | 交易類型 JSAPI:小程序 NATIVE:掃碼支付 |
nonce_str | 32位隨機字符 |
sub_mch_id | 特約商戶號 |
totalPrice | 支付金額單位分 |
spbill_create_ip | 發起支付者的IP |
sub_openid | 發起支付的微信統一標識 |
在填充好了WechatUnifiedorderRequest對象後
咱們須要對對象按照字典序排序
第一步,設全部發送或者接收到的數據爲集合M,將集合M內非空參數值的參數按照參數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
/** * 獲取用戶真實IP * 若是有代理,獲取真實客戶端IP * @param request * @return */ public static String getRealId(HttpServletRequest request){ String xForwardedForHeader= request.getHeader("X-Forwarded-For"); if(xForwardedForHeader == null){ 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 +"]]>"; } }
到此爲止,咱們已經獲得微信統一下單的響應值了,後續的處理不是很複雜。按照文檔不會有很大的坑。
在作微信支付的時候,難點是以上的:請求參數說明模糊,在經歷幾回的傳參試驗及百度谷歌以後,才明白了參數的具體的使用,其實後續在作掃碼支付的時候,發現掃碼支付解釋的比較清楚,小程序的文檔確實比較坑。
參考文檔:
https://developers.weixin.qq....
https://segmentfault.com/a/11...
https://developers.weixin.qq....
關注個人公衆號第一時間閱讀有趣的技術故事
掃碼關注:
也能夠在微信搜索公衆號便可關注我:codexiulian 渴望與你一塊兒成長進步!