~ubuntu-branches/ubuntu/trusty/python-boto/trusty

« back to all changes in this revision

Viewing changes to boto/fps/connection.py

  • Committer: Package Import Robot
  • Author(s): Eric Evans
  • Date: 2013-05-10 23:38:14 UTC
  • mfrom: (1.1.10) (14.1.2 experimental)
  • Revision ID: package-import@ubuntu.com-20130510233814-701dvlop7xfh88i7
Tags: 2.9.2-1
New upstream release (Closes: #700743).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2012 Andy Davidoff http://www.disruptek.com/
 
2
# Copyright (c) 2010 Jason R. Coombs http://www.jaraco.com/
1
3
# Copyright (c) 2008 Chris Moyer http://coredumped.org/
2
 
# Copyringt (c) 2010 Jason R. Coombs http://www.jaraco.com/
3
4
#
4
5
# Permission is hereby granted, free of charge, to any person obtaining a
5
6
# copy of this software and associated documentation files (the
15
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
17
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17
18
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18
 
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 
19
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19
20
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
21
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
22
# IN THE SOFTWARE.
22
23
 
23
24
import urllib
24
 
import xml.sax
25
25
import uuid
26
 
import boto
27
 
import boto.utils
28
 
from boto import handler
29
26
from boto.connection import AWSQueryConnection
30
 
from boto.resultset import ResultSet
31
 
from boto.exception import FPSResponseError
32
 
from boto.fps.response import FPSResponse
 
27
from boto.fps.exception import ResponseErrorFactory
 
28
from boto.fps.response import ResponseFactory
 
29
import boto.fps.response
 
30
 
 
31
__all__ = ['FPSConnection']
 
32
 
 
33
decorated_attrs = ('action', 'response')
 
34
 
 
35
 
 
36
def add_attrs_from(func, to):
 
37
    for attr in decorated_attrs:
 
38
        setattr(to, attr, getattr(func, attr, None))
 
39
    return to
 
40
 
 
41
 
 
42
def complex_amounts(*fields):
 
43
    def decorator(func):
 
44
        def wrapper(self, *args, **kw):
 
45
            for field in filter(kw.has_key, fields):
 
46
                amount = kw.pop(field)
 
47
                kw[field + '.Value'] = getattr(amount, 'Value', str(amount))
 
48
                kw[field + '.CurrencyCode'] = getattr(amount, 'CurrencyCode',
 
49
                                                      self.currencycode)
 
50
            return func(self, *args, **kw)
 
51
        wrapper.__doc__ = "{0}\nComplex Amounts: {1}".format(func.__doc__,
 
52
                                                 ', '.join(fields))
 
53
        return add_attrs_from(func, to=wrapper)
 
54
    return decorator
 
55
 
 
56
 
 
57
def requires(*groups):
 
58
 
 
59
    def decorator(func):
 
60
 
 
61
        def wrapper(*args, **kw):
 
62
            hasgroup = lambda x: len(x) == len(filter(kw.has_key, x))
 
63
            if 1 != len(filter(hasgroup, groups)):
 
64
                message = ' OR '.join(['+'.join(g) for g in groups])
 
65
                message = "{0} requires {1} argument(s)" \
 
66
                          "".format(getattr(func, 'action', 'Method'), message)
 
67
                raise KeyError(message)
 
68
            return func(*args, **kw)
 
69
        message = ' OR '.join(['+'.join(g) for g in groups])
 
70
        wrapper.__doc__ = "{0}\nRequired: {1}".format(func.__doc__,
 
71
                                                           message)
 
72
        return add_attrs_from(func, to=wrapper)
 
73
    return decorator
 
74
 
 
75
 
 
76
def needs_caller_reference(func):
 
77
 
 
78
    def wrapper(*args, **kw):
 
79
        kw.setdefault('CallerReference', uuid.uuid4())
 
80
        return func(*args, **kw)
 
81
    wrapper.__doc__ = "{0}\nUses CallerReference, defaults " \
 
82
                      "to uuid.uuid4()".format(func.__doc__)
 
83
    return add_attrs_from(func, to=wrapper)
 
84
 
 
85
 
 
86
def api_action(*api):
 
87
 
 
88
    def decorator(func):
 
89
        action = ''.join(api or map(str.capitalize, func.func_name.split('_')))
 
90
        response = ResponseFactory(action)
 
91
        if hasattr(boto.fps.response, action + 'Response'):
 
92
            response = getattr(boto.fps.response, action + 'Response')
 
93
 
 
94
        def wrapper(self, *args, **kw):
 
95
            return func(self, action, response, *args, **kw)
 
96
        wrapper.action, wrapper.response = action, response
 
97
        wrapper.__doc__ = "FPS {0} API call\n{1}".format(action,
 
98
                                                         func.__doc__)
 
99
        return wrapper
 
100
    return decorator
 
101
 
33
102
 
34
103
class FPSConnection(AWSQueryConnection):
35
104
 
36
105
    APIVersion = '2010-08-28'
37
 
 
38
 
    def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
39
 
                 is_secure=True, port=None, proxy=None, proxy_port=None,
40
 
                 proxy_user=None, proxy_pass=None,
41
 
                 host='fps.sandbox.amazonaws.com', debug=0,
42
 
                 https_connection_factory=None, path="/"):
43
 
        AWSQueryConnection.__init__(self, aws_access_key_id,
44
 
                                    aws_secret_access_key,
45
 
                                    is_secure, port, proxy, proxy_port,
46
 
                                    proxy_user, proxy_pass, host, debug,
47
 
                                    https_connection_factory, path)
48
 
    
 
106
    ResponseError = ResponseErrorFactory
 
107
    currencycode = 'USD'
 
108
 
 
109
    def __init__(self, *args, **kw):
 
110
        self.currencycode = kw.pop('CurrencyCode', self.currencycode)
 
111
        kw.setdefault('host', 'fps.sandbox.amazonaws.com')
 
112
        AWSQueryConnection.__init__(self, *args, **kw)
 
113
 
49
114
    def _required_auth_capability(self):
50
115
        return ['fps']
51
116
 
52
 
    def install_payment_instruction(self, instruction,
53
 
                                    token_type="Unrestricted",
54
 
                                    transaction_id=None):
55
 
        """
56
 
        InstallPaymentInstruction
57
 
        instruction: The PaymentInstruction to send, for example: 
58
 
        
59
 
            MyRole=='Caller' orSay 'Roles do not match';
60
 
        
61
 
        token_type: Defaults to "Unrestricted"
62
 
        transaction_id: Defaults to a new ID
63
 
        """
64
 
 
65
 
        if(transaction_id == None):
66
 
            transaction_id = uuid.uuid4()
67
 
        params = {}
68
 
        params['PaymentInstruction'] = instruction
69
 
        params['TokenType'] = token_type
70
 
        params['CallerReference'] = transaction_id
71
 
        response = self.make_request("InstallPaymentInstruction", params)
72
 
        return response
73
 
    
74
 
    def install_caller_instruction(self, token_type="Unrestricted",
75
 
                                   transaction_id=None):
76
 
        """
77
 
        Set us up as a caller
78
 
        This will install a new caller_token into the FPS section.
79
 
        This should really only be called to regenerate the caller token.
80
 
        """
81
 
        response = self.install_payment_instruction("MyRole=='Caller';",
82
 
                                                    token_type=token_type,
83
 
                                                    transaction_id=transaction_id)
84
 
        body = response.read()
85
 
        if(response.status == 200):
86
 
            rs = ResultSet()
87
 
            h = handler.XmlHandler(rs, self)
88
 
            xml.sax.parseString(body, h)
89
 
            caller_token = rs.TokenId
90
 
            try:
91
 
                boto.config.save_system_option("FPS", "caller_token",
92
 
                                               caller_token)
93
 
            except(IOError):
94
 
                boto.config.save_user_option("FPS", "caller_token",
95
 
                                             caller_token)
96
 
            return caller_token
97
 
        else:
98
 
            raise FPSResponseError(response.status, response.reason, body)
99
 
 
100
 
    def install_recipient_instruction(self, token_type="Unrestricted",
101
 
                                      transaction_id=None):
102
 
        """
103
 
        Set us up as a Recipient
104
 
        This will install a new caller_token into the FPS section.
105
 
        This should really only be called to regenerate the recipient token.
106
 
        """
107
 
        response = self.install_payment_instruction("MyRole=='Recipient';",
108
 
                                                    token_type=token_type,
109
 
                                                    transaction_id=transaction_id)
110
 
        body = response.read()
111
 
        if(response.status == 200):
112
 
            rs = ResultSet()
113
 
            h = handler.XmlHandler(rs, self)
114
 
            xml.sax.parseString(body, h)
115
 
            recipient_token = rs.TokenId
116
 
            try:
117
 
                boto.config.save_system_option("FPS", "recipient_token",
118
 
                                               recipient_token)
119
 
            except(IOError):
120
 
                boto.config.save_user_option("FPS", "recipient_token",
121
 
                                             recipient_token)
122
 
 
123
 
            return recipient_token
124
 
        else:
125
 
            raise FPSResponseError(response.status, response.reason, body)
126
 
 
127
 
    def make_marketplace_registration_url(self, returnURL, pipelineName,
128
 
                                          maxFixedFee=0.0, maxVariableFee=0.0,
129
 
                                          recipientPaysFee=True, **params):  
130
 
        """
131
 
        Generate the URL with the signature required for signing up a recipient
132
 
        """
133
 
        # use the sandbox authorization endpoint if we're using the
134
 
        #  sandbox for API calls.
135
 
        endpoint_host = 'authorize.payments.amazon.com'
136
 
        if 'sandbox' in self.host:
137
 
            endpoint_host = 'authorize.payments-sandbox.amazon.com'
138
 
        base = "/cobranded-ui/actions/start"
139
 
 
140
 
        params['callerKey'] = str(self.aws_access_key_id)
141
 
        params['returnURL'] = str(returnURL)
142
 
        params['pipelineName'] = str(pipelineName)
143
 
        params['maxFixedFee'] = str(maxFixedFee)
144
 
        params['maxVariableFee'] = str(maxVariableFee)
145
 
        params['recipientPaysFee'] = str(recipientPaysFee)
146
 
        params["signatureMethod"] = 'HmacSHA256'
147
 
        params["signatureVersion"] = '2'
148
 
 
149
 
        if(not params.has_key('callerReference')):
150
 
            params['callerReference'] = str(uuid.uuid4())
151
 
 
152
 
        parts = ''
153
 
        for k in sorted(params.keys()):
154
 
            parts += "&%s=%s" % (k, urllib.quote(params[k], '~'))
155
 
 
156
 
        canonical = '\n'.join(['GET',
157
 
                               str(endpoint_host).lower(),
158
 
                               base,
159
 
                               parts[1:]])
160
 
 
161
 
        signature = self._auth_handler.sign_string(canonical)
162
 
        params["signature"] = signature
163
 
 
164
 
        urlsuffix = ''
165
 
        for k in sorted(params.keys()):
166
 
            urlsuffix += "&%s=%s" % (k, urllib.quote(params[k], '~'))
167
 
        urlsuffix = urlsuffix[1:] # strip the first &
168
 
        
169
 
        fmt = "https://%(endpoint_host)s%(base)s?%(urlsuffix)s"
170
 
        final = fmt % vars()
171
 
        return final
172
 
 
173
 
 
174
 
    def make_url(self, returnURL, paymentReason, pipelineName,
175
 
                 transactionAmount, **params):
176
 
        """
177
 
        Generate the URL with the signature required for a transaction
178
 
        """
179
 
        # use the sandbox authorization endpoint if we're using the
180
 
        #  sandbox for API calls.
181
 
        endpoint_host = 'authorize.payments.amazon.com'
182
 
        if 'sandbox' in self.host:
183
 
            endpoint_host = 'authorize.payments-sandbox.amazon.com'
184
 
        base = "/cobranded-ui/actions/start"
185
 
 
186
 
        params['callerKey'] = str(self.aws_access_key_id)
187
 
        params['returnURL'] = str(returnURL)
188
 
        params['paymentReason'] = str(paymentReason)
189
 
        params['pipelineName'] = pipelineName
190
 
        params['transactionAmount'] = transactionAmount
191
 
        params["signatureMethod"] = 'HmacSHA256'
192
 
        params["signatureVersion"] = '2'
193
 
        
194
 
        if(not params.has_key('callerReference')):
195
 
            params['callerReference'] = str(uuid.uuid4())
196
 
 
197
 
        parts = ''
198
 
        for k in sorted(params.keys()):
199
 
            parts += "&%s=%s" % (k, urllib.quote(params[k], '~'))
200
 
 
201
 
        canonical = '\n'.join(['GET',
202
 
                               str(endpoint_host).lower(),
203
 
                               base,
204
 
                               parts[1:]])
205
 
 
206
 
        signature = self._auth_handler.sign_string(canonical)
207
 
        params["signature"] = signature
208
 
 
209
 
        urlsuffix = ''
210
 
        for k in sorted(params.keys()):
211
 
            urlsuffix += "&%s=%s" % (k, urllib.quote(params[k], '~'))
212
 
        urlsuffix = urlsuffix[1:] # strip the first &
213
 
        
214
 
        fmt = "https://%(endpoint_host)s%(base)s?%(urlsuffix)s"
215
 
        final = fmt % vars()
216
 
        return final
217
 
 
218
 
    def pay(self, transactionAmount, senderTokenId,
219
 
            recipientTokenId=None,
220
 
            chargeFeeTo="Recipient",
221
 
            callerReference=None, senderReference=None, recipientReference=None,
222
 
            senderDescription=None, recipientDescription=None,
223
 
            callerDescription=None, metadata=None,
224
 
            transactionDate=None, reserve=False):
225
 
        """
226
 
        Make a payment transaction. You must specify the amount.
227
 
        This can also perform a Reserve request if 'reserve' is set to True.
228
 
        """
229
 
        params = {}
230
 
        params['SenderTokenId'] = senderTokenId
231
 
        # this is for 2010-08-28 specification
232
 
        params['TransactionAmount.Value'] = str(transactionAmount)
233
 
        params['TransactionAmount.CurrencyCode'] = "USD"
234
 
        params['ChargeFeeTo'] = chargeFeeTo
235
 
 
236
 
        if recipientTokenId:
237
 
            params['RecipientTokenId'] = (
238
 
                recipientTokenId if recipientTokenId is not None
239
 
                else boto.config.get("FPS", "recipient_token")
240
 
                )
241
 
        if(transactionDate != None):
242
 
            params['TransactionDate'] = transactionDate
243
 
        if(senderReference != None):
244
 
            params['SenderReference'] = senderReference
245
 
        if(recipientReference != None):
246
 
            params['RecipientReference'] = recipientReference
247
 
        if(senderDescription != None):
248
 
            params['SenderDescription'] = senderDescription
249
 
        if(recipientDescription != None):
250
 
            params['RecipientDescription'] = recipientDescription
251
 
        if(callerDescription != None):
252
 
            params['CallerDescription'] = callerDescription
253
 
        if(metadata != None):
254
 
            params['MetaData'] = metadata
255
 
        if(callerReference == None):
256
 
            callerReference = uuid.uuid4()
257
 
        params['CallerReference'] = callerReference
258
 
        
259
 
        if reserve:
260
 
            action = "Reserve"
261
 
        else:
262
 
            action = "Pay"
263
 
        response = self.make_request(action, params)
264
 
        body = response.read()
265
 
        if(response.status == 200):
266
 
            rs = ResultSet([("%sResponse" %action, FPSResponse)])
267
 
            h = handler.XmlHandler(rs, self)
268
 
            xml.sax.parseString(body, h)
269
 
            return rs
270
 
        else:
271
 
            raise FPSResponseError(response.status, response.reason, body)
272
 
    
273
 
    def get_transaction_status(self, transactionId):
274
 
        """
275
 
        Returns the status of a given transaction.
276
 
        """
277
 
        params = {}
278
 
        params['TransactionId'] = transactionId
279
 
    
280
 
        response = self.make_request("GetTransactionStatus", params)
281
 
        body = response.read()
282
 
        if(response.status == 200):
283
 
            rs = ResultSet([("GetTransactionStatusResponse", FPSResponse)])
284
 
            h = handler.XmlHandler(rs, self)
285
 
            xml.sax.parseString(body, h)
286
 
            return rs
287
 
        else:
288
 
            raise FPSResponseError(response.status, response.reason, body)
289
 
    
290
 
    def cancel(self, transactionId, description=None):
291
 
        """
292
 
        Cancels a reserved or pending transaction.
293
 
        """
294
 
        params = {}
295
 
        params['TransactionId'] = transactionId
296
 
        if(description != None):
297
 
            params['description'] = description
298
 
        
299
 
        response = self.make_request("Cancel", params)
300
 
        body = response.read()
301
 
        if(response.status == 200):
302
 
            rs = ResultSet([("CancelResponse", FPSResponse)])
303
 
            h = handler.XmlHandler(rs, self)
304
 
            xml.sax.parseString(body, h)
305
 
            return rs
306
 
        else:
307
 
            raise FPSResponseError(response.status, response.reason, body)
308
 
    
309
 
    def settle(self, reserveTransactionId, transactionAmount=None):
310
 
        """
311
 
        Charges for a reserved payment.
312
 
        """
313
 
        params = {}
314
 
        params['ReserveTransactionId'] = reserveTransactionId
315
 
        if(transactionAmount != None):
316
 
            params['TransactionAmount.Value'] = transactionAmount
317
 
            params['TransactionAmount.CurrencyCode'] = "USD"
318
 
        
319
 
        response = self.make_request("Settle", params)
320
 
        body = response.read()
321
 
        if(response.status == 200):
322
 
            rs = ResultSet([("SettleResponse", FPSResponse)])
323
 
            h = handler.XmlHandler(rs, self)
324
 
            xml.sax.parseString(body, h)
325
 
            return rs
326
 
        else:
327
 
            raise FPSResponseError(response.status, response.reason, body)
328
 
    
329
 
    def refund(self, callerReference, transactionId, refundAmount=None,
330
 
               callerDescription=None):
331
 
        """
332
 
        Refund a transaction. This refunds the full amount by default
333
 
        unless 'refundAmount' is specified.
334
 
        """
335
 
        params = {}
336
 
        params['CallerReference'] = callerReference
337
 
        params['TransactionId'] = transactionId
338
 
        if(refundAmount != None):
339
 
            params['RefundAmount.Value'] = refundAmount
340
 
            params['RefundAmount.CurrencyCode'] = "USD"
341
 
        if(callerDescription != None):
342
 
            params['CallerDescription'] = callerDescription
343
 
        
344
 
        response = self.make_request("Refund", params)
345
 
        body = response.read()
346
 
        if(response.status == 200):
347
 
            rs = ResultSet([("RefundResponse", FPSResponse)])
348
 
            h = handler.XmlHandler(rs, self)
349
 
            xml.sax.parseString(body, h)
350
 
            return rs
351
 
        else:
352
 
            raise FPSResponseError(response.status, response.reason, body)
353
 
    
354
 
    def get_recipient_verification_status(self, recipientTokenId):
355
 
        """
356
 
        Test that the intended recipient has a verified Amazon Payments account.
357
 
        """
358
 
        params ={}
359
 
        params['RecipientTokenId'] = recipientTokenId
360
 
        
361
 
        response = self.make_request("GetRecipientVerificationStatus", params)
362
 
        body = response.read()
363
 
        if(response.status == 200):
364
 
            rs = ResultSet()
365
 
            h = handler.XmlHandler(rs, self)
366
 
            xml.sax.parseString(body, h)
367
 
            return rs
368
 
        else:
369
 
            raise FPSResponseError(response.status, response.reason, body)
370
 
    
371
 
    def get_token_by_caller_reference(self, callerReference):
372
 
        """
373
 
        Returns details about the token specified by 'CallerReference'.
374
 
        """
375
 
        params ={}
376
 
        params['CallerReference'] = callerReference
377
 
        
378
 
        response = self.make_request("GetTokenByCaller", params)
379
 
        body = response.read()
380
 
        if(response.status == 200):
381
 
            rs = ResultSet()
382
 
            h = handler.XmlHandler(rs, self)
383
 
            xml.sax.parseString(body, h)
384
 
            return rs
385
 
        else:
386
 
            raise FPSResponseError(response.status, response.reason, body)
387
 
 
388
 
    def get_token_by_caller_token(self, tokenId):
389
 
        """
390
 
        Returns details about the token specified by 'TokenId'.
391
 
        """
392
 
        params ={}
393
 
        params['TokenId'] = tokenId
394
 
        
395
 
        response = self.make_request("GetTokenByCaller", params)
396
 
        body = response.read()
397
 
        if(response.status == 200):
398
 
            rs = ResultSet()
399
 
            h = handler.XmlHandler(rs, self)
400
 
            xml.sax.parseString(body, h)
401
 
            return rs
402
 
        else:
403
 
            raise FPSResponseError(response.status, response.reason, body)
404
 
 
405
 
    def verify_signature(self, end_point_url, http_parameters):
406
 
        params = dict(
407
 
            UrlEndPoint = end_point_url,
408
 
            HttpParameters = http_parameters,
409
 
            )
410
 
        response = self.make_request("VerifySignature", params)
411
 
        body = response.read()
412
 
        if(response.status != 200):
413
 
            raise FPSResponseError(response.status, response.reason, body)
414
 
        rs = ResultSet([("VerifySignatureResponse", FPSResponse)])
415
 
        h = handler.XmlHandler(rs, self)
416
 
        xml.sax.parseString(body, h)
417
 
        return rs
 
117
    @needs_caller_reference
 
118
    @complex_amounts('SettlementAmount')
 
119
    @requires(['CreditInstrumentId', 'SettlementAmount.Value',
 
120
               'SenderTokenId',      'SettlementAmount.CurrencyCode'])
 
121
    @api_action()
 
122
    def settle_debt(self, action, response, **kw):
 
123
        """
 
124
        Allows a caller to initiate a transaction that atomically transfers
 
125
        money from a sender's payment instrument to the recipient, while
 
126
        decreasing corresponding debt balance.
 
127
        """
 
128
        return self.get_object(action, kw, response)
 
129
 
 
130
    @requires(['TransactionId'])
 
131
    @api_action()
 
132
    def get_transaction_status(self, action, response, **kw):
 
133
        """
 
134
        Gets the latest status of a transaction.
 
135
        """
 
136
        return self.get_object(action, kw, response)
 
137
 
 
138
    @requires(['StartDate'])
 
139
    @api_action()
 
140
    def get_account_activity(self, action, response, **kw):
 
141
        """
 
142
        Returns transactions for a given date range.
 
143
        """
 
144
        return self.get_object(action, kw, response)
 
145
 
 
146
    @requires(['TransactionId'])
 
147
    @api_action()
 
148
    def get_transaction(self, action, response, **kw):
 
149
        """
 
150
        Returns all details of a transaction.
 
151
        """
 
152
        return self.get_object(action, kw, response)
 
153
 
 
154
    @api_action()
 
155
    def get_outstanding_debt_balance(self, action, response):
 
156
        """
 
157
        Returns the total outstanding balance for all the credit instruments
 
158
        for the given creditor account.
 
159
        """
 
160
        return self.get_object(action, {}, response)
 
161
 
 
162
    @requires(['PrepaidInstrumentId'])
 
163
    @api_action()
 
164
    def get_prepaid_balance(self, action, response, **kw):
 
165
        """
 
166
        Returns the balance available on the given prepaid instrument.
 
167
        """
 
168
        return self.get_object(action, kw, response)
 
169
 
 
170
    @api_action()
 
171
    def get_total_prepaid_liability(self, action, response):
 
172
        """
 
173
        Returns the total liability held by the given account corresponding to
 
174
        all the prepaid instruments owned by the account.
 
175
        """
 
176
        return self.get_object(action, {}, response)
 
177
 
 
178
    @api_action()
 
179
    def get_account_balance(self, action, response):
 
180
        """
 
181
        Returns the account balance for an account in real time.
 
182
        """
 
183
        return self.get_object(action, {}, response)
 
184
 
 
185
    @needs_caller_reference
 
186
    @requires(['PaymentInstruction', 'TokenType'])
 
187
    @api_action()
 
188
    def install_payment_instruction(self, action, response, **kw):
 
189
        """
 
190
        Installs a payment instruction for caller.
 
191
        """
 
192
        return self.get_object(action, kw, response)
 
193
 
 
194
    @needs_caller_reference
 
195
    @requires(['returnURL', 'pipelineName'])
 
196
    def cbui_url(self, **kw):
 
197
        """
 
198
        Generate a signed URL for the Co-Branded service API given arguments as
 
199
        payload.
 
200
        """
 
201
        sandbox = 'sandbox' in self.host and 'payments-sandbox' or 'payments'
 
202
        endpoint = 'authorize.{0}.amazon.com'.format(sandbox)
 
203
        base = '/cobranded-ui/actions/start'
 
204
 
 
205
        validpipelines = ('SingleUse', 'MultiUse', 'Recurring', 'Recipient',
 
206
                          'SetupPrepaid', 'SetupPostpaid', 'EditToken')
 
207
        assert kw['pipelineName'] in validpipelines, "Invalid pipelineName"
 
208
        kw.update({
 
209
            'signatureMethod':  'HmacSHA256',
 
210
            'signatureVersion': '2',
 
211
        })
 
212
        kw.setdefault('callerKey', self.aws_access_key_id)
 
213
 
 
214
        safestr = lambda x: x is not None and str(x) or ''
 
215
        safequote = lambda x: urllib.quote(safestr(x), safe='~')
 
216
        payload = sorted([(k, safequote(v)) for k, v in kw.items()])
 
217
 
 
218
        encoded = lambda p: '&'.join([k + '=' + v for k, v in p])
 
219
        canonical = '\n'.join(['GET', endpoint, base, encoded(payload)])
 
220
        signature = self._auth_handler.sign_string(canonical)
 
221
        payload += [('signature', safequote(signature))]
 
222
        payload.sort()
 
223
 
 
224
        return 'https://{0}{1}?{2}'.format(endpoint, base, encoded(payload))
 
225
 
 
226
    @needs_caller_reference
 
227
    @complex_amounts('TransactionAmount')
 
228
    @requires(['SenderTokenId', 'TransactionAmount.Value',
 
229
                                'TransactionAmount.CurrencyCode'])
 
230
    @api_action()
 
231
    def reserve(self, action, response, **kw):
 
232
        """
 
233
        Reserve API is part of the Reserve and Settle API conjunction that
 
234
        serve the purpose of a pay where the authorization and settlement have
 
235
        a timing difference.
 
236
        """
 
237
        return self.get_object(action, kw, response)
 
238
 
 
239
    @needs_caller_reference
 
240
    @complex_amounts('TransactionAmount')
 
241
    @requires(['SenderTokenId', 'TransactionAmount.Value',
 
242
                                'TransactionAmount.CurrencyCode'])
 
243
    @api_action()
 
244
    def pay(self, action, response, **kw):
 
245
        """
 
246
        Allows calling applications to move money from a sender to a recipient.
 
247
        """
 
248
        return self.get_object(action, kw, response)
 
249
 
 
250
    @requires(['TransactionId'])
 
251
    @api_action()
 
252
    def cancel(self, action, response, **kw):
 
253
        """
 
254
        Cancels an ongoing transaction and puts it in cancelled state.
 
255
        """
 
256
        return self.get_object(action, kw, response)
 
257
 
 
258
    @complex_amounts('TransactionAmount')
 
259
    @requires(['ReserveTransactionId', 'TransactionAmount.Value',
 
260
                                       'TransactionAmount.CurrencyCode'])
 
261
    @api_action()
 
262
    def settle(self, action, response, **kw):
 
263
        """
 
264
        The Settle API is used in conjunction with the Reserve API and is used
 
265
        to settle previously reserved transaction.
 
266
        """
 
267
        return self.get_object(action, kw, response)
 
268
 
 
269
    @complex_amounts('RefundAmount')
 
270
    @requires(['TransactionId',   'RefundAmount.Value',
 
271
               'CallerReference', 'RefundAmount.CurrencyCode'])
 
272
    @api_action()
 
273
    def refund(self, action, response, **kw):
 
274
        """
 
275
        Refunds a previously completed transaction.
 
276
        """
 
277
        return self.get_object(action, kw, response)
 
278
 
 
279
    @requires(['RecipientTokenId'])
 
280
    @api_action()
 
281
    def get_recipient_verification_status(self, action, response, **kw):
 
282
        """
 
283
        Returns the recipient status.
 
284
        """
 
285
        return self.get_object(action, kw, response)
 
286
 
 
287
    @requires(['CallerReference'], ['TokenId'])
 
288
    @api_action()
 
289
    def get_token_by_caller(self, action, response, **kw):
 
290
        """
 
291
        Returns the details of a particular token installed by this calling
 
292
        application using the subway co-branded UI.
 
293
        """
 
294
        return self.get_object(action, kw, response)
 
295
 
 
296
    @requires(['UrlEndPoint', 'HttpParameters'])
 
297
    @api_action()
 
298
    def verify_signature(self, action, response, **kw):
 
299
        """
 
300
        Verify the signature that FPS sent in IPN or callback urls.
 
301
        """
 
302
        return self.get_object(action, kw, response)
 
303
 
 
304
    @api_action()
 
305
    def get_tokens(self, action, response, **kw):
 
306
        """
 
307
        Returns a list of tokens installed on the given account.
 
308
        """
 
309
        return self.get_object(action, kw, response)
 
310
 
 
311
    @requires(['TokenId'])
 
312
    @api_action()
 
313
    def get_token_usage(self, action, response, **kw):
 
314
        """
 
315
        Returns the usage of a token.
 
316
        """
 
317
        return self.get_object(action, kw, response)
 
318
 
 
319
    @requires(['TokenId'])
 
320
    @api_action()
 
321
    def cancel_token(self, action, response, **kw):
 
322
        """
 
323
        Cancels any token installed by the calling application on its own
 
324
        account.
 
325
        """
 
326
        return self.get_object(action, kw, response)
 
327
 
 
328
    @needs_caller_reference
 
329
    @complex_amounts('FundingAmount')
 
330
    @requires(['PrepaidInstrumentId', 'FundingAmount.Value',
 
331
               'SenderTokenId',       'FundingAmount.CurrencyCode'])
 
332
    @api_action()
 
333
    def fund_prepaid(self, action, response, **kw):
 
334
        """
 
335
        Funds the prepaid balance on the given prepaid instrument.
 
336
        """
 
337
        return self.get_object(action, kw, response)
 
338
 
 
339
    @requires(['CreditInstrumentId'])
 
340
    @api_action()
 
341
    def get_debt_balance(self, action, response, **kw):
 
342
        """
 
343
        Returns the balance corresponding to the given credit instrument.
 
344
        """
 
345
        return self.get_object(action, kw, response)
 
346
 
 
347
    @needs_caller_reference
 
348
    @complex_amounts('AdjustmentAmount')
 
349
    @requires(['CreditInstrumentId', 'AdjustmentAmount.Value',
 
350
                                     'AdjustmentAmount.CurrencyCode'])
 
351
    @api_action()
 
352
    def write_off_debt(self, action, response, **kw):
 
353
        """
 
354
        Allows a creditor to write off the debt balance accumulated partially
 
355
        or fully at any time.
 
356
        """
 
357
        return self.get_object(action, kw, response)
 
358
 
 
359
    @requires(['SubscriptionId'])
 
360
    @api_action()
 
361
    def get_transactions_for_subscription(self, action, response, **kw):
 
362
        """
 
363
        Returns the transactions for a given subscriptionID.
 
364
        """
 
365
        return self.get_object(action, kw, response)
 
366
 
 
367
    @requires(['SubscriptionId'])
 
368
    @api_action()
 
369
    def get_subscription_details(self, action, response, **kw):
 
370
        """
 
371
        Returns the details of Subscription for a given subscriptionID.
 
372
        """
 
373
        return self.get_object(action, kw, response)
 
374
 
 
375
    @needs_caller_reference
 
376
    @complex_amounts('RefundAmount')
 
377
    @requires(['SubscriptionId'])
 
378
    @api_action()
 
379
    def cancel_subscription_and_refund(self, action, response, **kw):
 
380
        """
 
381
        Cancels a subscription.
 
382
        """
 
383
        message = "If you specify a RefundAmount, " \
 
384
                  "you must specify CallerReference."
 
385
        assert not 'RefundAmount.Value' in kw \
 
386
                or 'CallerReference' in kw, message
 
387
        return self.get_object(action, kw, response)
 
388
 
 
389
    @requires(['TokenId'])
 
390
    @api_action()
 
391
    def get_payment_instruction(self, action, response, **kw):
 
392
        """
 
393
        Gets the payment instruction of a token.
 
394
        """
 
395
        return self.get_object(action, kw, response)