微信支付服務商接入指引
本文主要針對服務商下特約商戶的小程序支付進行講解。(掃碼支付, h5支付大致流程都差不多,了解了小程序支付能夠很快接入其他支付類型)
說明:本文中的支付都是指在服務商模式下
支付主體
-
服務商:擁有支付開發能力的第三方提供商
-
普通商戶: 擁有開發能力的商戶
-
特約商戶:服務商下的商戶
一個商家主體可以在不同服務商下申請特約商戶,每個服務商都會給商家主體在此服務商下一個特約商戶號。
普通商戶申請需要花費大約300RMB,服務商申請特約商戶不需要費用。
一個商家主體可以申請 普通商戶,特約商戶。同一個商戶主體申請的普通商戶與在服務商下申請的特約商戶號是獨立的。
服務商
服務商下的特約商戶的資金流轉不會直接經過服務商的支付賬戶,最終消費者的資金直接和服務商下的特約商戶進行來往,但是服務商可以查看自己下的特約商戶資金流水。
服務商小程序開發文檔
開發支付
開發之前
申請注冊服務商,通過之后登錄微信商戶平臺,進入菜單: 服務商功能 --> 特約商戶管理 -->新增商戶(也就是申請服務商下的特約商戶)
申請如果沒有問題會在三到五天通過,之后可以在特約商戶管理
下看到服務商自己的特約商戶,我們在開發中需要 服務商商戶號及這里的商戶號(特約商戶號)
支付需要接口:微信統一下單,及提供給微信的回調接口
微信官方給的業務流程圖:

可以很清晰的理解業務流程走向。
統一下單接口
微信統一下單請求參數
統一下單請求參數封裝為我們可以處理的對象:
此處我的命名是: WechatUnifiedorderRequest
以下是我開發中遇到一些坑,主要是由于微信官方的文檔給的參數很模糊,特別是小程序支付。

在填充好了WechatUnifiedorderRequest對象后對我們填充的值按照字典排序,連接key進行簽名,以xml格式字符向微信發起請求
-
我們需要對對象按照字典序排序
第一步,設所有發送或者接收到的數據為集合M,將集合M內非空參數值的參數按照參數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
-
字典排序后的字符連接key(需要在微信商戶平臺進行配置建議使用UUID生成32位)
-
MD5加密簽名,得到sign填充WechatUnifiedorderRequest對象
-
WechatUnifiedorderRequest轉換為微信需要的xml類型
-
發起請求
-
得到微信統一下單的響應(是xml字符格式),解析為對象(對返回的響應封裝對象進行處理WechatUnifiedorderResponse),
-
對返回的對象進行驗證,通過驗證返回給小程序 需要的參數及簽名 小程序調起支付API
-
小程序支付成功,微信開始回調在統一下單傳給微信的回調地址
獲取下單用戶的真實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 +"]]>";
}
}
|
到此為止,我們已經得到微信統一下單的響應值了,后續的處理不是很復雜。按照文檔不會有很大的坑。
在做微信支付的時候,難點是以上的:請求參數說明模糊,在經歷幾次的傳參試驗及百度谷歌之后,才明白了參數的具體的使用,其實后續在做掃碼支付的時候,發現掃碼支付解釋的比較清楚,小程序的文檔確實比較坑。
|