一 .前置工作
1.首先,前置工作肯定要先搭建支付宝沙盒环境。打开支付宝的开发者平台,地址:支付宝开放平台,先登录账号,如下图
2.接下来就点击旁边的“控制台”,进入控制台页面,然后鼠标滚轮滑倒最下面,下图所示
点击“开发工具推荐”中的沙箱,进入沙箱调试页面,下图所示
接下来
然后就要下载生成密钥的工具,安装软件就不用兄弟教学了吧,你可以的
3.然后打开“支付宝开放平台密钥工具”,点击生成密钥
最后生成页面
回到支付宝开放平台页面操作,复制公钥进去点击保存
4.然后下载支付宝沙盒版本去到要操作的设备中去,然后登录
最后打开沙箱版支付宝,登录买家账号,注意:一定要登录买家账号
5.到这一步,前置工作已经全部完成,已经离成功不远了
二.代码层面
1.创建一个新的项目(这个就不用教了)
我这里就只做一个简单得页面算了,上代码!
创建项目之后在app模块中的build.gradle中加入支付的依赖
implementation 'com.alipay.sdk:alipaysdk-android:+@aar'
2.布局页面 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rc_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
3.接下来左一个列表视图需要的适配器 RcAdapter
public class RcAdapter extends RecyclerView.Adapter<RcAdapter.RcViewHolder> {
private List<RcBean> list = new ArrayList<>();
public SetItemOnClickListener listener;
public RcAdapter( List<RcBean> list) {
this.list = list;
}
@Override
public RcViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new RcViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_rc, parent, false));
}
@Override
public void onBindViewHolder(RcViewHolder holder, int position) {
holder.tv_name.setText(list.get(position).getName());
holder.tv_money.setText(String.valueOf(list.get(position).getMoney()));
holder.itemView.setOnClickListener(v -> {
listener.setItemOnClickListener(position);
});
}
@Override
public int getItemCount() {
return list.size();
}
class RcViewHolder extends RecyclerView.ViewHolder {
TextView tv_name, tv_money;
public RcViewHolder(View v) {
super(v);
tv_name = v.findViewById(R.id.tv_name);
tv_money = v.findViewById(R.id.tv_money);
}
}
public void setOnClickListener(SetItemOnClickListener listener) {
this.listener = listener;
}
public interface SetItemOnClickListener {
void setItemOnClickListener(int position);
}
}
4.对应的数据类 RcBean
public class RcBean {
private String name;
private BigDecimal money;
public RcBean(String name, BigDecimal money) {
this.name = name;
this.money = money;
}
public RcBean() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getMoney() {
return money;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
}
5.列表 item对应的布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/l1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="name"/>
<TextView
android:id="@+id/tv_money"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="money"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintTop_toBottomOf="@id/l1"
android:layout_marginTop="5dp"
android:background="@color/black"/>
</androidx.constraintlayout.widget.ConstraintLayout>
6.然后复制官方给的demo中的三个类
第一个 OrderInfoUtil2_0
public class OrderInfoUtil2_0 {
/**
* 构造授权参数列表
*
* @param pid
* @param app_id
* @param target_id
* @return
*/
public static Map<String, String> buildAuthInfoMap(String pid, String app_id, String target_id, String orderInfo,boolean rsa2) {
SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator(1);
Map<String, String> keyValues = new HashMap<String, String>();
// 商户签约拿到的app_id,如:2013081700024223
keyValues.put("app_id", app_id);
// 商户签约拿到的pid,如:2088102123816631
keyValues.put("pid", pid);
// 服务接口名称, 固定值
keyValues.put("apiname", "");
// 服务接口名称, 固定值
keyValues.put("methodname", "alipay.open.auth.sdk.code.get");
// 商户类型标识, 固定值
keyValues.put("app_name", "mc");
// 业务类型, 固定值
keyValues.put("biz_type", "openservice");
// 产品码, 固定值
keyValues.put("product_id", "APP_FAST_LOGIN");
// 授权范围, 固定值
keyValues.put("scope", "kuaijie");
// 商户唯一标识,如:kkkkk091125
keyValues.put("target_id", target_id);
// 授权类型, 固定值
keyValues.put("auth_type", "AUTHACCOUNT");
// 签名类型
keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");
return keyValues;
}
/**
* 构造支付订单参数列表
*/
public static Map<String, String> buildOrderParamMap(String app_id, String total_amount,boolean rsa2) {
Map<String, String> keyValues = new HashMap<String, String>();
keyValues.put("app_id", app_id);
keyValues.put("biz_content", "{\"timeout_express\":\"30m\",\"product_code\":\"QUICK_MSECURITY_PAY\",\"total_amount\":\"" + total_amount + "\",\"subject\":\"1\",\"body\":\"我是测试数据\",\"out_trade_no\":\"" + getOutTradeNo() + "\"}");
keyValues.put("charset", "utf-8");
keyValues.put("method", "alipay.trade.app.pay");
keyValues.put("sign_type", rsa2 ? "RSA2" : "RSA");
keyValues.put("timestamp", "2024-10-21 12:00:00");
keyValues.put("version", "1.0");
return keyValues;
}
/**
* 构造支付订单参数信息
*
* @param map
* 支付订单参数
* @return
*/
public static String buildOrderParam(Map<String, String> map) {
List<String> keys = new ArrayList<String>(map.keySet());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.size() - 1; i++) {
String key = keys.get(i);
String value = map.get(key);
sb.append(buildKeyValue(key, value, true));
sb.append("&");
}
String tailKey = keys.get(keys.size() - 1);
String tailValue = map.get(tailKey);
sb.append(buildKeyValue(tailKey, tailValue, true));
return sb.toString();
}
/**
* 拼接键值对
*
* @param key
* @param value
* @param isEncode
* @return
*/
private static String buildKeyValue(String key, String value, boolean isEncode) {
StringBuilder sb = new StringBuilder();
sb.append(key);
sb.append("=");
if (isEncode) {
try {
sb.append(URLEncoder.encode(value, "UTF-8"));
} catch (UnsupportedEncodingException e) {
sb.append(value);
}
} else {
sb.append(value);
}
return sb.toString();
}
/**
* 对支付参数信息进行签名
*
* @param map
* 待签名授权信息
*
* @return
*/
public static String getSign(Map<String, String> map, String rsaKey, boolean rsa2) {
List<String> keys = new ArrayList<String>(map.keySet());
// key排序
Collections.sort(keys);
StringBuilder authInfo = new StringBuilder();
for (int i = 0; i < keys.size() - 1; i++) {
String key = keys.get(i);
String value = map.get(key);
authInfo.append(buildKeyValue(key, value, false));
authInfo.append("&");
}
String tailKey = keys.get(keys.size() - 1);
String tailValue = map.get(tailKey);
authInfo.append(buildKeyValue(tailKey, tailValue, false));
String oriSign = SignUtils.sign(authInfo.toString(), rsaKey, rsa2);
String encodedSign = "";
try {
encodedSign = URLEncoder.encode(oriSign, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "sign=" + encodedSign;
}
/**
* 要求外部订单号必须唯一。
* @return
*/
private 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;
}
}
第二个 SignUtils
public class SignUtils {
private static final String ALGORITHM = "RSA";
private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
private static final String DEFAULT_CHARSET = "UTF-8";
private static String getAlgorithms(boolean rsa2) {
return rsa2 ? SIGN_SHA256RSA_ALGORITHMS : SIGN_ALGORITHMS;
}
public static String sign(String content, String privateKey, boolean rsa2) {
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
Base64.decode(privateKey,Base64.DEFAULT));
KeyFactory keyf = KeyFactory.getInstance(ALGORITHM);
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
java.security.Signature signature = java.security.Signature
.getInstance(getAlgorithms(rsa2));
signature.initSign(priKey);
signature.update(content.getBytes(DEFAULT_CHARSET));
byte[] signed = signature.sign();
return Base64.encodeToString(signed, Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
第三个SnowflakeIdGenerator
public class SnowflakeIdGenerator {
private static final long START_EPOCH = 1609459200000L; // 2021-01-01 00:00:00
private static final long MACHINE_ID_BITS = 5L;
private static final long SEQUENCE_BITS = 15L;
private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
private long machineId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long machineId) {
if (machineId > MAX_MACHINE_ID || machineId < 0) {
throw new IllegalArgumentException("Machine ID can't be greater than " + MAX_MACHINE_ID + " or less than 0");
}
this.machineId = machineId;
}
public synchronized String nextId() {
long timestamp = System.currentTimeMillis();
// 检查时间戳是否回退
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
}
if (lastTimestamp == timestamp) {
// 如果在同一毫秒内,增加序列号
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) {
// 序列号达到最大值,需要等待下一毫秒
timestamp = waitNextMillis(lastTimestamp);
}
} else {
// 不同毫秒,重置序列号
sequence = 0L;
}
lastTimestamp = timestamp;
long uniqueId = ((timestamp - START_EPOCH) << (MACHINE_ID_BITS + SEQUENCE_BITS)) | (machineId << SEQUENCE_BITS) | sequence;
Log.d("uniqueId = ", String.valueOf(uniqueId));
String idStr = String.valueOf(uniqueId).substring(0, 18);
return idStr;
}
private long waitNextMillis(long lastTimestamp) {
long timestamp;
try {
while ((timestamp = System.currentTimeMillis()) <= lastTimestamp) {
Thread.sleep(1);
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for next millisecond", e);
}
return timestamp;
}
private String toBase36(long id) {
return Long.toString(id, 36);
}
}
最后给出MainActivity.java
public class MainActivity extends AppCompatActivity {
public static final String APPID = "9021000141649348";
//私钥
public static final String RSA2_PRIVATE = "MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC4Stt4NtX1bazrqznm/NCLDHh54X+upPfWpZkRDwe/1BNAqR094NaD+2OfrEZZ5sWU5K/NMM2DSqnJ7qSIUpKira+0gh/0Iadtj+MyH17V5zBCsHHnPD1h+fuJfY8z6zUEEkPbObzWydephd1KP+6vGuHhOe9EIrSJWGD6VddfKEFPtFFFdNEUVHXi+cURPojRwUhz5ZZkrH5jJWL0n9COd3eYQ3hict/7gWWvLtRGcvsI/WkHMHnzWLO/X/LTSN2q+UIIb2zT1udN+o4Rl2GHUqITWR310WULNNFcUN9ilPa8F84XxjmTFn9B28Oj7we65cvFNTPllazaSlAVUXM7AgMBAAECggEBAJkzIV69tv9fPPBsVqX+ZB4zL7OiEVJNMPnuj1/u7rI4yghmjThzP+BXimmh/JmfjYBI2xvoBe+ukZacG4p3mg5B9F5KDeYUU+pwQp3YFi4bEbNemlFvcleQR9nz1qeCT4Ai7uW/CYxEHlN+RIcEvpYx8rQYRR6O+yVxoqx3z6H5m3CprcGbS3Qe5JBdD55WiWaMmuWS3pWFBpmtetNv6qGAvxgocXcLaAQ/6XDhOBeMFq68fFKdZqd4OYMWI2STzz9sR0oX8OO/0unFqGWXViclqPnJ/Z8nFTLFWliyJyGIMafl26b0Rx2HM3692HUu1J7ahurblE0iu/6tdPcNnwECgYEA4XJy9joXdV3d2RwEgzsKbJjtMfvzZudRLP8z4os9Ub2ydyZaP9rBjd7pY4+LmPFIzVILUcLz7R84CKyW/aG+Lv1B3o7fxESkWnfP/EyEJgx8zfEHLobJW/2X3F4qIkv4xbmcOPn1kk0Lkw8uRWSrfopTJsmVIwk46aIS9fvweZkCgYEA0USbzxMkKEo5v4P2PBG4Ul4qngOlx4vs5DrfvLReEZEvm/U4pQW3CgMk/QHj2POOjpNLNOSh7d4h3dgkuflFRRsN5K94rQP0RFCGW7w0aIWbL1WU0FcGliBcAw+vf5Ijzr6OgxZfSSKjnZpsue27CBxG+5GuDc//yvT1LF3In/MCgYEAtknzOLLKCwVl/0nPQEj56ctRZywgqCD7mxWS32fUogZvijYBnUYFYPBP6EfGCVl3k0T2kBrBXwbyKNlckSI6BAaVPx5pQmp6NghQrOE1rQpF08NDGlSz9eS76Nxe1zJ0qXOmJM+/x5byd+s7b7Kxk/TGvUMbiqPHV+nLyQf4bmkCgYEA0S6JqLZzgCqiCwR30JfN7dffNdBjmFIQXBtVpqWNGnZMZtL66koKK2H1SUroXOco6u/lT1vzWXif1cfG/ndjfK6MdrnIIPpA40Cy7WP15z0WYHxlotQ66zoxf4XgYd7NGE522iY03UBY2KOSZ1BxkqvhcHqwx3HROSkfIlgkwW0CgYEAxzGTFYu/jm1EfzBY75VyO1aaOqKTgOFUXvG1sh9P3zuOZqKZA2p5j27Stnd0auUaLb8/LhRp8OheJsKMso8JDXw8DaYvWqZLdT+sHthT57Wu3MfmKZZ9IGd9rxT3Q/JsuyyGlnV9ZRdUrPpwlwDNNI48uwRxuGTEqGHZmLTaGZU=";
private RecyclerView rc_view;
//创建一个List 原始数据源
private List<RcBean> list = new ArrayList<>();
//渲染用的列表
private List<RcBean> newList = new ArrayList<>();
//实例化Adapter
RcAdapter adapter = new RcAdapter(newList);
public static final int SDK_PAY_FLAG = 0x200;
private static final String TAG = "====日志====";
private final Handler mHandler = new Handler() {
@SuppressLint("HandlerLeak")
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SDK_PAY_FLAG:
Map<String, String> resultMap = (Map<String, String>) (Map<String, String>)msg.obj;
if (resultMap.get("resultStatus").equals("9000")) {
Toast.makeText(MainActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
}
if (resultMap.get("resultStatus").equals("6001")) {
Toast.makeText(MainActivity.this, resultMap.get("memo").toString(), Toast.LENGTH_SHORT).show();
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//进入沙盒环境
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
setContentView(R.layout.activity_main);
//初始化控件
initView();
//初始化加数据
setData();
//点击方法
setOnClickListener();
}
private void setData() {
/*这里用两个List另有他用,有点经验的应该知道,新手朋友造写吧*/
list.clear();
newList.clear();
RcBean rcBean1 = new RcBean("斐济杯", new BigDecimal(1000.00));
RcBean rcBean2 = new RcBean("刀魔", new BigDecimal(999.00));
list.add(rcBean1);
list.add(rcBean2);
newList.addAll(list);
}
private void setOnClickListener() {
adapter.setOnClickListener(position -> {
Log.d(TAG, "newList.get(position).getMoney(): " + newList.get(position).getMoney());
// TODO: 2024/10/23 这里写支付代码模块
Map<String, String> params = OrderInfoUtil2_0.buildOrderParamMap(APPID, String.valueOf(newList.get(position).getMoney()), true);
String orderParam = OrderInfoUtil2_0.buildOrderParam(params);
String privateKey = RSA2_PRIVATE;
String sign = OrderInfoUtil2_0.getSign(params, privateKey, true);
final String orderInfo = orderParam + "&" + sign;
Log.d(TAG, "orderInfo: " + orderInfo);
Runnable payRunnable = new Runnable() {
@Override
public void run() {
AuthTask alipay = new AuthTask(MainActivity.this);
Map<String, String> result = alipay.authV2(orderInfo, true);
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
Thread payThread = new Thread(payRunnable);
payThread.start();
});
}
private void initView() {
rc_view = findViewById(R.id.rc_view);
rc_view.setLayoutManager(new LinearLayoutManager(this));
rc_view.setAdapter(adapter);
}
}
三.测试效果
至此整个项目已经全部完成了,启动测试,看效果
当前项目已经放入gitee有意者可自取 地址:无敌最俊朗/csdn-支付宝沙箱