HOME> 世界杯谁是冠军> 真 · 保姆级支付宝沙盒支付教程(本人遇坑多次总结)
{$vo.文章标题}
{$vo.文章标题}

真 · 保姆级支付宝沙盒支付教程(本人遇坑多次总结)

admin
966

一 .前置工作

1.首先,前置工作肯定要先搭建支付宝沙盒环境。打开支付宝的开发者平台,地址:支付宝开放平台,先登录账号,如下图

2.接下来就点击旁边的“控制台”,进入控制台页面,然后鼠标滚轮滑倒最下面,下图所示

点击“开发工具推荐”中的沙箱,进入沙箱调试页面,下图所示

接下来

然后就要下载生成密钥的工具,安装软件就不用兄弟教学了吧,你可以的

3.然后打开“支付宝开放平台密钥工具”,点击生成密钥

最后生成页面

回到支付宝开放平台页面操作,复制公钥进去点击保存

4.然后下载支付宝沙盒版本去到要操作的设备中去,然后登录

最后打开沙箱版支付宝,登录买家账号,注意:一定要登录买家账号

5.到这一步,前置工作已经全部完成,已经离成功不远了

二.代码层面

1.创建一个新的项目(这个就不用教了)

我这里就只做一个简单得页面算了,上代码!

创建项目之后在app模块中的build.gradle中加入支付的依赖

implementation 'com.alipay.sdk:alipaysdk-android:+@aar'

2.布局页面 activity_main.xml

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">

android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@+id/rc_view"/>

3.接下来左一个列表视图需要的适配器 RcAdapter

public class RcAdapter extends RecyclerView.Adapter {

private List list = new ArrayList<>();

public SetItemOnClickListener listener;

public RcAdapter( List 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对应的布局

android:layout_width="match_parent"

android:layout_height="wrap_content"

xmlns:app="http://schemas.android.com/apk/res-auto">

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">

android:id="@+id/tv_name"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="name"/>

android:id="@+id/tv_money"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="money"/>

android:layout_width="match_parent"

android:layout_height="1dp"

app:layout_constraintTop_toBottomOf="@id/l1"

android:layout_marginTop="5dp"

android:background="@color/black"/>

6.然后复制官方给的demo中的三个类

第一个 OrderInfoUtil2_0

public class OrderInfoUtil2_0 {

/**

* 构造授权参数列表

*

* @param pid

* @param app_id

* @param target_id

* @return

*/

public static Map buildAuthInfoMap(String pid, String app_id, String target_id, String orderInfo,boolean rsa2) {

SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator(1);

Map keyValues = new HashMap();

// 商户签约拿到的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 buildOrderParamMap(String app_id, String total_amount,boolean rsa2) {

Map keyValues = new HashMap();

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 map) {

List keys = new ArrayList(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 map, String rsaKey, boolean rsa2) {

List keys = new ArrayList(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 list = new ArrayList<>();

//渲染用的列表

private List 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 resultMap = (Map) (Map)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 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 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-支付宝沙箱