[CocosCreator]全栈接入阿里支付宝SDK

一.前言

        最近给公司的游戏接入支付宝SDK(小预告:下期接微信SDK),又是焦头烂额的3天(有些人就会问,怎么要3天?首先我是全栈,整个app的支付流程,包括前,后端都是我亲力亲为的).其实现在回看这几天的历程,80%的时间都是花在看官方文档和实例上.这并不是我第一次接入支付宝了,奈何时隔太久,完全没留有一丝印象,导致痛苦的经历又经历一遍.于是乎本文就诞生了,我会把很官方的东西,嚼碎了喂给你吃,同时我自己也再次巩固一遍.

二.开始接入

        1.首先拿到官方的授权三宝:

        appid :这个我不多解释,懂的都懂

        appPrivateKey:应用私钥

        alipayPublicKey:支付宝公钥(这里我啰嗦一句:这个支付宝公钥是拿应用公钥在官网上兑换而来的,兑换而来的兑换而来的......总要的问题说三遍,不是应用公钥)

        2.明确支付流程:(熟读几遍

                (1)客户端点击充值发起支付行动,通过服务器接口获取签名:orderStr,这个是非常机密的,所有会在服务端生成再交给客户端,这个过程服务端会初步生成订单,其中主要包括订单号,金额等

                (2)客户端拿到签名,调用支付宝接口,正式发起支付应用跳转,这里的跳转当然是跳转到支付宝App,大家都熟悉的消费界面,这里没啥好说的

                (3)买家支付操作完成,支付成功的话,服务器会收到支付宝官方的回调,服务器通过回调信息,校验本次支付的有效性,处理对此订单的处理后续工作,比如发送充值所得等.如果买家退出支付,放弃支付,支付失败等情况,服务端是不会收到官方回调.

这是我画的具体流程图,请细品:

        3.客户端代码接入:

                (1)首先CocosCreator需要构建Android工程,然后下载官方的Demo工程,解压,把工具类导入工程,如图:

                (2)然后在 工程的build.gradle添加依赖:如图:

                (3)改写org/cocos2dx/javascript/AppActivity.java,这里我省略了一些没用的干扰方法,只保留支付宝需要的,如图:

/****************************************************************************
 Copyright (c) 2015-2016 Chukong Technologies Inc.
 Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.

 http://www.cocos2d-x.org

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/
package org.cocos2dx.javascript;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.alipay.sdk.app.EnvUtils;
import com.alipay.sdk.app.PayTask;

import org.cocos2dx.javascript.alipay.AuthResult;
import org.cocos2dx.javascript.alipay.PayResult;
import org.cocos2dx.javascript.utils.NetworkUtil;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.lib.Cocos2dxGLSurfaceView;

import java.util.Map;

public class AppActivity extends Cocos2dxActivity {

    private static final int PERMISSION_REQUEST_CODE = 1;

    //支付结果
    public static String alipayResult = null;
    private static final int SDK_PAY_FLAG = 1;
    private static final int SDK_AUTH_FLAG = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // DO OTHER INITIALIZATION BELOW
        SDKWrapper.getInstance().init(this);

       
        //fixme 支付宝 沙箱环境(正式环境要去掉)
        EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);
    }


 


    @SuppressLint("HandlerLeak")
    public static Handler mHandler = new Handler() {
        @SuppressWarnings("unused")
        public void handleMessage(Message msg) {
            System.out.println("msg is " + msg);
            switch (msg.what) {
                case SDK_PAY_FLAG: {
                    @SuppressWarnings("unchecked") PayResult payResult = new PayResult((Map<String, String>) msg.obj);
                    System.out.println("------------->>>>>>payResult is " + payResult.getResult());
                    alipayResult = payResult.getResult().toString();
                    System.out.println("alipayResult is " + alipayResult.toString());
                    //对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
                    String resultInfo = payResult.getResult();// 同步返回需要验证的信息
                    String resultStatus = payResult.getResultStatus();
                    // 判断resultStatus 为9000则代表支付成功
                    if (TextUtils.equals(resultStatus, "9000")) {
                        // 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
                        Toast.makeText(AppActivity.getContext(), "支付成功", Toast.LENGTH_SHORT).show();
                    } else {
                        // 该笔订单真实的支付结果,需要依赖服务端的异步通知。
                        Toast.makeText(AppActivity.getContext(), "支付失败", Toast.LENGTH_SHORT).show();
                    }
                    break;
                }
                case SDK_AUTH_FLAG: {
                    @SuppressWarnings("unchecked") AuthResult authResult = new AuthResult((Map<String, String>) msg.obj, true);
                    //打印支付信息
                    String resultStatus = authResult.getResultStatus();

                    // 判断resultStatus 为“9000”且result_code
                    // 为“200”则代表授权成功,具体状态码代表含义可参考授权接口文档
                    if (TextUtils.equals(resultStatus, "9000") && TextUtils.equals(authResult.getResultCode(), "200")) {
                        // 获取alipay_open_id,调支付时作为参数extern_token 的value
                        // 传入,则支付账户为该授权账户
                        Toast.makeText(AppActivity.getContext(), "授权成功\n" + String.format("authCode:%s", authResult.getAuthCode()), Toast.LENGTH_SHORT).show();
                    } else {
                        // 其他状态值则为授权失败
                        Toast.makeText(AppActivity.getContext(), "授权失败" + String.format("authCode:%s", authResult.getAuthCode()), Toast.LENGTH_SHORT).show();

                    }
                    break;
                }
                default:
                    break;
            }
        }
    };

    /**
     * final String orderMessage
     */
    public static String payOrder(final String orderStr) {
        System.out.println("payOrder is App Activity");
        Runnable payRunnable = () -> {
            PayTask alipay = new PayTask((Activity) AppActivity.getContext());
            Map<String, String> result = alipay.payV2(orderStr, 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();
        System.out.println("alipayResult is " + alipayResult);
        if (alipayResult != null) {
            //返回支付结果信息
            return alipayResult.toString();
        } else {
            //返回空字符串
            return "";
        }
    }
}

        这里我介绍一下吧:

        1沙箱测试是需要onCreate里加上,正式支付当然是要去掉的

//fixme 支付宝 沙箱环境(正式环境要去掉)
EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);

        2由于cocos只能调用静态方法,必须把官方那个代码修改成静态,所以如果你直接用官方是不行的.其中payOrder就是客户端发起支付跳转的接口

        (4)在CocosCreator的代码提供调用入口,毕竟这才是支付的起点,代码如下:

import ConstantSys from "../../common/ConstantSys";
import GameFunctionUtil from "../../utils/GameFunctionUtil";
import SysLog from "../../utils/SysLog";


export default class ServerRechargeDataHandler implements Handler {
    executeOrder(protobufData: any) {
        SysLog.debug(`ServerRechargeDataHandler`);
        if (ConstantSys.Yes == protobufData.result) {

            let payWay = protobufData.payWay; // 支付渠道
            let orderStr = protobufData.orderStr;//订单参数

            SysLog.info(`payWay:${payWay}`);
            SysLog.info(`orderStr:${orderStr}`);

            if (cc.sys.isNative && cc.sys.os === cc.sys.OS_ANDROID) {
                if (1 == payWay) {
                    //支付宝
                    this.alipayOrdering(orderStr);
                }
            }
        } else {
            GameFunctionUtil.instance.showSimpleTips(`${protobufData.msg}`);
        }
    }

    /**
     * 调用支付宝支付接口,发起支付跳转
     * @param orderStr
     * @private
     */
    private alipayOrdering(orderStr) {
        //fixme 这个ok没问题
        const payOrder = jsb.reflection.callStaticMethod(
            "org/cocos2dx/javascript/AppActivity", // Java 类的完整路径
            "payOrder", // Java 方法的名字
            "(Ljava/lang/String;)Ljava/lang/String;", // Java 方法的签名
            orderStr // 第二个参数
        );

        SysLog.info(`payOrder:${payOrder}`);
    }
}

这里大家只需要关注alipayOrdering方法就行,其中参数orderStr从由服务端获取,后面服务端代码会说到!OK,到此整个客户端支付就顺利完成~非常简单~

        4.服务端代码接入(Java版):

                (1)开头我先说说,支付宝官方那个是提供至少两种服务端接入方式的,一种是通用版2.0(还有个3.0我是在某个文档翻到有提到,但是demo给的是2.0,这里我就用2.0),另一种是Easy版.两种版本是有区别的,这里我就忘记,也懒得翻文档.总之:通用版是比Easy版的功能更加全面,只是代码量会多点而已,而Easy版相对简化了很多.

                (2)引入通用版官方依赖:

 <!-- 阿里支付宝SDK-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.39.200.ALL</version>
        </dependency>

                (3)创建各种类:

这里我也说明一下,这里我用到了工厂+策略模式编写,同学们看不懂的不要紧,主要就关注

alipay包里面的4个类就行了.

第一个:AlipayCallbackRequest,回调数据封装类,也就是常用的DTO

package com.zhcj.xzjh.payment.alipay;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 阿里支付回调数据
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AlipayCallbackRequest {
    private String tradeNo;        // 支付宝交易凭证号
    private String outTradeNo;     // 商户订单号
    private String totalAmount;    // 交易金额
    private String buyerId;        // 买家支付宝用户ID
    private String subject;        // 交易名称
    private String tradeStatus;    // 交易状态
    private String gmtPayment;     // 买家付款时间
    private String buyerPayAmount; // 买家付款金额
}

第二个:AlipayTradeStatusEnum,支付订单的状态枚举类

package com.zhcj.xzjh.payment.alipay;

/**
 * 交易状态枚举类
 */
public enum AlipayTradeStatusEnum {

    WAIT_BUYER_PAY("WAIT_BUYER_PAY", "交易创建,等待买家付款", "交易创建", 1),
    TRADE_CLOSED("TRADE_CLOSED", "未付款交易超时关闭,或支付完成后全额退款", "交易关闭", 0),
    TRADE_SUCCESS("TRADE_SUCCESS", "交易支付成功,可退款", "支付成功", 99),
    TRADE_FINISHED("TRADE_FINISHED", "交易结束,不可退款", "交易完成", 100);

    // 枚举参数
    private final String code;
    private final String description;
    private final String triggerCondition;
    private final Integer status;

    // 枚举构造函数
    AlipayTradeStatusEnum(String code, String description, String triggerCondition, Integer status) {
        this.code = code;
        this.description = description;
        this.triggerCondition = triggerCondition;
        this.status = status;
    }

    // 获取交易状态码
    public String getCode() {
        return code;
    }

    // 获取交易状态说明
    public String getDescription() {
        return description;
    }

    // 获取触发条件描述
    public String getTriggerCondition() {
        return triggerCondition;
    }
    // 获取状态编码
    public Integer getStatus() {
        return status;
    }

    // 根据交易状态码获取枚举实例
    public static AlipayTradeStatusEnum fromCode(String code) {
        for (AlipayTradeStatusEnum status : AlipayTradeStatusEnum.values()) {
            if (status.getCode().equals(code)) {
                return status;
            }
        }
        throw new IllegalArgumentException("无效的交易状态代码: " + code);
    }
}

第三个:MyAlipayConfig,支付宝配置类,上面提到的appid,私钥,公钥都在这里

package com.zhcj.xzjh.payment.alipay;

import com.alipay.api.AlipayConfig;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Setter
@Configuration
@ConfigurationProperties(prefix = "payment.alipay")
public class MyAlipayConfig {

    private String gatewayHost;
    private String appId;
    private String merchantPrivateKey;
    private String alipayPublicKey;


    @Bean
    public AlipayConfig aliPayConfig() {
        AlipayConfig alipayConfig = new AlipayConfig();
        alipayConfig.setServerUrl(gatewayHost);
        alipayConfig.setAppId(appId);
        alipayConfig.setPrivateKey(merchantPrivateKey);
        alipayConfig.setFormat("json");
        alipayConfig.setAlipayPublicKey(alipayPublicKey);
        alipayConfig.setCharset("UTF-8");
        alipayConfig.setSignType("RSA2");
        return alipayConfig;
    }
}

最后一个重头戏:

package com.zhcj.xzjh.payment.alipay;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.diagnosis.DiagnosisUtils;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zhcj.xzjh.common.Constants;
import com.zhcj.xzjh.common.RedisConstants;
import com.zhcj.xzjh.dao.UserBasicDao;
import com.zhcj.xzjh.dao.UserRechargeDao;
import com.zhcj.xzjh.enumData.PaymentWayEnum;
import com.zhcj.xzjh.excelModel.GameRechargeConfigData;
import com.zhcj.xzjh.exception.RechargeRunTimeException;
import com.zhcj.xzjh.model.UserBasic;
import com.zhcj.xzjh.model.UserRecharge;
import com.zhcj.xzjh.payment.GamePaymentFactory;
import com.zhcj.xzjh.payment.GamePaymentHandler;
import com.zhcj.xzjh.util.PublicUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class AlipayHandler implements GamePaymentHandler {
    @Value("${payment.alipay.notify-url}")
    private String notifyUrl;

    @Autowired
    private AlipayConfig aliPayConfig;
    @Autowired
    private UserRechargeDao userRechargeDao;
    @Autowired
    private UserBasicDao userBasicDao;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public String ordering(String userId, String code) throws AlipayApiException {

        //验证商品是否合法
        GameRechargeConfigData gameRechargeConfigData = redisTemplate.<String, GameRechargeConfigData>opsForHash().get(RedisConstants.Redis_Hash_GameRechargeConfigData, code);
        if (gameRechargeConfigData == null) {
            throw new RechargeRunTimeException("阿里支付宝支付处理失败:{} 对应的商品不存在", code);
        }

        //获取用户的uin码
        QueryWrapper<UserBasic> userBasicQueryWrapper = new QueryWrapper<>();
        userBasicQueryWrapper.select("uin").eq("id", userId);
        UserBasic userBasic = userBasicDao.selectOne(userBasicQueryWrapper);
        if (userBasic == null) {
            throw new RechargeRunTimeException("阿里支付宝支付处理失败:{} 对应的玩家不存在", userId);
        }

        String uin = userBasic.getUin();
        String outTradeNo = PublicUtil.getTimesKey();

        //创建并保存订单信息
        UserRecharge userRecharge = new UserRecharge();
        userRecharge.setUserId(userId).setCode(code).setOutTradeNo(outTradeNo).setPayWay(PaymentWayEnum.Ali.getPayWay()).setSend(Constants.NO).setState(AlipayTradeStatusEnum.WAIT_BUYER_PAY.getStatus());
        userRechargeDao.insert(userRecharge);

        //根据订单数据,生成客户端所需的签名
        AlipayClient alipayClient = new DefaultAlipayClient(aliPayConfig);
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        request.setNotifyUrl(notifyUrl);//支付成功回调路径
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setOutTradeNo(outTradeNo);//订单号
        model.setTotalAmount(gameRechargeConfigData.getAmount());//订单金额
        model.setSubject(gameRechargeConfigData.getSubject());//订单名字
        model.setPassbackParams(uin);// 设置公用回传参数
        request.setBizModel(model);
        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
        String orderStr = response.getBody();
        if (!response.isSuccess()) {
            // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
            String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
            throw new AlipayApiException("支付宝签名失败:{}", diagnosisUrl);
        }
        return orderStr;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        GamePaymentFactory.register(PaymentWayEnum.Ali.getPayWay(), this);
    }
}

这里就是生成客户端所需签名参数的地方

然后附上用户支付成功后,支付宝服务器回调的接口,必须是Post类型

package com.zhcj.xzjh.controller;

import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import com.google.common.collect.Maps;
import com.zhcj.xzjh.enumData.PaymentWayEnum;
import com.zhcj.xzjh.payment.alipay.AlipayCallbackRequest;
import com.zhcj.xzjh.payment.alipay.AlipayTradeStatusEnum;
import com.zhcj.xzjh.service.UserRechargeService;
import com.zhcj.xzjh.util.GsonUtil;
import com.zhcj.xzjh.util.HttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/payment")
public class PaymentController {

    @Value("${game.zhcj_acus_rechargeOrder_url}")
    private String ZHCJ_ACUS_RECHARGEORDER_URL;

    @Value("${payment.alipay.alipay-public-key}")
    private String alipayPublicKey;
    @Autowired
    private UserRechargeService userRechargeService;

    /**
     * 支付宝回调
     */
    @PostMapping("/alipayCallBack")
    public String alipayCallBack(HttpServletRequest request) throws Exception {

        // 构建 params Map
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            params.put(name, request.getParameter(name));
        }

        //调用SDK验证签名
        boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);

        if (!signVerified) {
            log.error("验签失败");
            return "error";
        }

        String tradeStatus = request.getParameter("trade_status");
        if (AlipayTradeStatusEnum.TRADE_SUCCESS.getCode().equals(tradeStatus) || AlipayTradeStatusEnum.TRADE_FINISHED.getCode().equals(tradeStatus) || AlipayTradeStatusEnum.TRADE_CLOSED.getCode().equals(tradeStatus)) {

            // 支付宝验签
            // 将 params 转换为 AlipayCallbackRequest 对象
            AlipayCallbackRequest callbackRequest = new AlipayCallbackRequest();
            callbackRequest.setTradeNo(params.get("trade_no"));
            callbackRequest.setOutTradeNo(params.get("out_trade_no"));
            callbackRequest.setTotalAmount(params.get("total_amount"));
            callbackRequest.setBuyerId(params.get("buyer_id"));
            callbackRequest.setSubject(params.get("subject"));
            callbackRequest.setTradeStatus(params.get("trade_status"));
            callbackRequest.setGmtPayment(params.get("gmt_payment"));
            callbackRequest.setBuyerPayAmount(params.get("buyer_pay_amount"));

            // 调用Service层业务逻辑处理
            userRechargeService.processAlipayCallback(callbackRequest);

            String uin = params.get("passback_params");//我要的玩家uin

            //统计用户充值数据
            Map<String, Object> resultMap = Maps.newHashMap();
            resultMap.put("uin", uin);
            resultMap.put("amount", callbackRequest.getBuyerPayAmount());
            resultMap.put("payWay", PaymentWayEnum.Ali.getPayWay());
            String json = GsonUtil.objectToJson(resultMap);
            HttpClientUtils.httpPostJSON(ZHCJ_ACUS_RECHARGEORDER_URL, json);
        }
        return "success";
    }
}

到此,整个支付流程就基本完成了,这里只是给大家展示支付的核心接口,实际上支付宝还有其他接口,比如:退款,查询等,这些就不难,这里就不说了.

三.结尾

        本次接入就圆满结束,如果大家想看微信支付,也给我留言,我看热度酌情公布.最后打个小广告:

 欢迎喜欢或者从事CocosCreator开发的小伙伴请加入我的大家庭CocosCreator游戏开发Q群:26855530

        咱们下期再见,好人好梦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Artemis丶月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值