springmvc实现支付宝移动支付的签名与异步回调
现在越来越多的应用需要接入支付的功能,我们通过手机客户端输入银行账号和验证码就可以轻松的实现支付,对于我们的生活非常方便,今天我们来说一下如何在我们的springmvc项目+Android客户端实现手机支付。
我们知道支付宝的支付方式,通过客户端发起支付请求,为了保证我们的数据的安全,一般都会把数据的签名放到服务器端来实现,目的是可以保证我们的私钥不放在客户端,从而保证了安全性。以下是支付宝的交易流程图。
那么在这里,我们的Android客户端要实现支付宝的支付功能,大概需要以下几个步骤:
1、生成公钥和私钥。
2、订单信息生成
3、服务端生成签名函数
4、客户端配置与调用客户端SDK
5、支付结果
6、异步回调到服务器
我逐一把上面的步骤为大家讲解以下,前提是你已经签约商家服务的移动支付了,否则是无法进行移动支付的,一般都是公司的客户。
1、生成公钥和私钥
使用openssl生成公私钥,这里具体的安装我就不说了,之前的文章也有介绍,可以关注我的头条号:一点热,然后阅读,这里我们也可以到支付宝商家服务的网站下载ssl工具
https://doc.open.alipay.com/doc2/detail.htm?treeId=58&articleId=103242&docType=1
这里使用openssl的方法
OpenSSL> genrsa -out rsa_private_key.pem 1024 #生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥
OpenSSL> exit
然后需要注意一点就是把得到公钥rsa_public_key.pem提交给在支付宝后台里设置,对于使用Java的开发者,将pkcs8在console中输出的私钥去除头尾、换行和空格,作为开发者私钥,对于.NET和PHP的开发者来说,无需进行pkcs8命令行操作。
2、订单信息生成
这里主要需要填写的是订单的信息和价格,注意还有回调的URL,这个一定要填写,并且是外网可以访问的URL,不能带参数。我这里使用http://www.yeehot.com/mynotifyurl
/**
* 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://www.yeehot.com/mynotifyurl" + "\"";
// 服务接口名称, 固定值
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\"";
// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
// orderInfo += "&paymethod=\"expressGateway\"";
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;
}
3、服务端生成签名函数
我们在第2步的时候通过调用getOrderInfo("","","");得到的字符串,我们把得到的字符串提交到服务器签名,这个步骤至关重要,千万不要在客户端实现。
服务器springmvc实现签名的方法:
首先添加一些httpclient的库
这里我使用maven
<!-- http://mvnrepository.com/artifact/commons-httpclient/commons-httpclient -->
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4</version>
</dependency>
<!-- http://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.6</version>
</dependency>
然后,把服务器的一些源文件复制到我们的项目
复制后结构如下:
在AlipayConfig.java文件输入自己的公钥和私钥,还有账号信息
// 合作身份者ID,以2088开头由16位纯数字组成的字符串
public static String partner = "";
// 商户的私钥
public static String private_key = "fadfdfdafdafdfdfdfafdf";
// 支付宝的公钥,无需修改该值
publicstatic String ali_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRAFljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQEB/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5KsiNG9zpgmLCUYuLkxpLQIDAQAB";
最后添加我们的Controller信息
public class PayController {
@RequestMapping(value = "/paysign",produces ="application/json; charset=utf-8")
@ResponseBody
public String inputsession(String order){
if (order!=null) {
try {
order=URLDecoder.decode(order, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
order=order.replace("://", "%3A%2F%2F");
order=order.replace("/", "%2F");
System.out.println(order);
order= RSA.sign(order, AlipayConfig.private_key,AlipayConfig.input_charset);
System.out.println(order);
return order;
}
}
客户端的请求方法:请执行实现访问服务器的方法。
String orderinfo=getOrderInfo("yeehot","Y币充值","10");
Http.get("http://www.yeehot.com/paysign?order="+orderinfo);
4、客户端配置与调用客户端SDK
对于客户端的配置,这里是Android客户端,由于最新的版本不用下载支付宝插件,所以需要我们在客户端上配置H5调用的Activity
我们在AndroidManifest.xml
添加调用H5的页面
<!-- alipay sdk begin -->
<activity
android:name="com.alipay.sdk.app.H5PayActivity"
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:exported="false"
android:screenOrientation="behind"
android:windowSoftInputMode="adjustResize|stateHidden" >
</activity>
<!-- alipay sdk end -->
接着,我们在客户端调用SDK.
我们刚刚从服务器获取到sign,已经生成了orderInfo,所以我们可以直接调用如下的方法。
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();
5、支付结果
当支付成功或者取消的时候,会返回响应如下的结果
@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;
}
};
};
6、异步回调到服务器
这里的异步回调,不是我们主动实现的,是有支付宝服务器自动向我们刚刚订单Order填写的异步URL的地址发送的请求。
那么在springMVC实现异步的Contrller
http://www.yeehot.com/mynotifyurl
@RequestMapping(value = "/mynotifyurl",produces ="application/json; charset=utf-8")
@ResponseBody
public String mynotifyurl(HttpServletRequest request ){
//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
//交易状态
String trade_status = "";
try {
trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以上仅供参考)//
if(AlipayNotify.verify(params)){//验证成功
//////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代码
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
if(trade_status.equals("TRADE_FINISHED")){
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
//请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
} else if (trade_status.equals("TRADE_SUCCESS")){
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//付款完成后,支付宝系统发送该交易状态通知
//请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
}
return"success";
}else{//验证失败
return"fail";
}
}
最后客户端呈现的方式如下图
这里就讲解到这里了,如果在自己的项目中遇到问题可以提问,下一节讲解微信支付的SpringMVC的实现方法,欢迎继续关注我的头条号:一点热。欢迎大家收藏与转发,如果转载到其它网站,请与我联系。