不废话,直接从开发者角度详解支付宝接入。
首先说一下,支付宝现有两版支付宝支付接口,暂称老版为1.0,新版为2.0。由于我们PC端接入的是1.0版,所以我这弄的也是1.0。
两版的主要区别在于调用支付接口时的请求参数有所变化,其余流程大致相同。
1.0官方文档地址:https://doc.open.alipay.com/doc2/detail?treeId=59&articleId=103563&docType=1
2.0官方文档地址:https://doc.open.alipay.com/
2.0文档入口截图:
2.0文档要点及SDK下载
下面详细介绍一下支付宝1.0Android端接入。
1、开发前:有可能是因为客户的建议或者本来需求的安排或者大领导的命令,总之产品经理发话,我们要接入支付宝了
首先本公司相关部门向支付宝提出申请,经过签约审核。然后支付宝提供给我们一些密钥,我们需要用支付宝文档里提供的工具生成自己的私钥和公钥,把公钥上传给支付宝同时下载支付宝公钥。其实这些步骤都是需要我们本公司服务端处理的,以及后续的生成订单和签名。仅针对于Android端开发来说需要做的事情很简单。但是了解一下大概流程毕竟没有坏处。并且有可能有时候服务端也是我们自己那就必须要知道这些了。
2、接入前准备阶段工作:
密钥配置及管理:在上述1.0官方文档地址页面点击快速接入,然后在接入前准备阶段中点击密钥作用进入下图。即可按照文档中指示的步骤获取商户PID、生成私钥与公钥、公钥上传的一些说明。
SDK及demo示例下载:在1.0文档中点击资源下载,下载的压缩包解压之后打开里边包括服务端demo和客户端demo,Android、IOS的都有。我 们只需要找我们要用的就可以了。解压(支付宝钱包支付接口开发包标准版(Android ......)),解压之后包括一个SDK的jar包和一个demo示例, demo很简单可以导入到开发工具里看一下。
3、具体的接入流程:
点击集成流程详解,包括IOS接入流程和Android接入流程
按照流程导入jar包然后Build Path一下,在Manifest中添加权限和声明,添加混淆规则,然后就看代码了。
package com.alipay.sdk.pay.demo; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Random; import com.alipay.sdk.app.PayTask; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import android.view.View; import android.widget.Toast; public class PayDemoActivity extends FragmentActivity { // 商户PID public static final String PARTNER = "2088********8050"; // 商户收款账号 public static final String SELLER = "2088********8050"; // 商户私钥,pkcs8格式 public static final String RSA_PRIVATE = "MIICd*************************"; // 支付宝公钥 public static final String RSA_PUBLIC = ""; private static final int SDK_PAY_FLAG = 1; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @SuppressWarnings("unused") public void handleMessage(Message msg) { switch (msg.what) { case SDK_PAY_FLAG: { PayResult payResult = new PayResult((String) msg.obj); /** * 同步返回的结果必须放置到服务端进行验证(验证的规则请看https://doc.open.alipay.com/doc2/ * detail.htm?spm=0.0.0.0.xdvAU6&treeId=59&articleId=103665& * docType=1) 建议商户依赖异步通知 */ String resultInfo = payResult.getResult();// 同步返回需要验证的信息 String resultStatus = payResult.getResultStatus(); // 判断resultStatus 为“9000”则代表支付成功,具体状态码代表含义可参考接口文档 if (TextUtils.equals(resultStatus, "9000")) { Toast.makeText(PayDemoActivity.this, "支付成功", Toast.LENGTH_SHORT).show(); } else { // 判断resultStatus 为非"9000"则代表可能支付失败 // "8000"代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态) if (TextUtils.equals(resultStatus, "8000")) { Toast.makeText(PayDemoActivity.this, "支付结果确认中", Toast.LENGTH_SHORT).show(); } else { // 其他值就可以判断为支付失败,包括用户主动取消支付,或者系统返回的错误 Toast.makeText(PayDemoActivity.this, "支付失败", Toast.LENGTH_SHORT).show(); } } break; } default: break; } }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pay_main); } /** * call alipay sdk pay. 调用SDK支付 * */ public void pay(View v) { if (TextUtils.isEmpty(PARTNER) || TextUtils.isEmpty(RSA_PRIVATE) || TextUtils.isEmpty(SELLER)) { new AlertDialog.Builder(this).setTitle("警告").setMessage("需要配置PARTNER | RSA_PRIVATE| SELLER") .setPositiveButton("确定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialoginterface, int i) { // finish(); } }).show(); return; } String orderInfo = getOrderInfo("测试的商品", "该测试商品的详细描述", "0.01"); /** * 特别注意,这里的签名逻辑需要放在服务端,切勿将私钥泄露在代码中! */ String sign = sign(orderInfo); try { /** * 仅需对sign 做URL编码 */ sign = URLEncoder.encode(sign, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } /** * 完整的符合支付宝参数规范的订单信息 */ final String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType(); Runnable payRunnable = new Runnable() { @Override public void run() { // 构造PayTask 对象 PayTask alipay = new PayTask(PayDemoActivity.this); // 调用支付接口,获取支付结果 String result = alipay.pay(payInfo, true); Message msg = new Message(); msg.what = SDK_PAY_FLAG; msg.obj = result; mHandler.sendMessage(msg); } }; // 必须异步调用 Thread payThread = new Thread(payRunnable); payThread.start(); } /** * get the sdk version. 获取SDK版本号 * */ public void getSDKVersion() { PayTask payTask = new PayTask(this); String version = payTask.getVersion(); Toast.makeText(this, version, Toast.LENGTH_SHORT).show(); } /** * 原生的H5(手机网页版支付切natvie支付) 【对应页面网页支付按钮】 * * @param v */ public void h5Pay(View v) { Intent intent = new Intent(this, H5PayDemoActivity.class); Bundle extras = new Bundle(); /** * url是测试的网站,在app内部打开页面是基于webview打开的,demo中的webview是H5PayDemoActivity, * demo中拦截url进行支付的逻辑是在H5PayDemoActivity中shouldOverrideUrlLoading方法实现, * 商户可以根据自己的需求来实现 */ String url = "http://m.taobao.com"; // url可以是一号店或者淘宝等第三方的购物wap站点,在该网站的支付过程中,支付宝sdk完成拦截支付 extras.putString("url", url); intent.putExtras(extras); startActivity(intent); } /** * create the order info. 创建订单信息 * */ private String getOrderInfo(String subject, String body, String price) { // 签约合作者身份ID String orderInfo = "partner=" + "\"" + PARTNER + "\""; // 签约卖家支付宝账号 orderInfo += "&seller_id=" + "\"" + SELLER + "\""; // 商户网站唯一订单号 orderInfo += "&out_trade_no=" + "\"" + getOutTradeNo() + "\""; // 商品名称 orderInfo += "&subject=" + "\"" + subject + "\""; // 商品详情 orderInfo += "&body=" + "\"" + body + "\""; // 商品金额 orderInfo += "&total_fee=" + "\"" + price + "\""; // 服务器异步通知页面路径 orderInfo += "¬ify_url=" + "\"" + "http://notify.msp.hk/notify.htm" + "\""; // 服务接口名称, 固定值 orderInfo += "&service=\"mobile.securitypay.pay\""; // 支付类型, 固定值 orderInfo += "&payment_type=\"1\""; // 参数编码, 固定值 orderInfo += "&_input_charset=\"utf-8\""; // 设置未付款交易的超时时间 // 默认30分钟,一旦超时,该笔交易就会自动被关闭。 // 取值范围:1m~15d。 // m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。 // 该参数数值不接受小数点,如1.5h,可转换为90m。 orderInfo += "&it_b_pay=\"30m\""; // extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付 // orderInfo += "&extern_token=" + "\"" + extern_token + "\""; // 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空 orderInfo += "&return_url=\"m.alipay.com\""; return orderInfo; } /** * get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范) * */ private String getOutTradeNo() { SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault()); Date date = new Date(); String key = format.format(date); Random r = new Random(); key = key + r.nextInt(); key = key.substring(0, 15); return key; } /** * sign the order info. 对订单信息进行签名 * * @param content * 待签名订单信息 */ private String sign(String content) { return SignUtils.sign(content, RSA_PRIVATE); } /** * get the sign type we use. 获取签名方式 * */ private String getSignType() { return "sign_type=\"RSA\""; } }
我们只要仔细看懂这个类的代码,就差不多了解调用的流程了。其中构造PayTask对象调用支付接口时必须新开起一个线程,然后在handler中接收返回的结果。
这里针对掉支付宝接口所用的参数再说明一下,示例代码中我们可以看到调支付接口时只传了两个参数,payInfo和一个boolean型的值,payInfo就是我们加入商户id :PARTNER(2088开头16位数字字符串)、商户收款账号:SELLER的订单信息和这个订单信息签名加密后的字符串以及签名方式拼接的字符串。boolean型的值表示是不是需要一个调接口时的缓冲框,直接传true。
现在我们再来看一下我们与支付宝交互的功能流程图:
注意在拼接订单信息时有一个参数notify_url
对于处理完成的交易,支付宝会以两种方式把数据分别反馈给商户客户端和商户服务器端。
- 在手机客户端上,手机支付宝支付开发包直接把处理的数据结果反馈给商户客户端;
- 在服务器端上,手机支付宝支付服务器端主动发起通知,调用商户在请求时设定好的页面路径(参数notify_url,如果商户没设定,则不会进行该操作)。
接入流程全部弄明白之后,在自己的工程中集成支付宝支付是不是就so easy啦!
ps:如果贵公司APP开发服务端与移动端分工明确的话,Android工程师只需要导入jar包配置好工程中接入支付宝需要的基本配置然后传基本的订单信息给自己服务器坐等返回调支付接口所需的整个字符串就好了。