跳出ping++退款的坑

近期在項目的開發過程當中,須要用到ping++的退款功能,因爲使用的版本比官方提供的要低2個小版本,所以問題並非很大。可是因爲官方文檔有些內容寫的比較含蓄,所以遇到了一些問題。
咱們能夠經過以下的方式來獲取SDK的版本:python

>>> import pingpp
>>> pingpp.VERSION
'2.0.7'
>>> pingpp.api_version
'2.0.7'

在官方文檔得說明中,咱們能夠看到這樣3句代碼:git

import pingpp
pingpp.api_key = 'sk_test_ibbTe5jLGCi5rzfH4OqPW9KC'
ch = pingpp.Charge.retrieve('CH-ID')
re = ch.refunds.create(description='desc', amount=1)

入坑

在這裏,咱們看到咱們先導入pingpp庫,而後經過賦值的方式將其傳入。而後咱們經過pingpp得Charge類的retrieve方法獲取給定ch_id,而後再根據其refunds屬性得create方法傳入關鍵字參數來實現退款的操做。
若是咱們沒有傳入api_key,將獲得1個AuthenticationError權限錯誤:github

AuthenticationError: No API key provided. (HINT: set your API key using "pingpp.api_key = <API-KEY>"). You can generate
 API keys from the Ping++ web interface.  See https://pingxx.com for details, or email support@pingxx.com if you have a
ny questions.

在這裏,我按照官方提供的方式進行賦值,直接就出現上面的狀況了,讓人比較納悶。web

出坑

下面咱們來看下其實現的源碼,在resource模塊下的Charge類繼承自3個類,分別爲CreateableAPIResource, ListableAPIResource,UpdateableAPIResource。其源碼以下:api

class Charge(CreateableAPIResource, ListableAPIResource,
             UpdateableAPIResource):
    def refund(self, **params):
        ...

而這3個父類繼承自APIResource類,而APIResource類繼承自PingppObject類,它是1個Pingpp對象。ide

class CreateableAPIResource(APIResource):
    @classmethod
    def create(cls, api_key=None, **params):
        requestor = api_requestor.APIRequestor(api_key)
        url = cls.class_url()
        response, api_key = requestor.request('post', url, params)
        return convert_to_pingpp_object(response, api_key)

class APIResource(PingppObject):
    @classmethod
    def retrieve(cls, id, api_key=None, **params):
        instance = cls(id, api_key, **params)
        instance.refresh()
        return instance

    def refresh(self):
        self.refresh_from(self.request('get', self.instance_url()))
        return self
    @classmethod
    def class_name(cls):
        ...
        return str(urllib.quote_plus(cls.__name__.lower()))

    @classmethod
    def class_url(cls):
        cls_name = cls.class_name()
        return "/v1/%ss" % (cls_name,)

    def instance_url(self):
        ...
        extn = urllib.quote_plus(id)
        return "%s/%s" % (base, extn)

從上述代碼,咱們能夠發現,咱們還能夠直接將api_key以關鍵字參數的形式傳入到retrieve方法中。
咱們調用Charge類的retrieve方法時,其會生成1個實例,而後調用該實例得refresh方法。然後調研該實例的refresh_from方法,使用get請求,而地址爲該實例的url。
所以,最終的url地址爲https://api.pingxx.com/v1/charges/,首先class_name方法返回Charge類名的小寫名稱,然後在class_url方法中進行組裝後返回給instance_url方法。
而在調用request方法的過程當中,咱們會涉及到1個convert_to_pingpp_object方法,其將響應的內容轉換爲pingpp對象。
經過這種方式咱們完成了官方文檔中查詢Charge對象的操做,即進行以下的操做:函數

GET https://api.pingxx.com/v1/charges/{CHARGE_ID}

所以,上面ch最終的結果爲咱們使用API調用後獲得的JSON數據結果,然後咱們經過ch的refunds屬性獲得這樣1個對象:post

...
   "refunds": {
    "url": "/v1/charges/ch_xxx/refunds", 
    "has_more": false,
    "object": "list",
    "data": [
      {
        ...
      }
    ]
  }

而這個轉換的過程是在refresh_from函數中進行的:ui

def refresh_from(self, values, api_key=None, partial=False):
        ...
        for k, v in values.iteritems():
            super(PingppObject, self).__setitem__(
                k, convert_to_pingpp_object(v, api_key))

然後咱們經過object屬性獲取到ch.refunds的結果爲list。經過以下的方式咱們獲得的ch.refunds爲1個ListObject:url

def convert_to_pingpp_object(resp,api_key):
    klass_name = resp.get('object')
    if isinstance(klass_name, basestring):
       klass = types.get(klass_name, PingppObject)

這樣,咱們在create方法中傳入的參數與API文檔中建立Refund對象的參數一一對應了。而這些傳入的參數將在調用api_requestor模塊中得APIRequestor類時傳入。其中,url爲refund對象中的url屬性,即上面的/v1/charges/ch_xxx/refunds
所以,第3行中的關鍵字參數description和amount正好對應官方文檔中的說明。須要提示的是,description參數只能是最大255個unicode字符,否則又會出現一些問題。

總結

其實ping++的SDK是與其API接口對應的,若是你在使用SDK的過程當中對其傳入的參數不明確,能夠查看API文檔相應篇章中的說明。否則你會遇到ping++平臺給你返回一些讓你摸不着頭腦的回覆。

原文:

http://yuki-onna.github.io/jump-out-of-the-refund-of-ping++/

參考文章:

https://www.pingxx.com/api#api-r-new
https://www.pingxx.com/guidance/server/charge/refund
https://github.com/PingPlusPlus/pingpp-python/blob/2.0.7/example/refund.py

相關文章
相關標籤/搜索