微信支付服務商接入指引

微信支付服務商接入指引

本文主要針對服務商下特約商戶的小程序支付進行講解。(掃碼支付, 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 發起支付的微信統一標識

對咱們填充的值按照字典排序,鏈接key進行簽名,以xml格式字符向微信發起請求

在填充好了WechatUnifiedorderRequest對象後

  1. 咱們須要對對象按照字典序排序

    第一步,設全部發送或者接收到的數據爲集合M,將集合M內非空參數值的參數按照參數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
  2. 字典排序後的字符鏈接key(須要在微信商戶平臺進行配置建議使用UUID生成32位)
  3. MD5加密簽名,獲得sign填充WechatUnifiedorderRequest對象
  4. WechatUnifiedorderRequest轉換爲微信須要的xml類型
  5. 發起請求
  6. 獲得微信統一下單的響應(是xml字符格式),解析爲對象(對返回的響應封裝對象進行處理WechatUnifiedorderResponse),
  7. 對返回的對象進行驗證,經過驗證返回給小程序 須要的參數及簽名 小程序調起支付API
  8. 小程序支付成功,微信開始回調在統一下單傳給微信的回調地址
獲取下單用戶的真實IP
/**
     * 獲取用戶真實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轉換爲微信須要的xml類型

在WechatUnifiedorderRequest對象上使用註解

  • @xmlAccessorType @xmlAccessorType(XmlAccessType.FIELD)
  • @xmlRootElement @xmlRootElement(name ="xml") ( name = "xml : "WechatUnifiedorderReques對象轉換爲xml的根名稱)
/**
 * 微信統一下單請求對象
 *
 * @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();
    }
獲得微信統一下單的響應(是xml字符格式),解析爲對象

封裝對象: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對象使用註解

    • @XmlAccessorType(XmlAccessType.FIELD)
    • @XmlRootElement(name = "xml")//解析xml的根元素

    以上的和WechatUnifiedorderRequest是同樣,可是因爲須要解析<![CDATA[]]>,咱們建立CDataAdapter繼承XmlAdapter ,使用註解@XmlJavaTypeAdapter來處理,在WechatUnifiedorderResponse須要處理<![CDATA[]]>的域上使用註解

    以下:

    @XmlJavaTypeAdapter(CDataAdapter.class)// 解析<![CDATA[]]>
        private String return_code; //返回狀態碼
    CDataAdapter解析<![CDATA[]]>
    /**
     *
     * 註解使用, 對象與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 渴望與你一塊兒成長進步!

    相關文章
    相關標籤/搜索