之前写了一篇安卓支付宝支付的文章,但是那些隐私数据都在安卓端,包括私钥,支付宝企业账号,还有APPID,这样太不安全了,另外,如果不设服务端做异步通知的话,同样也是不安全的,容易被人钻空子。因此,这篇文章是在安卓端在发起支付请求时,先向服务端请求支付接口,然后安卓才能发起支付,支付成功后,安卓会有个同步的通知支付成功,但是对于支付成功的后续订单等操作仍依赖服务端的异步通知,当服务端接收到支付宝发过来的异步通知后,才把订单状态修改为已付款。
(1)app
(2)服务端异步通知截图
1.注册企业账号;
2.创建应用 ;
3.配置应用 ;
4.开启支付APP支付 ;
5.签约,在支付宝开放平台中跟着步骤走就可以完成的操作,为对接做准备工作。
6.去管理中心得到应用的APPID;
7.用支付宝开放平台开发助手生成一一对密钥,分别是应用公钥和私钥
8.用应用公钥去生成支付宝公钥,如图,点击 设置公钥
9.填写异步通知URL;
然后就可以开始做服务端和客户端的开发啦!
对于怎么才能得到APPID。首先你得是企业账号才能接入支付宝支付,因为还要签约的。
在这里想说明一下,很多人都分不清私钥和公钥到底要怎么用,用哪一个?其实很简单。首先是打开这个支付宝开放平台开发助手,如下图
点击生成密钥,就会生成一个公钥和一个私钥。这个私钥就是我上面所说的APP_PRIVATE_KEY,但是这个生成的公钥有什么用呢?
这个公钥要把它放到支付宝开发平台那里,把它生成支付宝公钥,而生成的支付宝公钥,就是我上述所说的ALIPAY_PUBLIC_KEY。至于普通的公钥怎么才能生成支付宝公钥呢?
点击设置公钥,就能生成支付宝公钥了。
有写可能会问,为什么要这么做呢?我们要知道这三个密钥的作用分别是什么,用着才不会乱。
1.应用私钥:用于请求支付宝数据加签(放在服务端);
2.应用公钥:用于提供给支付宝,让支付宝去拿着公钥获取你私钥加签后的数据;(放在支付宝开发平台用于生成支付宝公钥);
3.支付宝公钥:是用来接收支付宝回调信息的(放在服务端);
这样说应该可以明白了吧。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
public class BaseEntity {
private String code;
private String message;
private Object content;
public BaseEntity(String code, String message, Object content) {
this.code = code;
this.message = message;
this.content = content;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
}
public class AlipayConfig {
public static final String APP_ID = "这里填签约后的APPID";
public static final String APP_PRIVATE_KEY = "这里填用支付宝开放平台开发助手生成的应用私钥";
public static final String ALIPAY_PUBLIC_KEY = "这里填支付宝公钥";
public static final String CHARSET = "UTF-8";
}
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.example.demo.bean.BaseEntity;
import com.example.demo.config.AlipayConfig;
import com.example.demo.utils.AliRequestParam;
import com.example.demo.utils.OrderNo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class AlipayController {
@SuppressWarnings("unchecked")
@RequestMapping(value = "/alipay", method = RequestMethod.POST)
public Object alipayment(@RequestParam String payMoney) {
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
AlipayConfig.APP_ID, AlipayConfig.APP_PRIVATE_KEY, "json",
AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY,
"RSA2");
String out_trade_no=OrderNo.getOutTradeNo();
System.out.println("商户订单号"+out_trade_no);
//构建支付宝请求参数
AlipayTradeAppPayRequest request = AliRequestParam.startRequestAli(payMoney,out_trade_no);
try {
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
System.out.println(response.getBody());//就是orderString 可以直接给客户端请求,客户端可以直接发起支付,无需再做处理。
@SuppressWarnings("rawtypes")
Map map = new HashMap();
map.put("orderString", response.getBody());
BaseEntity baseEntity = new BaseEntity("200", "支付宝下单成功", map);
//这里做生成订单操作,订单状态为未支付
return baseEntity;
} catch (AlipayApiException e) {
e.printStackTrace();
}
BaseEntity baseEntity = new BaseEntity("200", "支付宝下单失败", null);
return baseEntity;
}
//异步通知
@RequestMapping(value = "/pay_notify",method = RequestMethod.POST)
public void notifyUrl(HttpServletResponse response,HttpServletRequest request) throws IOException, AlipayApiException {
System.out.println("异步通知");
PrintWriter out = response.getWriter();
request.setCharacterEncoding("utf-8");//乱码解决,这段代码在出现乱码时使用
//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map<String,String[]> requestParams = request.getParameterMap();
for(String str :requestParams.keySet()){
String name = str;
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);
}
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, "RSA2"); //调用SDK验证签名
if(!signVerified) {
System.out.println("验签失败");
out.print("fail");
return;
}
//商户订单号,之前生成的带用户ID的订单号
String out_trade_no = params.get("out_trade_no");
//支付宝交易号
String trade_no = params.get("trade_no");
//付款金额
String total_amount = params.get("total_amount");
//交易状态
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
// String appId=params.get("app_id");//支付宝分配给开发者的应用Id
// String notifyTime=params.get("notify_time");//通知时间:yyyy-MM-dd HH:mm:ss
// String gmtCreate=params.get("gmt_create");//交易创建时间:yyyy-MM-dd HH:mm:ss
String gmtPayment=params.get("gmt_payment");//交易付款时间
// String gmtRefund=params.get("gmt_refund");//交易退款时间
// String gmtClose=params.get("gmt_close");//交易结束时间
// String tradeNo=params.get("trade_no");//支付宝的交易号
// String outTradeNo = params.get("out_trade_no");//获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
// String outBizNo=params.get("out_biz_no");//商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
// String buyerLogonId=params.get("buyer_logon_id");//买家支付宝账号
// String sellerId=params.get("seller_id");//卖家支付宝用户号
// String sellerEmail=params.get("seller_email");//卖家支付宝账号
// String totalAmount=params.get("total_amount");//订单金额:本次交易支付的订单金额,单位为人民币(元)
// String receiptAmount=params.get("receipt_amount");//实收金额:商家在交易中实际收到的款项,单位为元
// String invoiceAmount=params.get("invoice_amount");//开票金额:用户在交易中支付的可开发票的金额
// String buyerPayAmount=params.get("buyer_pay_amount");//付款金额:用户在交易中支付的金额
// String tradeStatus = params.get("trade_status");// 获取交易状态
if(trade_status.equals("TRADE_FINISHED")){
/*此处可自由发挥*/
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
}else if (trade_status.equals("TRADE_SUCCESS")){
//这里就可以更新订单状态为已支付啦
}
out.print("success");
}
}
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
public class AliRequestParam {
public static AlipayTradeAppPayRequest startRequestAli(String payMoney,String trade_no) {
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("某公司");
model.setSubject("某商品");
model.setOutTradeNo(trade_no);
model.setTimeoutExpress("30m");
model.setTotalAmount(payMoney);
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("这里填你服务端的地址+异步通知的地址,如:http://example.com/pay_notify");//这里要注意的是,这个地址外网必须要能访问,不然服务端无法接收到异步通知的,你只需要把example.com改成你的ip地址和端口号即可,如果做了端口映射的话就只填ip地址即可
return request;
}
}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
public class OrderNo {
/**
* get the out_trade_no for an order. 生成商户订单号,该值在商户端应保持唯一(可自定义格式规范)
*/
public static 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;
}
}
server.port=8080
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
服务器到这就完成啦。
//http请求
implementation 'com.zhy:okhttputils:2.6.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.alibaba:fastjson:1.2.39'
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import androidx.fragment.app.FragmentActivity;
import com.alibaba.fastjson.JSON;
import com.alipay.sdk.app.EnvUtils;
import com.learn.agg.R;
import com.learn.agg.alipay.AlipayAck;
import com.learn.agg.alipay.AlipayHelper;
import com.learn.agg.alipay.URLInterface;
import com.zhy.http.okhttp.OkHttpUtils;
import com.zhy.http.okhttp.callback.StringCallback;
import okhttp3.Call;
/**
* 重要说明:
*
* 这里只是为了方便直接向商户展示支付宝的整个支付流程;所以Demo中加签过程直接放在客户端完成;
* 真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成;
* 防止商户私密数据泄露,造成不必要的资金损失,及面临各种安全风险;
*/
public class PayDemoActivity extends FragmentActivity {
/** 商户私钥,pkcs8格式 */
/** 如下私钥,RSA2_PRIVATE 或者 RSA_PRIVATE 只需要填入一个 */
/** 如果商户两个都设置了,优先使用 RSA2_PRIVATE */
/** RSA2_PRIVATE 可以保证商户交易在更加安全的环境下进行,建议使用 RSA2_PRIVATE */
/** 获取 RSA2_PRIVATE,建议使用支付宝提供的公私钥生成工具生成, */
public PayDemoActivity() {
}
@Override
protected void onCreate(Bundle savedInstanceState) {
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
super.onCreate(savedInstanceState);
setContentView(R.layout.pay_main);
RadioGroup radioGroup;
final RadioButton alipay;
alipay = findViewById(R.id.alipay);
radioGroup = findViewById(R.id.payWay_choose);
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
if (alipay.getId()==i){
}
}
});
}
/**
* 支付宝支付业务
*
* @param v
*/
public void payV2(View v) {
/**
* 这里只是为了方便直接向商户展示支付宝的整个支付流程;所以Demo中加签过程直接放在客户端完成;
* 真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成;
* 防止商户私密数据泄露,造成不必要的资金损失,及面临各种安全风险;
*
* orderInfo的获取必须来自服务端
* 如果RSA2_PRIVATE RSA_PRIVATE都设置了,那么我是优先选择RSA2_PRIVATE,因为更加安全
*/
//这里是用OkHttpUtils向服务器发起请求
String url = URLInterface.BASE_URL + "/alipay";
String paymoney = "0.05";//分为单位 1==1元
OkHttpUtils
.post()
.url(url)
.addParams("payMoney", paymoney )
.build()
.execute(new StringCallback() {
@Override
public void onError(Call call, Exception e, int id) {
Toast.makeText(PayDemoActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(String response, int id) {
AlipayAck alipayAck = JSON.parseObject(response, AlipayAck.class);
Toast.makeText(PayDemoActivity.this, alipayAck.getMessage(), Toast.LENGTH_SHORT).show();
testAliPay(alipayAck.getContent().getOrderString());
}
});
}
private void testAliPay(String orderInfo) {
AlipayHelper mAlipayHelper = new AlipayHelper();
mAlipayHelper.startAlipay(this, orderInfo, new AlipayHelper.OnPayClickListener() {
@Override
public void paySuccess() {
//支付成功
Log.d("tag", "paySuccess: 哈哈哈");
Toast.makeText(PayDemoActivity.this,"支付成功",Toast.LENGTH_LONG).show();
//支付成功的操作
}
@Override
public void payFailure() {
//支付失败
Log.d("tag", "payFailure: ");
}
});
}
}
public class AlipayAck extends BaseEntity {
private ContentBean content;
public ContentBean getContent() {
return content;
}
public void setContent(ContentBean content) {
this.content = content;
}
public static class ContentBean {
private String orderString;
private String out_trade_no;
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getOrderString() {
return orderString;
}
public void setOrderString(String orderString) {
this.orderString = orderString;
}
}
}
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import com.alipay.sdk.app.PayTask;
import java.util.Map;
public class AlipayHelper {
private static final int SDK_PAY_FLAG = 1;
private static AlipayHelper mAlipayHelper = new AlipayHelper();
private OnPayClickListener onPayClickListener;
//支付成功接口回调
public interface OnPayClickListener {
void paySuccess();
void payFailure();
}
public void startAlipay(final Activity activity, final String orderInfo, OnPayClickListener onPayClickListener) {
if (onPayClickListener == null || activity == null || orderInfo == null) {
return;
}
this.onPayClickListener = onPayClickListener;
Runnable payRunnable = new Runnable() {
@Override
public void run() {
PayTask alipay = new PayTask(activity);
Map<String, String> result = alipay.payV2(orderInfo, true);
Log.i("msp", result.toString());
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
Thread payThread = new Thread(payRunnable);
payThread.start();
}
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@SuppressWarnings("unused")
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
@SuppressWarnings("unchecked")
PayResult payResult = new PayResult((Map<String, String>) msg.obj);
/**
对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
*/
String resultInfo = payResult.getResult();// 同步返回需要验证的信息
String resultStatus = payResult.getResultStatus();
// 判断resultStatus 为9000则代表支付成功
if (TextUtils.equals(resultStatus, "9000")) {
// 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
// Toast.makeText(MyApplication.getContext(), "支付成功", Toast.LENGTH_SHORT).show();
if (onPayClickListener != null) {
onPayClickListener.paySuccess();
}
} else {
// 该笔订单真实的支付结果,需要依赖服务端的异步通知。
// Toast.makeText(MyApplication.getContext(), "支付失败", Toast.LENGTH_SHORT).show();
if (onPayClickListener != null) {
onPayClickListener.payFailure();
}
}
break;
}
default:
break;
}
}
;
};
}
public class BaseEntity {
private int code;
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.learn.agg.R;
public class ExternalFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.pay_external, container, false);
}
}
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="12dp">
<View
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:layout_marginTop="5dp"
android:background="#000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2">
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/payWay_choose">
<RadioButton
android:id="@+id/alipay"
android:layout_width="match_parent"
android:layout_height="65dp"
android:onClick="payV2"
android:drawableLeft="@drawable/alipay"
android:padding="20dp" />
</RadioGroup>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:layout_marginTop="5dp"
android:background="#000" />
</LinearLayout>
</ScrollView>
import android.text.TextUtils;
import java.util.Map;
public class PayResult {
private String resultStatus;
private String result;
private String memo;
public PayResult(Map<String, String> rawResult) {
if (rawResult == null) {
return;
}
for (String key : rawResult.keySet()) {
if (TextUtils.equals(key, "resultStatus")) {
resultStatus = rawResult.get(key);
} else if (TextUtils.equals(key, "result")) {
result = rawResult.get(key);
} else if (TextUtils.equals(key, "memo")) {
memo = rawResult.get(key);
}
}
}
@Override
public String toString() {
return "resultStatus={" + resultStatus + "};memo={" + memo
+ "};result={" + result + "}";
}
/**
* @return the resultStatus
*/
public String getResultStatus() {
return resultStatus;
}
/**
* @return the memo
*/
public String getMemo() {
return memo;
}
/**
* @return the result
*/
public String getResult() {
return result;
}
}
public interface URLInterface {
String BASE_URL = "http://localhost:8080";//测试服服务器
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ImageView
android:layout_width="fill_parent"
android:layout_height="45dp"
android:background="@drawable/msp_demo_title_bg"
android:scaleType="center"
android:src="@drawable/msp_demo_title"
tools:ignore="ContentDescription" />
<fragment
android:id="@+id/fragment"
android:name="com.learn.agg.alipay.ExternalFragment"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
msp_demo_title_bg.png
<application
android:networkSecurityConfig="@xml/network_config"
>
<activity android:name=".act.PayDemoActivity" />
network_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
完结了。
我已经成功实现了,如果你没有成功的话,可以留言,看看是什么问题导致的…
如果你的PayDemoActivity是主活动,请在manifest加上
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>