当前位置: 移动技术网 > IT编程>开发语言>PHP > 详解PHP实现支付宝小程序用户授权的工具类

详解PHP实现支付宝小程序用户授权的工具类

2019年05月29日  | 移动技术网IT编程  | 我要评论
背景 最近项目需要上线支付宝小程序,同时需要走用户的授权流程完成用户信息的存储,以前做过微信小程序的开发,本以为实现授权的过程是很简单的事情,但是再实现的过程中还是遇到了

背景

最近项目需要上线支付宝小程序,同时需要走用户的授权流程完成用户信息的存储,以前做过微信小程序的开发,本以为实现授权的过程是很简单的事情,但是再实现的过程中还是遇到了不少的坑,因此记录一下实现的过程

学到的知识

  • 支付宝开放接口的调用模式以及实现方式
  • 支付宝小程序授权的流程
  • rsa加密方式

吐槽点

支付宝小程序的入口隐藏的很深,没有微信小程序那么直接了当
支付宝小程序的开发者工具比较难用,编译时候比较卡,性能有很大的问题
每提交一次代码,支付宝小程序的体验码都要进行更换,比较繁琐,而且localstorage的东西不知道要如何删除

事先准备

  • 支付宝开放平台注册一个开发者账号,并做好相应的认证等工作
  • 创建一个小程序,并记录好相关的小程序信息,包括支付宝公钥,私钥,app公钥等,可以借鉴支付宝官方提供的相应的公钥生成工具来生成公钥和私钥,工具的下载地址:
  • 了解下支付宝小程序的签名机制,详细见
  • 熟悉下支付宝小程序获取用户信息的过程,详细见

授权的步骤

授权时序图

实现流程

  1. 客户端通过my.getauthcode接口获取code,传给服务端
  2. 服务端通过code,调用获取token接口获取access_token,alipay.system.oauth.token(换取授权访问令牌)
  3. 通过token接口调用支付宝会员查询接口获取会员信息,alipay.user.info.share(支付宝会员授权信息查询接口)
  4. 将获取的用户信息保存到数据库

amphelper工具类

<?php
/**
 * created by phpstorm.
 * user: my
 * date: 2018/8/16
 * time: 17:45
 */

namespace app\http\helper;

use app\http\helper\sys\businesshelper;
use illuminate\support\facades\log;

class amphelper
{

  const api_domain = "https://openapi.alipay.com/gateway.do?";
  const api_method_generate_qr = 'alipay.open.app.qrcode.create';
  const api_method_auth_token = 'alipay.system.oauth.token';
  const api_method_get_user_info = 'alipay.user.info.share';

  const sign_type_rsa2 = 'rsa2';
  const version = '1.0';
  const file_charset_utf8 = "utf-8";
  const file_charset_gbk = "gbk";
  const response_outer_node_qr = 'alipay_open_app_qrcode_create_response';
  const response_outer_node_auth_token = 'alipay_system_oauth_token_response';
  const response_outer_node_user_info = 'alipay_user_info_share_response';
  const response_outer_node_error_response = 'error_response';

  const status_code_success = 10000;
  const status_code_except = 20000;


  /**
   * 获取用户信息接口,根据token
   * @param $code 授权码
   * 通过授权码获取用户的信息
   */
  public static function getampuserinfobyauthcode($code){
    $aliuserinfo = [];
    $tokendata = amphelper::getamptoken($code);
    //如果token不存在,这种主要是为了处理支付宝的异常记录
    if(isset($tokendata['code'])){
      return $tokendata;
    }
    $token = formatarrvalue($tokendata,'access_token');
    if($token){
      $userbusiparam = self::getampuserbaseparam($token);
      $url = self::buildrequesturl($userbusiparam);
      $resonse = self::getresponse($url,self::response_outer_node_user_info);
      if($resonse['code'] == self::status_code_success){
        //有效的字段列
        $userinfocolumn = ['user_id','avatar','province','city','nick_name','is_student_certified','user_type','user_status','is_certified','gender'];
        foreach ($userinfocolumn as $column){
          $aliuserinfo[$column] = formatarrvalue($resonse,$column,'');
        }

      }else{
        $exceptcolumns = ['code','msg','sub_code','sub_msg'];
        foreach ($exceptcolumns as $column){
          $aliuserinfo[$column] = formatarrvalue($resonse,$column,'');
        }
      }
    }
    return $aliuserinfo;
  }


  /**
   * 获取小程序token接口
   */
  public static function getamptoken($code){
    $param = self::getauthbaseparam($code);
    $url = self::buildrequesturl($param);
    $response = self::getresponse($url,self::response_outer_node_auth_token);
    $tokenresult = [];
    if(isset($response['code']) && $response['code'] != self::status_code_success){
      $exceptcolumns = ['code','msg','sub_code','sub_msg'];
      foreach ($exceptcolumns as $column){
        $tokenresult[$column] = formatarrvalue($response,$column,'');
      }
    }else{
      $tokenresult = $response;
    }
    return $tokenresult;
  }

  /**
   * 获取二维码链接接口
   * 433ac5ea4c044378826afe1532bcvx78
   * https://openapi.alipay.com/gateway.do?timestamp=2013-01-01 08:08:08&method=alipay.open.app.qrcode.create&app_id=2893&sign_type=rsa2&sign=eritjkeijkjhkkkkkkkhjereeeeeeeeeee&version=1.0&biz_content=
  {"url_param":"/?name=ali&loc=hz", "query_param":"name=1&age=2", "describe":"二维码描述"}
  */
  public static function generateqrcode($mppage = 'pages/index',$queryparam = [],$describe){
    $param = self::getqrcodebaseparam($mppage,$queryparam,$describe );
    $url = self::buildrequesturl($param);
    $response = self::getresponse($url,self::response_outer_node_qr);
    return $response;
  }


  /**
   * 获取返回的数据,对返回的结果做进一步的封装和解析,因为支付宝的每个接口的返回都是由一个特定的  
   * key组成的,因此这里直接封装了而一个通用的方法,对于不同的接口只需要更改相应的node节点就可以了
   */
  public static function getresponse($url,$responsenode){
    $json = curlrequest($url);
    $response = json_decode($json,true);
    $responsecontent = formatarrvalue($response,$responsenode,[]);
    $errresponse = formatarrvalue($response,self::response_outer_node_error_response,[]);
    if($errresponse){
      return $errresponse;
    }
    return $responsecontent;
  }

  /**
   * 获取请求的链接
   */
  public static function buildqrrequesturl($mppage = 'pages/index',$queryparam = []){
    $paramstr = http_build_query(self::getqrbaseparam($mppage,$queryparam));
    return self::api_domain . $paramstr;
  }



  /**
   * 构建请求链接
   */
  public static function buildrequesturl($param){
    $paramstr = http_build_query($param);
    return self::api_domain . $paramstr;
  }


  /**
   * 获取用户的基础信息接口
   */
  public static function getampuserbaseparam($token){
    $busiparam = [
      'auth_token' => $token,
    ];
    $param = self::buildapibuisinessparam($busiparam,self::api_method_get_user_info);
    return $param;

  }

  /**
   *获取二维码的基础参数
   */
  public static function getqrcodebaseparam($page= 'pages/index/index',$queryparam = [],$describe = ''){
    $busiparam = [
      'biz_content' => self::getqrbizcontent($page,$queryparam,$describe)
    ];
    $param = self::buildapibuisinessparam($busiparam,self::api_method_generate_qr);
    return $param;

  }

  /**
   *获取授权的基础参数
   */
  public static function getauthbaseparam($code,$refreshtoken = ''){
    $busiparam = [
      'grant_type' => 'authorization_code',
      'code' => $code,
      'refresh_token' => $refreshtoken,
    ];
    $param = self::buildapibuisinessparam($busiparam,self::api_method_auth_token);
    return $param;
  }


  /**
   * 构建业务参数
   */
  public static function buildapibuisinessparam($businessparam,$apimethod){
    $pubparam = self::getapipubparam($apimethod);
    $businessparam = array_merge($pubparam,$businessparam);
    $signcontent = self::getsigncontent($businessparam);
    error_log('sign_content ===========>'.$signcontent);
    $rsahelper = new rsahelper();
    $sign = $rsahelper->createsign($signcontent);
    error_log('sign ===========>'.$sign);
    $businessparam['sign'] = $sign;
    return $businessparam;
  }


  /**
   * 公共参数
   *
   */
  public static function getapipubparam($apimethod){
    $ampbaseinfo = businesshelper::getampbaseinfo();
    $param = [
      'timestamp' => date('y-m-d h:i:s') ,
      'method' => $apimethod,
      'app_id' => formatarrvalue($ampbaseinfo,'appid',config('param.amp.appid')),
      'sign_type' =>self::sign_type_rsa2,
      'charset' =>self::file_charset_utf8,
      'version' =>self::version,
    ];
    return $param;
  }


  /**
   * 获取签名的内容
   */
  public static function getsigncontent($params) {
    ksort($params);
    $stringtobesigned = "";
    $i = 0;
    foreach ($params as $k => $v) {
      if (!empty($v) && "@" != substr($v, 0, 1)) {
        if ($i == 0) {
          $stringtobesigned .= "$k" . "=" . "$v";
        } else {
          $stringtobesigned .= "&" . "$k" . "=" . "$v";
        }
        $i++;
      }
    }
    unset ($k, $v);
    return $stringtobesigned;
  }


  public static function convertarrtoqueryparam($param){
    $queryparam = [];
    foreach ($param as $key => $val){
      $obj = $key.'='.$val;
      array_push($queryparam,$obj);
    }
    $querystr = implode('&',$queryparam);
    return $querystr;
  }

  /**
   * 转换字符集编码
   * @param $data
   * @param $targetcharset
   * @return string
   */
  public static function characet($data, $targetcharset) {
    if (!empty($data)) {
      $filetype = self::file_charset_utf8;
      if (strcasecmp($filetype, $targetcharset) != 0) {
        $data = mb_convert_encoding($data, $targetcharset, $filetype);
      }
    }
    return $data;
  }

  /**
   * 获取业务参数内容
   */
  public static function getqrbizcontent($page, $queryparam = [],$describe = ''){
    if(is_array($queryparam)){
      $queryparam = http_build_query($queryparam);
    }
    $obj = [
      'url_param' => $page,
      'query_param' => $queryparam,
      'describe' => $describe
    ];
    $bizcontent = json_encode($obj,json_unescaped_unicode);
    return $bizcontent;
  }

}

ampheler工具类关键代码解析相关常量

//支付宝的api接口地址
const api_domain = "https://openapi.alipay.com/gateway.do?";
//获取支付宝二维码的接口方法
const api_method_generate_qr = 'alipay.open.app.qrcode.create';
//获取token的接口方法
const api_method_auth_token = 'alipay.system.oauth.token';
//获取用户信息的接口方法
const api_method_get_user_info = 'alipay.user.info.share';
//支付宝的签名方式,由rsa2和rsa两种
const sign_type_rsa2 = 'rsa2';
//版本号,此处固定挑那些就可以了
const version = '1.0';
//utf8编码
const file_charset_utf8 = "utf-8";
//gbk编码
const file_charset_gbk = "gbk";
//二维码接口调用成功的 返回节点
const response_outer_node_qr = 'alipay_open_app_qrcode_create_response';
//token接口调用成功的 返回节点
const response_outer_node_auth_token = 'alipay_system_oauth_token_response';
//用户信息接口调用成功的 返回节点
const response_outer_node_user_info = 'alipay_user_info_share_response';
//错误的返回的时候的节点
const response_outer_node_error_response = 'error_response';

const status_code_success = 10000;
const status_code_except = 20000;

getampuserinfobyauthcode方法

这个方法是获取用户信息的接口方法,只需要传入客户端传递的code,就可以获取到用户的完整信息

getamptoken方法

这个方法是获取支付宝接口的token的方法,是一个公用方法,后面所有的支付宝的口调用,都可以使用这个方法先获取token

getresponse方法

考虑到会调用各个支付宝的接口,因此这里封装这个方法是为了方便截取接口返回成功之后的信息,提高代码的阅读性

getapipubparam方法

这个方法是为了获取公共的参数,包括版本号,编码,appid,签名类型等基础业务参数

getsigncontent方法

这个方法是获取签名的内容,入参是一个数组,最后输出的是参数的拼接字符串

buildapibuisinessparam($businessparam,$apimethod)

这个是构建api独立的业务参数部分方法,businessparam参数是支付宝各个接口的业务参数部分(出去公共参数),$apimethod是对应的接口的方法名称,如获取token的方法名为alipay.system.oauth.token

签名帮助类

<?php
/**
 * created by phpstorm.
 * user: auser
 * date: 2018/12/4
 * time: 15:37
 */

namespace app\http\helper;

/**
 *$rsa2 = new rsa2();
 *$data = 'mydata'; //待签名字符串
 *$strsign = $rsa2->createsign($data);   //生成签名
 *$is_ok = $rsa2->verifysign($data, $strsign); //验证签名
 */
class rsahelper
{

  private static $private_key;
  private static $public_key;


  function __construct(){
    self::$private_key = config('param.amp.private_key');
    self::$public_key = config('param.amp.public_key');
  }

  /**
   * 获取私钥
   * @return bool|resource
   */
  private static function getprivatekey()
  {
    $privkey = self::$private_key;
    $privkey = "-----begin rsa private key-----".php_eol.wordwrap($privkey, 64, php_eol, true).php_eol."-----end rsa private key-----";
    ($privkey) or die('您使用的私钥格式错误,请检查rsa私钥配置');
    error_log('private_key is ===========>: '.$privkey);
    return openssl_pkey_get_private($privkey);
  }
  /**
   * 获取公钥
   * @return bool|resource
   */
  private static function getpublickey()
  {
    $publickey = self::$public_key;
    $publickey = "-----begin rsa private key-----".php_eol.wordwrap($publickey, 64, php_eol, true).php_eol."-----end rsa private key-----";
    error_log('public key is : ===========>'.$publickey);
    return openssl_pkey_get_public($publickey);
  }
  /**
   * 创建签名
   * @param string $data 数据
   * @return null|string
   */
  public function createsign($data = '')
  {
    // var_dump(self::getprivatekey());die;
    if (!is_string($data)) {
      return null;
    }
    return openssl_sign($data, $sign, self::getprivatekey(),openssl_algo_sha256 ) ? base64_encode($sign) : null;
  }
  /**
   * 验证签名
   * @param string $data 数据
   * @param string $sign 签名
   * @return bool
   */
  public function verifysign($data = '', $sign = '')
  {
    if (!is_string($sign) || !is_string($sign)) {
      return false;
    }
    return (bool)openssl_verify(
      $data,
      base64_decode($sign),
      self::getpublickey(),
      openssl_algo_sha256
    );
  }
}

调用

$originuserdata = amphelper::getampuserinfobyauthcode($code);
echo $originuserdata;

注意getampuserinfobyauthcode方法,调用接口成功,会返回支付宝用户的正确信息,示例如下

{
  "alipay_user_info_share_response": {
    "code": "10000",
    "msg": "success",
    "user_id": "2088102104794936",
    "avatar": "http://tfsimg.alipay.com/images/partner/t1uixxxbpxxxxxxxx",
    "province": "安徽省",
    "city": "安庆",
    "nick_name": "支付宝小二",
    "is_student_certified": "t",
    "user_type": "1",
    "user_status": "t",
    "is_certified": "t",
    "gender": "f"
  },
  "sign": "eritjkeijkjhkkkkkkkhjereeeeeeeeeee"
}

踩坑点

  1. 在开发之前一定要仔细阅读用户的授权流程指引文档,否则很容出错
  2. 对于用户信息接口,在获取授权信息接口并没有做明确的说明,所以需要先梳理清楚
  3. 支付宝的签名机制和微信的有很大不同,对于习惯了微信小程序开发的人来说,刚开始可能有点不适应,所以需要多看看sdk里面的实现

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网