标签归档:access token

Wish的OAuth 2认证流程

Wish还提供了一个sandbox环境,不像那些个Ali系统,从不提供这种东西,想想都恶心(玛尼,就知道坑钱去了)。

http://sandbox.merchant.wish.com,自己注册个sandbox账户,随意折腾。

其实,OAuth 2认证流程都差不多,首先跳转到认证登录页,要求输入用户名密码进行授权(这个过程可能有点差别),然后重定向到预定的redirect_uri并附上一个一次性的code(这个就是认证码了),然后用这个code去获取access_token和refresh_token,这个时候要把它们保存下来,再然后就可以使用access_token来访问了。如果access_token过期了,可以使用refresh_token来换取新的access_token。

在进行第一步之前,要先获取API相关信息(账户-设置):
wish_api
理论上,这里就是对应一个App,那么不同的店铺应该都可以绑定到这个App。不过,如果别人都可以绑定到你的App,那么你就可以提供服务了,很明显,它不应该不允许你这样干,这个跟恶心的Ali系统一样,如果要这样干,需要得到特别授权,所以,这里的App(Api)跟你的账户一一对应,你的账户只能授权给你自己创建的这个App。(注意以上的信息,后面都要用到)

在基本信息中,还有一个叫商户 ID的,它唯一标识你的账户。这个后面会涉及到。

弄好之后,就可以开始了。详细内容参考:https://merchant.wish.com/documentation/oauth。这里只做一些备注。在第一步时,直接发起https://merchant.wish.com/oauth/authorize?client_id={client_id},参数除了client_id,你无法再提供其它参数了,有些实现方案还可以在这传递redirect_uri和其它参数,Wish不允许,授权码(code)直接附加到你在账户设置里面设置的那个redirect_uri。

在第二步中,你需要使用第一步中获取到的一次性code换取令牌,你需要通过POST方法发送如下参数:

//https://merchant.wish.com/api/v2/oauth/access_token 
client_id	Your app's client ID
client_secret	Your app's client secret
code	        The authorization code you received
grant_type	The string 'authorization_code'
redirect_uri	Your app's redirect uri that you specified when you created the app

这里需要注意的是,redirect_uri必须填写在你账户里设置的那个,否则无法通过验证。在Ali系统中,这个是不验证的,可以随意,玛尼,被它坑死。

这个通过之后会返回一个JSON信息,包含了Token,这个时候要把Token保存起来。不过这里,如果参考文档,你发现,它返回的数据没带有商户Id,那么怎么跟商户关联起来?你要是可以在redirect时携带参数还好,不过Wish你允许啊,所以,千万别被它坑了,真实返回的JSON是带有这个商户ID,这样就可以对应起来。

从真实返回的信息可以知道,它的Access Token有效是30天(玛尼,是不是有点长),另外,refresh_token视乎没有过期概念。在Access Token过期前换一个新的,不知道旧的会不会失效。同样,如果重新对App进行授权,这样就产生新的refresh_token,那么旧的会不会就失效了呢? 答案应该是的。(Ali系貌似这个问题比较隐晦)所以,如果要换一换Token,再来一次授权吧。

最后说说官方提供的PHP SDK吧。地址:https://github.com/ContextLogic/Wish-Merchant-API,可以通过composer引用,不过要注意的是,它至今还是dev版本,所以要这样干:

{
  "minimum-stability": "dev",
  "require":{
      "wish/php-sdk":"*"
  }
}

好的不教,这个简直要害死你。这样其它的包,也下载dev分支的给你。对我来说,我直接下载下来当本地包使用,因为这个包有点问题,需要改。故而:

    "autoload": {
        "psr-4": {
            "Wish\\": "local/Wish/"
        }
    },

然后更新下就完事了。

我第一个需要改这个包的地方就是,它使用了curl去发起请求,而Wish的Api都是https的,玛尼,Windows下curl无法验证https的证书(需要设置,指定CA证书的位置),所以我需要把它给位不验证:

//WishRequest.php 65行
if ((int)preg_match('/^HTTPS/i', $url) > 0) {
    $options[CURLOPT_SSL_VERIFYPEER] = false;
    $options[CURLOPT_SSL_VERIFYHOST] = false;
}

另外,这个鬼佬写得代码是用两个空格作为tab的,简直欠抽的节奏。还好格式化工具比较强大。

然后,打开WishSession.php,兼容问题:

private static $api_key;
private static $session_type;
private static $merchant_id;

#改为

private $api_key;
private $session_type;
private $merchant_id;

在实例方法中,出现和静态变量重名报错。以下是一段实例:

// Laravel 的Model
<?php
namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Wish\WishAuth;

class WishAccount extends Model
{

    protected $table = 'wish_account';

    protected $guarded = [];

    public function getAccessToken($type='prod')
    {
        $default = ['success'=>0,'message'=>'获取Access Token失败','access_token'=>''];
        // Access Token不为空,判断是否过气
        if (! empty($this->access_token)) {
            $timeOut = trim($this->access_token_timeout_at);
            if(!empty($timeOut) && (strtotime($timeOut) > time())) {
                return ['success'=>1,'message'=>'','access_token'=>$this->access_token];
            }
        } 
        
        // 更换新Token
        if(! empty($this->refresh_token)) {
            $client_id = trim($this->client_id);
            $client_secret = trim($this->secret_id);
            
            if(empty($client_id) || empty($client_secret)) {
                $default['message'] = "Access Token过期,使用Refresh Token换取Access Token失败(Client_id 或 Client_secret为空,无法换取Token)";
                return $default;
            }
            
            // 
            try {
                $auth = new WishAuth($client_id,$client_secret,$type);
                $response = $auth->refreshToken($this->refresh_token);
                if($response->getStatusCode() > 0) {
                    $default['message'] = "Access Token过期,使用Refresh Token换取Access Token失败(状态码:".$response->getStatusCode()." Msg:".$response->getMessage();
                } else {
                    // 返回的Refresh Token,跟之前一样
                    $this->freshToken($response->getData());
                    return ['success'=>1,'message'=>'','access_token'=>$response->getData()->access_token];
                }
            } catch (Exception $e) {
                $default['message'] = "Access Token过期,使用Refresh Token换取Access Token失败(状态码:4000 Msg:Unauthorized access)";
                return $default;
            }
        }
        
        return $default;
    }
    
    // 一次性code换取Token access_token和refresh_token
    public function setToken($code,$type='prod') {

        $client_id = trim($this->client_id);
        $client_secret = trim($this->secret_id);
        $redirect_uri = trim($this->redirect_uri);
        
        if(empty($client_id) || empty($client_secret) || empty($redirect_uri) || empty($code)) {
            return "信息不完整,无法完成授权。";
        }
        
        try {
            $auth = new WishAuth($client_id,$client_secret,$type);
            $response = $auth->getToken($code,$redirect_uri);
            
            $this->freshToken($response->getData());
        } catch (Exception $e) {
            return "4000 或 1016 异常。";
        }
    }
    
    // 保存Token信息
    protected function freshToken($data) {
        if (is_object($data)) {
            $this->refresh_token = $data->refresh_token;
            $this->refresh_token_timeout_at = date("Y-m-d H:i:s", time() + 1 * 12 * 30 * 24 * 3600);
            $this->access_token = $data->access_token;
            $this->access_token_timeout_at = date("Y-m-d H:i:s", time() + $data->expires_in - 2 * 24 * 3600);
            
            $this->save();
        }
    }
}

抓Listing:

use Wish\WishClient;

$access_token = 'an_example_access_token';

$client = new WishClient($access_token,'prod');
$products = $client->getAllProducts();
echo "You have ".count($products)." products!\n";

注意,这段代码中间是可能抛出异常的,比如Access Token过期等,需要注意捕捉。