ios内购支付-支付宝APP支付提现

文章目录


前言

这里用的是thinkphp5,但是支付这个其他框架或版本也可以参考,只需要改下写法即可

一、IOS内购支付(ios订单生成自己写逻辑即可)

在苹果内购支付中第一步先生成一个内部订单,然后把订单信息给到IOS客户端,内购支付只要是回调校验票据出路,这个票据会返给IOS客户端,让客户端把票据及订单信息一起传过来,注意订单号唯一即可

1.支付回调票据校验controller

php 复制代码
public function payResult()
{
     //苹果内购的验证收据 orders模型换成自己的订单模型,字段换成自己的订单字段即可
     $receipt_data = $this->request->post('receipt-data');//票据
     $orderSn = $this->request->post('order_sn');//订单号
     // 验证支付状态
     $result = (new IosCheckServer())->payMoneyCheck($receipt_data, $orderSn);
     if($result['status']){
         $this->success('支付成功');
     }else{
         $this->error($result['message']);
     }
 }

1.支付回调票据校验server

php 复制代码
/**
     * @param $receipt_data
     * @param $orderSn
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * @throws \think\exception\PDOException
     * ios充值消费燃币支付回调票据校验
     */
    public function payMoneyCheck($receipt_data, $orderSn)
    {
        //苹果内购的验证收据 orders模型换成自己的订单模型,字段换成自己的订单字段即可
        $orderinfo = Orders::where(['order_sn' => $orderSn])->find();
        if($orderinfo){
            if($orderinfo['pay_status'] == Orders::PAY_STATUS_PAID){
                return ['status' => false,'message' => '订单已成功支付,请勿重复发起申请'];
            }
        }else{
            return ['status' => false,'message'=>'订单异常'];
        }
        // 验证支付状态
        $result = $this->validate_apple_pay($receipt_data);
        if($result['status']){
            /*业务逻辑*/
            Db::startTrans();
            try {
                $orderinfo->pay_status = Orders::PAY_STATUS_PAID;
                $orderinfo->paid_at = time();
                $orderinfo->save();
                /*处理自己内部业务逻辑*/
                /*处理自己内部业务逻辑*/
                /*处理自己内部业务逻辑*/
                Db::commit();
            } catch (\Exception $e) {
                Db::rollback();
                return ['status' => false,'message' => '支付失败,请联系管理员'];
            }
            return ['status' => true,'message' => '支付成功'];
        }else{
            return ['status' => false,'message' => $result['message']];
        }
    }
	/**
     * @param $receipt_data
     * @param $orderinfo
     * @return array
     * @throws \think\exception\PDOException
     * 校验票据
     */
    public function validate_apple_pay($receipt_data)
    {
        // 验证参数
        if (strlen($receipt_data) < 20){
            return ['status' => false, 'message' => '非法参数'];
        }
        // 请求验证
        $html = $this->acurl($receipt_data);
        $data = json_decode($html,true);
        // 如果是沙盒数据 则验证沙盒模式
        if($data['status'] == '21007'){
            // 请求验证
            $html = $this->acurl($receipt_data, 1);
            $data = json_decode($html,true);
            $data['sandbox'] = '1';
        }

        //找出时间最大的数组
        $receiptitem = $data['receipt']['in_app'];
        //这个是排序的方法  下面回帖出来 苹果会把这个会员往期的订单信息全部返回,需要找出来最近的那一组信息
        $item = $this->arraySort($receiptitem,'purchase_date','desc');
        //判断一下过期时间 延长会员时间
        $orderThird = $item['transaction_id'];                //本次订阅的订单号
        $orderThirdFirst = $item['original_transaction_id'];  //这个是原始订单号
        if($orderThird == $orderThirdFirst){                  //首次订阅 两个相等
            return ['status' => true,'message' => '支付成功'];
        }else{
            //判断过期时间和当前时间比较   expires_date_ms是毫秒单位
            if($item['expires_date_ms']/1000 > time()){
                return ['status' => true,'message' => '支付成功'];
            }else{
                //过期处理  票据失效
                return ['code' => false,'message' => '票据失效,请联系管理员'];
            }
        }
    }

	/**
     * @param $receipt_data
     * @param int $sandbox
     * @return bool|string
     * 数据校验
     */
    public function acurl($receipt_data, $sandbox = 0)
    {
        /**
         * 21000 App Store不能读取你提供的JSON对象
         * 21002 receipt-data域的数据有问题
         * 21003 receipt无法通过验证
         * 21004 提供的shared secret不匹配你账号中的shared secret
         * 21005 receipt服务器当前不可用
         * 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
         * 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
         * 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
         */
        //小票信息
        $POSTFIELDS = array("receipt-data" => $receipt_data);
        $POSTFIELDS = json_encode($POSTFIELDS);

        $url_buy     = "https://buy.itunes.apple.com/verifyReceipt";//正式购买地址
        $url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";//沙盒购买地址
        $url = $sandbox ? $url_sandbox : $url_buy;

        //curl - post
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS);
        $result = curl_exec($ch);
        curl_close($ch);
        return $result;
    }

    /**
     * @param $arr
     * @param $key
     * @param string $type
     * @return mixed
     * 查找最新数据
     */
    public static function arraySort($arr,$key,$type='asc')
    {
        $keyArr = []; // 初始化存放数组将要排序的字段值
        foreach ($arr as $k => $v) {
            $keyArr[$k] = $v[$key]; // 循环获取到将要排序的字段值
        }
        if ($type == 'asc') {
            asort($keyArr); // 排序方式,将一维数组进行相应排序
        } else {
            arsort($keyArr);
        }
        foreach ($keyArr as $k => $v) {
            $newArray[$k] = $arr[$k]; // 循环将配置的值放入响应的下标下
        }
        $newArray = array_merge($newArray); // 重置下标
        return $newArray[0]; // 数据返回
    }

二、安卓APP支付宝支付

前期准备工作:

  1. 获取 App ID 和 商户私钥
  2. 申请并下载支付宝公钥证书和应用证书
  3. 如果站点有ssl更好,没有的话可以下载一个cacert.pem证书放到配置文件对应的地址里面,https://curl.se/docs/caextract.html
  4. 完成之后修改一下php.ini的配置curl.cainfo = /etc/pki/tls/certs/cacert.pem
    这里的地址时我按照原配置文件直接用的,放其他地方的话只要保证配置文件里面一致即可,支付宝的证书我是直接放项目里面的,引用的时候保证路径没问题即可

1.生成订单返回支付宝字符串(用于app拉起支付宝,这里用的是证书模式)

controller方法(订单逻辑部分可以自己处理)

php 复制代码
	/**
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * 会员订单创建 - android
     */
    public function memberOrderByAndroid()
    {
        /**
         * 生成订单数据
         */
        $result = [];
        Db::startTrans();
        try {
            $model = new Orders($arr);
            $res = $model->save($arr);
            if ($res === false){
                Db::rollback();
                $this->error('订单创建失败');
            }
            $server = new AlipayServer();
            //订单号,金额,回调地址
            $res = $server->payMemberOrder($model->order_sn, number_format($model->amount, 2), '****/api/v1/member/notify');
            if ($res['code']){
                $result['orderStr'] = $res['data'];
            }
            Db::commit();
        }catch (\Exception $exception){
            Db::rollback();
            $this->error('订单创建失败');
        }
        $this->success('操作成功', $result);
    }

2.生成订单返回支付宝字符串server方法

php 复制代码
	/**
     * @param $out_trade_no 订单号
     * @param $total_amount 金额
     * @param $notify_url 回调地址
     * @return array
     * 燃币充值 - 安卓 - 支付宝
     */
    public function payMemberOrder($out_trade_no, $total_amount, $notify_url)
    {
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v2/aop/AopCertClient.php';
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v2/aop/request/AlipayTradeAppPayRequest.php';

        /** 初始化 **/
        $aop = new \AopCertClient;
        /** 支付宝网关 **/
        $aop->gatewayUrl = "https://openapi.alipay.com/gateway.do";
        /** 应用id,如何获取请参考:https://opensupport.alipay.com/support/helpcenter/190/201602493024 **/
        $aop->appId = config('alipay.app_id');
        /** 密钥格式为pkcs1,如何获取私钥请参考:https://opensupport.alipay.com/support/helpcenter/207/201602469554  **/
        $aop->rsaPrivateKey = trim(config('alipay.private_key'));
        /** 应用公钥证书路径,下载后保存位置的绝对路径  **/
        $appCertPath = __PUBLIC__ . '/cert/appCertPublicKey_2021004170664201.crt';
        /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/
        $alipayCertPath = __PUBLIC__ . '/cert/alipayCertPublicKey_RSA2.crt';
        /** 支付宝根证书路径,下载后保存位置的绝对路径 **/
        $rootCertPath = __PUBLIC__ . '/cert/alipayRootCert.crt';
        /** 设置签名类型 **/
        $aop->signType= "RSA2";
        /** 设置请求格式,固定值json **/
        $aop->format = "json";
        /** 设置编码格式 **/
        $aop->charset= "utf-8";
        /** 调用getPublicKey从支付宝公钥证书中提取公钥 **/
        $aop->alipayrsaPublicKey = $aop->getPublicKey($alipayCertPath);
        /** 是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内 **/
        $aop->isCheckAlipayPublicCert = true;
        /** 调用getCertSN获取证书序列号 **/
        $aop->appCertSN = $aop->getCertSN($appCertPath);
        /** 调用getRootCertSN获取支付宝根证书序列号 **/
        $aop->alipayRootCertSN = $aop->getRootCertSN($rootCertPath);
        /** 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay **/
        $request = new \AlipayTradeAppPayRequest();
        /** 设置业务参数 **/
        $request->setBizContent("{" .
            /**  商户订单号,商户自定义,需保证在商户端不重复,如:20200612000001 **/
            "\"out_trade_no\":\"{$out_trade_no}\"," .
            /** 销售产品码,固定值:QUICK_MSECURITY_PAY **/
            "\"product_code\":\"QUICK_MSECURITY_PAY\"," .
            /** 订单金额,精确到小数点后两位 **/
            "\"total_amount\":\"{$total_amount}\"," .
            /** 订单标题(可自定义) **/
            "\"subject\":\"会员充值\"," .
            /** 订单描述(可自定义) **/
            "\"body\":\"会员充值\"" .
            "}");
        /** 异步通知地址,以http或者https开头的,商户外网可以post访问的异步地址,用于接收支付宝返回的支付结果,如果未收到该通知可参考该文档进行确认:https://opensupport.alipay.com/support/helpcenter/193/201602475759 **/
        $request->setNotifyUrl($notify_url);
        try {
            /** 调用SDK生成支付链接,可在浏览器打开链接进入支付页面 **/
            $result = $aop->sdkExecute($request);
            $result = str_replace('&amp;', '&', htmlspecialchars($result));
            /** response.getBody()打印结果就是orderString,可以直接给客户端请求,无需再做处理。如果传值客户端失败,可根据返回错误信息到该文档寻找排查方案:https://opensupport.alipay.com/support/helpcenter/89 **/
            return ['code' => 1, 'data' => $result, 'msg' => ''];
        } catch (\Exception $e) {
            return ['code' => 0, 'data' => $e->getMessage(), 'msg' => ''];
        }
    }

返回APP客户端示例:

3.支付宝回调处理

php 复制代码
	/**
     * @return string
     * 安卓充值支付回调
     */
    public function notify()
    {
        if(isset($_POST['out_trade_no']) && empty($order_sn)){
            $order_sn = $_POST['out_trade_no'];
        }
        Db::startTrans();
        try {
            $order = Orders::where('order_sn', $order_sn)->find();
            if(!$order){
                Db::rollback();
                return 'false';
            }
            $order->save(['pay_status' => Orders::PAY_STATUS_PAID]);
            //处理自己的业务逻辑
            Db::commit();
        }catch (\Exception $exception){
            Db::rollback();
            return 'false';
        }
        return 'success';
    }

三、支付宝APP提现(没有授权,直接填用户账户信息)

这里设计的是让用户输入支付宝账户和绑定的用户名,授权的话可以参考支付宝文档https://opendocs.alipay.com/common/02nk10?pathHash=a7475006

1.支付宝APP提现controller方法

php 复制代码
	/**
     * 提现到支付宝
     */
    public function alipayToUser()
    {
        $redis = new \Redis();
        $redis->connect('127.0.0.1', 6379);
        $redis->auth('Hd20240306');
        $lockKey = 'box_lock_' . $this->user->id;
        $lockValue = uniqid(); // 生成唯一值,用于解锁验证
        $expireTime = 10; // 锁的超时时间,秒
        // 尝试获取锁
        $isLocked = $redis->setnx($lockKey, $lockValue);
        if ($isLocked) {
            $redis->expire($lockKey, $expireTime); // 设置锁的过期时间,以避免死锁
            Db::startTrans();
            try {
                $out_biz_no = $this->request->post('out_biz_no');//商户端的唯一订单号
                $payee_type = $this->request->post('payee_type');//收款方账户类型(支付宝账户)
                $payee_account = $this->request->post('payee_account');//收款方支付宝账号
                $name = $this->request->post('name');//收款方支付宝账号
                if (!$name || $name == ''){
                    throw new Exception('请输入支付宝绑定的真实用户名');
                }
                $cash = (new Cash())->where('order_sn', $out_biz_no)->find();
                if (!$cash){
                    throw new Exception('订单有误');
                }
                $amount = number_format($cash->amount, 2);//转账金额
                //用户 -
                $num = number_format($amount * config('money.other'), 2);
                $memo = '提现'.$num.'燃币';
                $user_info = \app\common\model\User::where('id', $this->user->id)->find();
                if ($user_info->money < number_format($cash->amount * config('money.other'), 2)){
                    throw new Exception('余额不足');
                }
                //业务逻辑处理
                //提现
                (new AlipayServer())->payToUser($out_biz_no, $payee_account, $amount, $payee_type, $name);
                //订单状态变更
                $cash->payee_account = $payee_account;
                $cash->real_name = $name;
                $cash->pay_status = 2;
                $cash->save();
                Db::commit();
            }catch (\Exception $exception){
                Db::rollback();
                Log::info('alipayToUser-to' . $exception->getMessage());
                $this->error($exception->getMessage());
            }
            $this->success('提现成功');
        } else {
            $this->error('操作过于频繁,请稍后再试');
        }
    }

2.支付宝APP提现server方法

php 复制代码
	/**
     * @param $outBizNo - 订单号
     * @param $payeeAccount - 账户
     * @param $amount - 金额
     * @param $payeeType - 账户类型
     * @param $name - 账户绑定的实名信息
     * @param string $remark - 备注
     */
    public function payToUser($outBizNo, $payeeAccount, $amount, $payeeType, $name, $remark = '提现')
    {
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Util/AlipayConfigUtil.php';
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Util/AlipayLogger.php';
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Api/AlipayFundTransUniApi.php';
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Model/AlipayFundTransUniTransferModel.php';
        require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Model/Participant.php';
        // 初始化SDK
        $alipayConfigUtil = new AlipayConfigUtil($this->getPayConfig());

        // 构造请求参数以调用接口
        $apiInstance = new AlipayFundTransUniApi(
            new Client()
        );
        // 设置AlipayConfigUtil
        $apiInstance->setAlipayConfigUtil($alipayConfigUtil);
        $data = new AlipayFundTransUniTransferModel();

        // 设置商家侧唯一订单号
        $data->setOutBizNo($outBizNo);//自己定义的转账外部单号

        // 设置订单总金额
        $data->setTransAmount($amount);

        // 设置描述特定的业务场景
        $data->setBizScene("DIRECT_TRANSFER");

        // 设置业务产品码
        $data->setProductCode("TRANS_ACCOUNT_NO_PWD");

        // 设置转账业务的标题
        $data->setOrderTitle($remark);

        // 设置收款方信息
        $payeeInfo = new Participant();
        $payeeInfo->setIdentity($payeeAccount);
        $payeeInfo->setIdentityType($payeeType);
        $payeeInfo->setName($name);
        $data->setPayeeInfo($payeeInfo);

        // 设置业务备注
        $data->setRemark($remark);

        // 设置转账业务请求的扩展参数
        $data->setBusinessParams("{\"payer_show_name_use_alias\":\"true\"}");

        try {
        	//屏蔽日志打印
            AlipayLogger::setNeedEnableLogger(false);
            $result = $apiInstance->transfer($data);
            $result = json_decode($result, true);
            if ($result['status'] == 'SUCCESS'){
                return true;
            }else{
                throw new Exception($result['message']);
            }
        } catch (ApiException $e) {
            throw new Exception('操作失败' . $e->getMessage());
        }
    }
相关推荐
吴Wu涛涛涛涛涛Tao11 小时前
基于TCA构建Instagram克隆:SwiftUI状态管理的艺术
ios·swiftui
分享点11 小时前
Laravel 使用阿里云OSS S3 协议文件上传
阿里云·php·laravel
苏琢玉13 小时前
订单号老是撞车?我写了个通用 PHP ID 生成器
php·composer
BingoGo15 小时前
PHP 测试框架 Pest v4 正式发布 革命性的浏览器测试体验
后端·php
想想吴20 小时前
Android.bp 基础
android·安卓·android.bp
搬码临时工1 天前
通过自定义域名访问内网的web服务和tcp应用:内网ip到局域网外域名访问过程
服务器·tcp/ip·php
用户3074596982071 天前
PHP 命名空间(Namespace)全解析:从零开始,一篇讲透!
php
2501_915921431 天前
iOS 应用上架多环境实战,Windows、Linux 与 Mac 的不同路径
android·ios·小程序·https·uni-app·iphone·webview
Q_Q5110082852 天前
python的校园研招网系统
开发语言·spring boot·python·django·flask·node.js·php
大熊不是猫2 天前
Laravel 事件与监听器
php·laravel·event