移动端开发文档:
微信“app支付”移动端开发文档:sdk
支付宝“手机网站支付转Native支付”移动端开发文档: Sdk
微信支付宝sdk接入流程
前言:
技术有限,仅以集成支付的经历写下此文,希望阅读此文过后能够以native为中心,带动后端、h5快速集成支付sdk。
支付宝支付
1.在支付宝开放平台(https://open.alipay.com)创建应用开通“手机网站支付”“APP支付”等必要的权限,在”接口加签方式”,设置好公钥
2.在(https://docs.open.alipay.com/54/104509)下载sdk,这里我是把jar sdk中必要我类都复制到真实的项目中进行测试的,因为以前接入过,所以比较随意。
基本都拿进来了,这样做只是为了尽快调起支付宝,跑通支付流程。
3.打开AlipayActivity将下面两个参数修改成真实数据
启动AlipayActivity如下这里点击第一个button你就可以看到支付宝支付了,到这已经把流程走完了。但是真正的工作在刚开始。
根据业务可以选择支付宝支付和网页支付
现在了解了支付流程现在开始拆分业务,现在支付宝下单是在app端实现的,为了安全appid还有key都要放到后端,下单的处理逻辑交给后端做,其实就是把app上你实现的下单逻辑给后端用。
4.1支付宝支付
三步
1.App向后端请求订单
2.调起支付宝支付
Runnable payRunnable =newRunnable(){
@Override
public voidrun(){
PayTask alipay =newPayTask(AlipayActivity.this);
Map result = alipay.payV2(orderInfo,true);
Log.i("msp", result.toString());
Message msg =newMessage();
msg.what=SDK_PAY_FLAG;
msg.obj= result;
mHandler.sendMessage(msg);
}
};
Thread payThread =newThread(payRunnable);
payThread.start();
1.查询支付结果
PayResult payResult = newPayResult((Map) msg.obj);
/**
对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
*/
String resultInfo =
payResult.getResult();//同步返回需要验证的信息
String resultStatus =payResult.getResultStatus();
//返回码,标识支付状态,含义如下:
//
9000——订单支付成功
//
8000——正在处理中
//
4000——订单支付失败
//
5000——重复请求
//
6001——用户中途取消
//
6002——网络连接出错
if (TextUtils.equals(resultStatus,"9000")) {
//该笔订单是否真实支付成功,需要依赖服务端的异步通知。
Toast.makeText(AlipayActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
} else {
//该笔订单真实的支付结果,需要依赖服务端的异步通知。
Toast.makeText(AlipayActivity.this, "支付失败", Toast.LENGTH_SHORT).show();
}
注意:这里返回的结果不能作为真实的结果需要在后端查询是否成功
这也是支付宝下单(https://docs.open.alipay.com/203/107090/)时传notify_url(支付宝服务器主动通知商户服务器里指定的页面 )的作用。
这里比较简单,不上代码了。
4.2网页支付
这里主要的逻辑就是app内嵌h5,由h5向native发起的支付请求。
先看官方例子(https://docs.open.alipay.com/203/106493/):
public booleanshouldOverrideUrlLoading(final WebView view, String url) {
final PayTask task =new PayTask(H5PayDemoActivity.this);
//处理订单信息
final String ex =task.fetchOrderInfoFromH5PayUrl(url);
if(!TextUtils.isEmpty(ex)) {
//调用支付接口进行支付
new Thread(new Runnable() {
public void run() {
H5PayResultModel result = task.h5Pay(ex, true);
//处理返回结果
if (!TextUtils.isEmpty(result.getReturnUrl())) {
view.loadUrl(result.getReturnUrl());
}
}
}).start();
} else {
view.loadUrl(url);
}
return true;
}
可以看到就是对url进行拦截看看是否是支付宝支付,如果网页都拦截这效率肯定不行,而且有部分项目中已经有的逻辑,还的按原来的套路来,这边提供一种方法作为参考:
H5用js接口把拼接好的url传递过来,直接用
final PayTask task = newPayTask(H5PayDemoActivity.this);
//处理订单信息
final String ex =task.fetchOrderInfoFromH5PayUrl(url);
if(!TextUtils.isEmpty(ex)) {
//调用支付接口进行支付
new Thread(new Runnable() {
public void run() {
H5PayResultModel result = task.h5Pay(ex, true);
//处理返回结果
//TODO把结果code msg返回h5
//这里有个return_url参数没用
}
}).start();
在把支付的结果返回给h5,然后h5去查询是否支付成功。
这里给出主要代码:
在调试的时候如果后端提供给你是一个支付url,那么简单直接webview加载拦截支付ok了,如果测试时后端给的是如下的Form表单数据就需要组装下(重要数据*号代替),
"
name=\"punchout_form\" method=\"post\"
action=\"https://openapi.alipay.com/gateway.do?sign=dHCdmMt1iG1X99JlHDeDuZVVbyYAnCCvLOlRfm1ZbzzEvHsLkoeo8KUe3mSAYeN5c2r%2BBTOZKgrrhtrKT1ZSU2k%2BPSUOC8ubvrPjefd7imH6ZlQTwFs76p1hJvkwzSFRyd4AoUMI1dxLMAywrbs5Us6O4gSzKetSYE0D1zWH2zplP%2FWTTQZ9pt2WH%2BkijxDwjPverQ5WkX6uQ89zoQQ3fHt8tlUX0hb%2BAQwA%2FB%2B7xtqu8F%2F6sVawbcvlavPp9gKwAy1Bf8qtAY7NXTeNNIQJl9fdjzamZPlnMkgidLEfjKfoCeH2DOsvXzIwsJ%2FFYEMDcH6BTtYAufA9AFUeWX7oiw%3D%3D×tamp=2017-07-28+13%3A45%3A14&sign_type=RSA2¬ify_url=**********8&app_id=**********&method=alipay.trade.wap.pay&version=1.0&alipay_sdk=alipay-sdk-java-dynamicVersionNo&format=json\">\n
type=\"hidden\" name=\"biz_content\"
value=\"{"out_trade_no":"*****************************
","product_code":"QUICK_WAP_PAY","subject":"一元购","total_amount":"0.01"}\">\n
type=\"submit\" value=\"立即支付\" style=\"display:none\">\n\ndocument.forms[0].submit();"
//创建完整的html
private String createALiForm(){
StringBuffer sb = new StringBuffer();
sb.append("").append("");
sb.append("");
sb.append("").append("表单测试").append("");
sb.append("");
sb.append("");
// sb.append(respOder.body);
sb.append(order);
sb.append("");
sb.append("");
// String s = TextUtils.htmlEncode(sb.toString());
return sb.toString() ;
}
加载方式webView.loadDataWithBaseURL(null,createALiForm(), "text/html", "utf-8", null);
到这里支付宝支付介绍完了,剩下的就是根据自己的项目coding代码了
微信支付
1.到微信开放平台https://pay.weixin.qq.com创建应用,开通支付,添加应用签名,接下来看文档https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5
有一步是设置签名和包名
1.微信比支付宝要繁琐一些,支付步骤为:微信下单-->调起微信支付-->接收支付结果-->查询支付结果。
下载SDK开发包https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1
微信下单:具体参数https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
调起微信支付:
接收支付结果:在包名+ .wxapi下新建WXPayEntryActivity接收类
android:exported="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
>
//微信支付回调
public class WXPayEntryActivity extendsActivity implements IWXAPIEventHandler {
private static finalString TAG = "WXPayEntryActivity";
private IWXAPI api;
@Override
public voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wxpay_entry);
api = WXAPIFactory.createWXAPI(this, WXPUtil.APPID);
api.handleIntent(getIntent(), this);
}
@Override
protected voidonNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
@Override
public voidonReq(BaseReq req) {
}
@Override
public voidonResp(BaseResp resp) {
Log.d(TAG, "onPayFinish, errCode = " + resp.errCode);
// 0成功展示成功页面
// -1错误可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。
// -2用户取消无需处理。发生场景:用户不支付了,点击取消,返回APP。
if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示");
builder.setMessage(String.format("微信支付结果:%s",String.valueOf(resp.errCode)));
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish();
}
});
builder.show();
}
}
}
这里收到支付结果然后再做查询真实测支付结果;
这里贴出代码:这个工具类有获取随机字符串,下单,数据转XML,调起支付等工具,其他工具在微信开发包内,网络请求依赖okhttp标配了,
/**
*
*
*注意 商品描述长度有限制, 单位是分
*/public classWXPUtil {
//微信参数配置public staticStringAPI_KEY="******";
public staticStringAPPID="****";
public staticStringMCH_ID="****";
private static finalStringTAG="WXPUtil";
private static finalMediaTypeMEDIA_TYPE_FORM= MediaType.parse("application/x-www-form-urlencoded;
charset=utf-8");//mdiatype这个需要和服务端保持一致private static finalStringBASE_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";//请求接口根地址private staticOkHttpClientmOkHttpClient;//okHttpClient实例staticStringdescription="块钱-这是一个测试产品";
staticStringsn= System.currentTimeMillis() +"";
staticDoubletotalAmount= 0.01;
/**
*初始化RequestManager
*/private static voidinitOkHttp() {
//初始化OkHttpClient
mOkHttpClient=newOkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)//设置超时时间.readTimeout(10,TimeUnit.SECONDS)//设置读取超时时间.writeTimeout(10,TimeUnit.SECONDS)//设置写入超时时间.build();
}
/**
*调用 微信支付前 要先注册
* # registerApp()
*调起微信支付
*
*结果回调在.
*/public static voidloadWXPay(IWXAPI api, Map mresPreOrder) {
try{
String timeStamp = String.valueOf(System.currentTimeMillis() /1000);
Gson gson =newGson();
String jsonRes = gson.toJson(mresPreOrder);
ResPreOrder resPreOrder =newResPreOrder();
resPreOrder = gson.fromJson(jsonRes, ResPreOrder.class);
PayReq request =newPayReq();
request.appId= resPreOrder.appid;
request.partnerId=MCH_ID;//商户号request.prepayId= resPreOrder.prepay_id;
request.packageValue="Sign=WXPay";//
request.nonceStr= resPreOrder.nonce_str;//随机字符串request.timeStamp= timeStamp;//时间戳
//创建signSortedMap sortedMap =newTreeMap();
sortedMap.put("appid", resPreOrder.appid);
sortedMap.put("partnerid",MCH_ID);
sortedMap.put("prepayid", resPreOrder.prepay_id);
sortedMap.put("package","Sign=WXPay");
sortedMap.put("noncestr", resPreOrder.nonce_str);
sortedMap.put("timestamp", timeStamp);//时间戳String sign =createSign(sortedMap);
request.sign= sign;
api.sendReq(request);
}catch(Exception e) {
Log.e(TAG,"异常:"+ e.getMessage());
}
}
/**
*有现成的支付参数直接 调起 微信支付
*/public static voidloadWXPay(IWXAPI api) {
try{
PayReq request =newPayReq();
request.appId="******";
request.partnerId="*****";//商户号request.prepayId="****93061d9c72c700197785826";
request.packageValue="Sign=WXPay";//
request.nonceStr="WqQBEjnJcJDhEFsYwc6RZOVqfTLmaM";//随机字符串request.timeStamp="1500866394";//时间戳request.sign="3d9338bf866e849f789e2472d3bb4b70";
api.sendReq(request);
}catch(Exception e) {
Log.e(TAG,"异常:"+ e.getMessage());
}
}
/**
*获取微信预订单
*
*@paramcallBackWX
*/public static voidgetPreOrder(WXOrder wXOrder, CallBackWXcallBackWX) {
callBackWX.onstart();
//参数拼接SortedMapparameterMap =newTreeMap();
parameterMap.put("appid",APPID);
parameterMap.put("body",cutString(wXOrder.body.replaceAll("[^0-9a-zA-Z\\u4e00-\\u9fa5
]",""), 128));
parameterMap.put("mch_id",MCH_ID);
parameterMap.put("nonce_str",getStringRandom(32));
parameterMap.put("notify_url","http://www.com");
parameterMap.put("out_trade_no", wXOrder.out_trade_no);//商户订单号parameterMap.put("spbill_create_ip",DeviceInfoManager.getDeviceInfo().getIpAddress());
parameterMap.put("fee_type","CNY");
BigDecimal b1 =newBigDecimal(wXOrder.total_fee);
BigDecimal total = b1.multiply(newBigDecimal(100));
java.text.DecimalFormat df =newjava.text.DecimalFormat("0");
parameterMap.put("total_fee", df.format(total));
parameterMap.put("trade_type","APP");
//非必要字段
// parameterMap.put("device_info", wXOrder.device_info);
// parameterMap.put("sign_type", wXOrder.sign_type);
// parameterMap.put("detail", wXOrder.detail);
// parameterMap.put("attach", wXOrder.attach);
// parameterMap.put("device_info", wXOrder.device_info);String sign =createSign(parameterMap);
parameterMap.put("sign", sign);
String requestXML =getRequestXml(parameterMap);
Log.i(TAG,"requestXML =="+ requestXML);
requestPost(BASE_URL,requestXML, callBackWX);
}
/**
*预下单
* okHttp post同步请求表单提交
*
*@param
url接口地址
*@param
params请求参数
*@param
callBackWX回调
*/private static voidrequestPost(String url, String params,finalCallBackWX callBackWX) {
initOkHttp();
try{
//创建一个请求finalRequestBody requestbody = RequestBody.create(MEDIA_TYPE_FORM,params);
Request request =newRequest.Builder()
.url(url)
.post(requestbody)
.build();
//创建一个Call
mOkHttpClient.newCall(request).enqueue(newCallback() {
@Override
public voidonFailure(Call call, IOException e) {
callBackWX.onFailure(e.getMessage());
}
@Override
public voidonResponse(Call call, Response response)throwsIOException{
if(response.isSuccessful()) {
Map map =null;
try{
String sresponse = response.body().string();
map =doXMLParse(sresponse);
Log.i(TAG,"preorederresponse =="+ sresponse);
String isbackok =checkSign(map);
if(isbackok.equals("nosign") || isbackok.equals("signok")){
callBackWX.onsuccessful(map);
}else{//签名不一样callBackWX.onFailure("微信预下单签名错误");
}
}catch(JDOMException e) {
callBackWX.onFailure(e.getMessage());
e.printStackTrace();
}catch(IOException e) {
callBackWX.onFailure(e.getMessage());
e.printStackTrace();
}
}else{
callBackWX.onFailure(response.message());
}
}
});
}catch(Exception e) {
Log.e(TAG, e.toString());
callBackWX.onFailure(e.getMessage());
}
}
//请求xml组装private staticString getRequestXml(SortedMapparameters) {
StringBuffer sb =newStringBuffer();
sb.append("");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
if("attach".equalsIgnoreCase(key) ||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {
sb.append("<"+ key +">"+"+ value +"]]>+ key +">");
}else{
sb.append("<"+ key +">"+ value +"+ key +">");
}
}
sb.append("");
returnsb.toString();
}
/**
*用于检测微信下单返回参数是否修改
*
*@paramparameters
*@returnnosign:return_code返回不为SUCCESS,signok,signerror
*/private staticString checkSign(Map parameters) {
if(parameters.get("return_code") ==null&&!parameters.get("return_code").toString().equals("SUCCESS")){
return "nosign";
}
SortedMap sortedMap =newTreeMap();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if(null!= v && !"".equals(v)
&& !"sign".equals(k)) {
sortedMap.put(k, v);
}
}
String backsign ="";
if(parameters.get("sign") !=null) backsign =parameters.get("sign").toString();
String sign =createSign(sortedMap);
returnsign.equals(backsign) ?"signok":"signerror";
}
//下单生成签名 获取签名https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3private staticString createSign(SortedMap parameters){
StringBuffer sb =newStringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if(null!= v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)){
sb.append(k +"="+ v +"&");
}
}
sb.append("key="+API_KEY);
String sign ="";
try{
sign = MD5.getMessageDigest(sb.toString().getBytes("UTF-8")).toUpperCase();
}catch(UnsupportedEncodingException e) {
e.printStackTrace();
}
returnsign;
}
//生成随机 位数字和字母private staticString getStringRandom(intlength) {
String val ="";
Random random =newRandom();
//参数length,表示生成几位随机数for(inti = 0; i < length; i++) {
String charOrNum = random.nextInt(2) % 2 == 0 ?"char":"num";
//输出字母还是数字if("char".equalsIgnoreCase(charOrNum)){
//输出是大写字母还是小写字母inttemp = random.nextInt(2) % 2 == 0 ? 65 :97;
val += (char) (random.nextInt(26) + temp);
}else if("num".equalsIgnoreCase(charOrNum)) {
val += String.valueOf(random.nextInt(10));
}
}
returnval;
}
//xml解析public staticMap doXMLParse(String strxml)throwsJDOMException,IOException {
strxml = strxml.replaceFirst("encoding=\".*\"","encoding=\"UTF-8\"");
Map m =newHashMap();
if(null== strxml ||"".equals(strxml)) {
returnm;
}
InputStream in =newByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder =newSAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v ="";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
}else{
v =getChildrenText(children);
}
m.put(k, v);
}
//关闭流in.close();
returnm;
}
public staticStringgetChildrenText(List children) {
StringBuffer sb =newStringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<"+ name +">");
if(!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("+ name +">");
}
}
returnsb.toString();
}
/**
*截取length -3长度 字符串后加...
*
*@paramcontent
*@paramlength
*@returncontent为空返回“”;content长度小于length或者 小于3返回原字符串;
*/private staticString cutString(String content,intlength) {
if(TextUtils.isEmpty(content)) {
return "";
}
if(content.length() < length || length < 3) {
returncontent;
}
content = content.substring(0, length - 3) +"...";
returncontent;
}
publicCallBackWXcallBcak;
public interfaceCallBackWX{
voidonstart();
voidonsuccessful(Map mpreOrder);
voidonFailure(String msg);
}
}
到这里已经在app端把支付流程除了最后的后端查询支付结果都走了。
然后也是业务拆分,微信下单给后端做,最后支付的结果查询给h5(因为我们的业务是由h5发起的)
问题总结
在实际集成的过程主要有两个问题:
1.微信支付需要应用签名,这个签名配置不正确会导致支付失败,最好的做法是根据微信提供的工具把app中签名取出来放到微信支付平台;
2.支付宝支付提供了两种调起支付宝支付的方式个人建议使用PayTask alipay =newPayTask(AliPayActivity.this);,这里遇到的最大问题是订单数据传递,订单数据由后端-->h5 -->app ,在Android和ios接收到数据的时候有可能遇到解析不了,这样后端h5 Android ios都要跟着反反复复调试,建议以后订单传递采用这种方式:后端生成可以直接调起支付宝的订单,然后base64加密转换下,把转换好的数据直接通过h5传给app,app拿到订单数据后base64解密下直接调起支付宝
3.没有安装支付宝会调到支付宝H5,没有安装微信,微信提供了方法检测,这里做好判断,做好用户友好设计
结束语
集成的过程都是快速的让支付正常使用,然后再根据业务详细coding,两大支付平台对比下来微信支付接入难些,下载的demo没有发现直接支付的流程,然后请求下单,数据封装XML格式,还有返回结果接收都比较不同,写下这个总结方便多接入的开发者快速接入。
网友评论