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

打印 上一主题 下一主题

主题 639|帖子 639|积分 1917


前言

这里用的是thinkphp5,但是支付这个其他框架或版本也可以参考,只必要改下写法即可
一、IOS内购支付(ios订单生成自己写逻辑即可)

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

  1. public function payResult()
  2. {
  3.      //苹果内购的验证收据 orders模型换成自己的订单模型,字段换成自己的订单字段即可
  4.      $receipt_data = $this->request->post('receipt-data');//票据
  5.      $orderSn = $this->request->post('order_sn');//订单号
  6.      // 验证支付状态
  7.      $result = (new IosCheckServer())->payMoneyCheck($receipt_data, $orderSn);
  8.      if($result['status']){
  9.          $this->success('支付成功');
  10.      }else{
  11.          $this->error($result['message']);
  12.      }
  13. }
复制代码
1.支付回调票据校验server

  1. /**
  2.      * @param $receipt_data
  3.      * @param $orderSn
  4.      * @return array
  5.      * @throws \think\db\exception\DataNotFoundException
  6.      * @throws \think\db\exception\ModelNotFoundException
  7.      * @throws \think\exception\DbException
  8.      * @throws \think\exception\PDOException
  9.      * ios充值消费燃币支付回调票据校验
  10.      */
  11.     public function payMoneyCheck($receipt_data, $orderSn)
  12.     {
  13.         //苹果内购的验证收据 orders模型换成自己的订单模型,字段换成自己的订单字段即可
  14.         $orderinfo = Orders::where(['order_sn' => $orderSn])->find();
  15.         if($orderinfo){
  16.             if($orderinfo['pay_status'] == Orders::PAY_STATUS_PAID){
  17.                 return ['status' => false,'message' => '订单已成功支付,请勿重复发起申请'];
  18.             }
  19.         }else{
  20.             return ['status' => false,'message'=>'订单异常'];
  21.         }
  22.         // 验证支付状态
  23.         $result = $this->validate_apple_pay($receipt_data);
  24.         if($result['status']){
  25.             /*业务逻辑*/
  26.             Db::startTrans();
  27.             try {
  28.                 $orderinfo->pay_status = Orders::PAY_STATUS_PAID;
  29.                 $orderinfo->paid_at = time();
  30.                 $orderinfo->save();
  31.                 /*处理自己内部业务逻辑*/
  32.                 /*处理自己内部业务逻辑*/
  33.                 /*处理自己内部业务逻辑*/
  34.                 Db::commit();
  35.             } catch (\Exception $e) {
  36.                 Db::rollback();
  37.                 return ['status' => false,'message' => '支付失败,请联系管理员'];
  38.             }
  39.             return ['status' => true,'message' => '支付成功'];
  40.         }else{
  41.             return ['status' => false,'message' => $result['message']];
  42.         }
  43.     }
  44.         /**
  45.      * @param $receipt_data
  46.      * @param $orderinfo
  47.      * @return array
  48.      * @throws \think\exception\PDOException
  49.      * 校验票据
  50.      */
  51.     public function validate_apple_pay($receipt_data)
  52.     {
  53.         // 验证参数
  54.         if (strlen($receipt_data) < 20){
  55.             return ['status' => false, 'message' => '非法参数'];
  56.         }
  57.         // 请求验证
  58.         $html = $this->acurl($receipt_data);
  59.         $data = json_decode($html,true);
  60.         // 如果是沙盒数据 则验证沙盒模式
  61.         if($data['status'] == '21007'){
  62.             // 请求验证
  63.             $html = $this->acurl($receipt_data, 1);
  64.             $data = json_decode($html,true);
  65.             $data['sandbox'] = '1';
  66.         }
  67.         //找出时间最大的数组
  68.         $receiptitem = $data['receipt']['in_app'];
  69.         //这个是排序的方法  下面回帖出来 苹果会把这个会员往期的订单信息全部返回,需要找出来最近的那一组信息
  70.         $item = $this->arraySort($receiptitem,'purchase_date','desc');
  71.         //判断一下过期时间 延长会员时间
  72.         $orderThird = $item['transaction_id'];                //本次订阅的订单号
  73.         $orderThirdFirst = $item['original_transaction_id'];  //这个是原始订单号
  74.         if($orderThird == $orderThirdFirst){                  //首次订阅 两个相等
  75.             return ['status' => true,'message' => '支付成功'];
  76.         }else{
  77.             //判断过期时间和当前时间比较   expires_date_ms是毫秒单位
  78.             if($item['expires_date_ms']/1000 > time()){
  79.                 return ['status' => true,'message' => '支付成功'];
  80.             }else{
  81.                 //过期处理  票据失效
  82.                 return ['code' => false,'message' => '票据失效,请联系管理员'];
  83.             }
  84.         }
  85.     }
  86.         /**
  87.      * @param $receipt_data
  88.      * @param int $sandbox
  89.      * @return bool|string
  90.      * 数据校验
  91.      */
  92.     public function acurl($receipt_data, $sandbox = 0)
  93.     {
  94.         /**
  95.          * 21000 App Store不能读取你提供的JSON对象
  96.          * 21002 receipt-data域的数据有问题
  97.          * 21003 receipt无法通过验证
  98.          * 21004 提供的shared secret不匹配你账号中的shared secret
  99.          * 21005 receipt服务器当前不可用
  100.          * 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
  101.          * 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
  102.          * 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
  103.          */
  104.         //小票信息
  105.         $POSTFIELDS = array("receipt-data" => $receipt_data);
  106.         $POSTFIELDS = json_encode($POSTFIELDS);
  107.         $url_buy     = "https://buy.itunes.apple.com/verifyReceipt";//正式购买地址
  108.         $url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";//沙盒购买地址
  109.         $url = $sandbox ? $url_sandbox : $url_buy;
  110.         //curl - post
  111.         $ch = curl_init($url);
  112.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  113.         curl_setopt($ch, CURLOPT_POST, 1);
  114.         curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS);
  115.         $result = curl_exec($ch);
  116.         curl_close($ch);
  117.         return $result;
  118.     }
  119.     /**
  120.      * @param $arr
  121.      * @param $key
  122.      * @param string $type
  123.      * @return mixed
  124.      * 查找最新数据
  125.      */
  126.     public static function arraySort($arr,$key,$type='asc')
  127.     {
  128.         $keyArr = []; // 初始化存放数组将要排序的字段值
  129.         foreach ($arr as $k => $v) {
  130.             $keyArr[$k] = $v[$key]; // 循环获取到将要排序的字段值
  131.         }
  132.         if ($type == 'asc') {
  133.             asort($keyArr); // 排序方式,将一维数组进行相应排序
  134.         } else {
  135.             arsort($keyArr);
  136.         }
  137.         foreach ($keyArr as $k => $v) {
  138.             $newArray[$k] = $arr[$k]; // 循环将配置的值放入响应的下标下
  139.         }
  140.         $newArray = array_merge($newArray); // 重置下标
  141.         return $newArray[0]; // 数据返回
  142.     }
复制代码
二、安卓APP支付宝支付

前期预备工作:

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

controller方法(订单逻辑部门可以自己处理)
  1.         /**
  2.      * @throws \think\db\exception\DataNotFoundException
  3.      * @throws \think\db\exception\ModelNotFoundException
  4.      * @throws \think\exception\DbException
  5.      * 会员订单创建 - android
  6.      */
  7.     public function memberOrderByAndroid()
  8.     {
  9.         /**
  10.          * 生成订单数据
  11.          */
  12.         $result = [];
  13.         Db::startTrans();
  14.         try {
  15.             $model = new Orders($arr);
  16.             $res = $model->save($arr);
  17.             if ($res === false){
  18.                 Db::rollback();
  19.                 $this->error('订单创建失败');
  20.             }
  21.             $server = new AlipayServer();
  22.             //订单号,金额,回调地址
  23.             $res = $server->payMemberOrder($model->order_sn, number_format($model->amount, 2), '****/api/v1/member/notify');
  24.             if ($res['code']){
  25.                 $result['orderStr'] = $res['data'];
  26.             }
  27.             Db::commit();
  28.         }catch (\Exception $exception){
  29.             Db::rollback();
  30.             $this->error('订单创建失败');
  31.         }
  32.         $this->success('操作成功', $result);
  33.     }
复制代码
2.生成订单返回支付宝字符串server方法

  1.         /**
  2.      * @param $out_trade_no 订单号
  3.      * @param $total_amount 金额
  4.      * @param $notify_url 回调地址
  5.      * @return array
  6.      * 燃币充值 - 安卓 - 支付宝
  7.      */
  8.     public function payMemberOrder($out_trade_no, $total_amount, $notify_url)
  9.     {
  10.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v2/aop/AopCertClient.php';
  11.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v2/aop/request/AlipayTradeAppPayRequest.php';
  12.         /** 初始化 **/
  13.         $aop = new \AopCertClient;
  14.         /** 支付宝网关 **/
  15.         $aop->gatewayUrl = "https://openapi.alipay.com/gateway.do";
  16.         /** 应用id,如何获取请参考:https://opensupport.alipay.com/support/helpcenter/190/201602493024 **/
  17.         $aop->appId = config('alipay.app_id');
  18.         /** 密钥格式为pkcs1,如何获取私钥请参考:https://opensupport.alipay.com/support/helpcenter/207/201602469554  **/
  19.         $aop->rsaPrivateKey = trim(config('alipay.private_key'));
  20.         /** 应用公钥证书路径,下载后保存位置的绝对路径  **/
  21.         $appCertPath = __PUBLIC__ . '/cert/appCertPublicKey_2021004170664201.crt';
  22.         /** 支付宝公钥证书路径,下载后保存位置的绝对路径 **/
  23.         $alipayCertPath = __PUBLIC__ . '/cert/alipayCertPublicKey_RSA2.crt';
  24.         /** 支付宝根证书路径,下载后保存位置的绝对路径 **/
  25.         $rootCertPath = __PUBLIC__ . '/cert/alipayRootCert.crt';
  26.         /** 设置签名类型 **/
  27.         $aop->signType= "RSA2";
  28.         /** 设置请求格式,固定值json **/
  29.         $aop->format = "json";
  30.         /** 设置编码格式 **/
  31.         $aop->charset= "utf-8";
  32.         /** 调用getPublicKey从支付宝公钥证书中提取公钥 **/
  33.         $aop->alipayrsaPublicKey = $aop->getPublicKey($alipayCertPath);
  34.         /** 是否校验自动下载的支付宝公钥证书,如果开启校验要保证支付宝根证书在有效期内 **/
  35.         $aop->isCheckAlipayPublicCert = true;
  36.         /** 调用getCertSN获取证书序列号 **/
  37.         $aop->appCertSN = $aop->getCertSN($appCertPath);
  38.         /** 调用getRootCertSN获取支付宝根证书序列号 **/
  39.         $aop->alipayRootCertSN = $aop->getRootCertSN($rootCertPath);
  40.         /** 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay **/
  41.         $request = new \AlipayTradeAppPayRequest();
  42.         /** 设置业务参数 **/
  43.         $request->setBizContent("{" .
  44.             /**  商户订单号,商户自定义,需保证在商户端不重复,如:20200612000001 **/
  45.             ""out_trade_no":"{$out_trade_no}"," .
  46.             /** 销售产品码,固定值:QUICK_MSECURITY_PAY **/
  47.             ""product_code":"QUICK_MSECURITY_PAY"," .
  48.             /** 订单金额,精确到小数点后两位 **/
  49.             ""total_amount":"{$total_amount}"," .
  50.             /** 订单标题(可自定义) **/
  51.             ""subject":"会员充值"," .
  52.             /** 订单描述(可自定义) **/
  53.             ""body":"会员充值"" .
  54.             "}");
  55.         /** 异步通知地址,以http或者https开头的,商户外网可以post访问的异步地址,用于接收支付宝返回的支付结果,如果未收到该通知可参考该文档进行确认:https://opensupport.alipay.com/support/helpcenter/193/201602475759 **/
  56.         $request->setNotifyUrl($notify_url);
  57.         try {
  58.             /** 调用SDK生成支付链接,可在浏览器打开链接进入支付页面 **/
  59.             $result = $aop->sdkExecute($request);
  60.             $result = str_replace('&amp;', '&', htmlspecialchars($result));
  61.             /** response.getBody()打印结果就是orderString,可以直接给客户端请求,无需再做处理。如果传值客户端失败,可根据返回错误信息到该文档寻找排查方案:https://opensupport.alipay.com/support/helpcenter/89 **/
  62.             return ['code' => 1, 'data' => $result, 'msg' => ''];
  63.         } catch (\Exception $e) {
  64.             return ['code' => 0, 'data' => $e->getMessage(), 'msg' => ''];
  65.         }
  66.     }
复制代码
返回APP客户端示例:

3.支付宝回调处理

  1.         /**
  2.      * @return string
  3.      * 安卓充值支付回调
  4.      */
  5.     public function notify()
  6.     {
  7.         if(isset($_POST['out_trade_no']) && empty($order_sn)){
  8.             $order_sn = $_POST['out_trade_no'];
  9.         }
  10.         Db::startTrans();
  11.         try {
  12.             $order = Orders::where('order_sn', $order_sn)->find();
  13.             if(!$order){
  14.                 Db::rollback();
  15.                 return 'false';
  16.             }
  17.             $order->save(['pay_status' => Orders::PAY_STATUS_PAID]);
  18.             //处理自己的业务逻辑
  19.             Db::commit();
  20.         }catch (\Exception $exception){
  21.             Db::rollback();
  22.             return 'false';
  23.         }
  24.         return 'success';
  25.     }
复制代码
三、支付宝APP提现(没有授权,直接填用户账户信息)

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

  1.         /**
  2.      * 提现到支付宝
  3.      */
  4.     public function alipayToUser()
  5.     {
  6.         $redis = new \Redis();
  7.         $redis->connect('127.0.0.1', 6379);
  8.         $redis->auth('Hd20240306');
  9.         $lockKey = 'box_lock_' . $this->user->id;
  10.         $lockValue = uniqid(); // 生成唯一值,用于解锁验证
  11.         $expireTime = 10; // 锁的超时时间,秒
  12.         // 尝试获取锁
  13.         $isLocked = $redis->setnx($lockKey, $lockValue);
  14.         if ($isLocked) {
  15.             $redis->expire($lockKey, $expireTime); // 设置锁的过期时间,以避免死锁
  16.             Db::startTrans();
  17.             try {
  18.                 $out_biz_no = $this->request->post('out_biz_no');//商户端的唯一订单号
  19.                 $payee_type = $this->request->post('payee_type');//收款方账户类型(支付宝账户)
  20.                 $payee_account = $this->request->post('payee_account');//收款方支付宝账号
  21.                 $name = $this->request->post('name');//收款方支付宝账号
  22.                 if (!$name || $name == ''){
  23.                     throw new Exception('请输入支付宝绑定的真实用户名');
  24.                 }
  25.                 $cash = (new Cash())->where('order_sn', $out_biz_no)->find();
  26.                 if (!$cash){
  27.                     throw new Exception('订单有误');
  28.                 }
  29.                 $amount = number_format($cash->amount, 2);//转账金额
  30.                 //用户 -
  31.                 $num = number_format($amount * config('money.other'), 2);
  32.                 $memo = '提现'.$num.'燃币';
  33.                 $user_info = \app\common\model\User::where('id', $this->user->id)->find();
  34.                 if ($user_info->money < number_format($cash->amount * config('money.other'), 2)){
  35.                     throw new Exception('余额不足');
  36.                 }
  37.                 //业务逻辑处理
  38.                 //提现
  39.                 (new AlipayServer())->payToUser($out_biz_no, $payee_account, $amount, $payee_type, $name);
  40.                 //订单状态变更
  41.                 $cash->payee_account = $payee_account;
  42.                 $cash->real_name = $name;
  43.                 $cash->pay_status = 2;
  44.                 $cash->save();
  45.                 Db::commit();
  46.             }catch (\Exception $exception){
  47.                 Db::rollback();
  48.                 Log::info('alipayToUser-to' . $exception->getMessage());
  49.                 $this->error($exception->getMessage());
  50.             }
  51.             $this->success('提现成功');
  52.         } else {
  53.             $this->error('操作过于频繁,请稍后再试');
  54.         }
  55.     }
复制代码
2.支付宝APP提现server方法

  1.         /**
  2.      * @param $outBizNo - 订单号
  3.      * @param $payeeAccount - 账户
  4.      * @param $amount - 金额
  5.      * @param $payeeType - 账户类型
  6.      * @param $name - 账户绑定的实名信息
  7.      * @param string $remark - 备注
  8.      */
  9.     public function payToUser($outBizNo, $payeeAccount, $amount, $payeeType, $name, $remark = '提现')
  10.     {
  11.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Util/AlipayConfigUtil.php';
  12.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Util/AlipayLogger.php';
  13.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Api/AlipayFundTransUniApi.php';
  14.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Model/AlipayFundTransUniTransferModel.php';
  15.         require_once __PUBLIC__ . '/../vendor/alipaysdk/openapi/v3/src/Model/Participant.php';
  16.         // 初始化SDK
  17.         $alipayConfigUtil = new AlipayConfigUtil($this->getPayConfig());
  18.         // 构造请求参数以调用接口
  19.         $apiInstance = new AlipayFundTransUniApi(
  20.             new Client()
  21.         );
  22.         // 设置AlipayConfigUtil
  23.         $apiInstance->setAlipayConfigUtil($alipayConfigUtil);
  24.         $data = new AlipayFundTransUniTransferModel();
  25.         // 设置商家侧唯一订单号
  26.         $data->setOutBizNo($outBizNo);//自己定义的转账外部单号
  27.         // 设置订单总金额
  28.         $data->setTransAmount($amount);
  29.         // 设置描述特定的业务场景
  30.         $data->setBizScene("DIRECT_TRANSFER");
  31.         // 设置业务产品码
  32.         $data->setProductCode("TRANS_ACCOUNT_NO_PWD");
  33.         // 设置转账业务的标题
  34.         $data->setOrderTitle($remark);
  35.         // 设置收款方信息
  36.         $payeeInfo = new Participant();
  37.         $payeeInfo->setIdentity($payeeAccount);
  38.         $payeeInfo->setIdentityType($payeeType);
  39.         $payeeInfo->setName($name);
  40.         $data->setPayeeInfo($payeeInfo);
  41.         // 设置业务备注
  42.         $data->setRemark($remark);
  43.         // 设置转账业务请求的扩展参数
  44.         $data->setBusinessParams("{"payer_show_name_use_alias":"true"}");
  45.         try {
  46.                 //屏蔽日志打印
  47.             AlipayLogger::setNeedEnableLogger(false);
  48.             $result = $apiInstance->transfer($data);
  49.             $result = json_decode($result, true);
  50.             if ($result['status'] == 'SUCCESS'){
  51.                 return true;
  52.             }else{
  53.                 throw new Exception($result['message']);
  54.             }
  55.         } catch (ApiException $e) {
  56.             throw new Exception('操作失败' . $e->getMessage());
  57.         }
  58.     }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

雁过留声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表