| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2008 Chris Moyer http://coredumped.org/ | |
| 2 # Copyringt (c) 2010 Jason R. Coombs http://www.jaraco.com/ | |
| 3 # | |
| 4 # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 # copy of this software and associated documentation files (the | |
| 6 # "Software"), to deal in the Software without restriction, including | |
| 7 # without limitation the rights to use, copy, modify, merge, publish, dis- | |
| 8 # tribute, sublicense, and/or sell copies of the Software, and to permit | |
| 9 # persons to whom the Software is furnished to do so, subject to the fol- | |
| 10 # lowing conditions: | |
| 11 # | |
| 12 # The above copyright notice and this permission notice shall be included | |
| 13 # in all copies or substantial portions of the Software. | |
| 14 # | |
| 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | |
| 17 # 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 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| 21 # IN THE SOFTWARE. | |
| 22 | |
| 23 import base64 | |
| 24 import hmac | |
| 25 import hashlib | |
| 26 import urllib | |
| 27 import xml.sax | |
| 28 import uuid | |
| 29 import boto | |
| 30 import boto.utils | |
| 31 from boto import handler | |
| 32 from boto.connection import AWSQueryConnection | |
| 33 from boto.resultset import ResultSet | |
| 34 from boto.exception import FPSResponseError | |
| 35 | |
| 36 class FPSConnection(AWSQueryConnection): | |
| 37 | |
| 38 APIVersion = '2007-01-08' | |
| 39 | |
| 40 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, | |
| 41 is_secure=True, port=None, proxy=None, proxy_port=None, | |
| 42 proxy_user=None, proxy_pass=None, | |
| 43 host='fps.sandbox.amazonaws.com', debug=0, | |
| 44 https_connection_factory=None, path="/"): | |
| 45 AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_k
ey, | |
| 46 is_secure, port, proxy, proxy_port, | |
| 47 proxy_user, proxy_pass, host, debug, | |
| 48 https_connection_factory, path) | |
| 49 | |
| 50 def _required_auth_capability(self): | |
| 51 return ['fps'] | |
| 52 | |
| 53 def install_payment_instruction(self, instruction, token_type="Unrestricted"
, transaction_id=None): | |
| 54 """ | |
| 55 InstallPaymentInstruction | |
| 56 instruction: The PaymentInstruction to send, for example: | |
| 57 | |
| 58 MyRole=='Caller' orSay 'Roles do not match'; | |
| 59 | |
| 60 token_type: Defaults to "Unrestricted" | |
| 61 transaction_id: Defaults to a new ID | |
| 62 """ | |
| 63 | |
| 64 if(transaction_id == None): | |
| 65 transaction_id = uuid.uuid4() | |
| 66 params = {} | |
| 67 params['PaymentInstruction'] = instruction | |
| 68 params['TokenType'] = token_type | |
| 69 params['CallerReference'] = transaction_id | |
| 70 response = self.make_request("InstallPaymentInstruction", params) | |
| 71 return response | |
| 72 | |
| 73 def install_caller_instruction(self, token_type="Unrestricted", transaction_
id=None): | |
| 74 """ | |
| 75 Set us up as a caller | |
| 76 This will install a new caller_token into the FPS section. | |
| 77 This should really only be called to regenerate the caller token. | |
| 78 """ | |
| 79 response = self.install_payment_instruction("MyRole=='Caller';", token_t
ype=token_type, transaction_id=transaction_id) | |
| 80 body = response.read() | |
| 81 if(response.status == 200): | |
| 82 rs = ResultSet() | |
| 83 h = handler.XmlHandler(rs, self) | |
| 84 xml.sax.parseString(body, h) | |
| 85 caller_token = rs.TokenId | |
| 86 try: | |
| 87 boto.config.save_system_option("FPS", "caller_token", caller_tok
en) | |
| 88 except(IOError): | |
| 89 boto.config.save_user_option("FPS", "caller_token", caller_token
) | |
| 90 return caller_token | |
| 91 else: | |
| 92 raise FPSResponseError(response.status, response.reason, body) | |
| 93 | |
| 94 def install_recipient_instruction(self, token_type="Unrestricted", transacti
on_id=None): | |
| 95 """ | |
| 96 Set us up as a Recipient | |
| 97 This will install a new caller_token into the FPS section. | |
| 98 This should really only be called to regenerate the recipient token. | |
| 99 """ | |
| 100 response = self.install_payment_instruction("MyRole=='Recipient';", toke
n_type=token_type, transaction_id=transaction_id) | |
| 101 body = response.read() | |
| 102 if(response.status == 200): | |
| 103 rs = ResultSet() | |
| 104 h = handler.XmlHandler(rs, self) | |
| 105 xml.sax.parseString(body, h) | |
| 106 recipient_token = rs.TokenId | |
| 107 try: | |
| 108 boto.config.save_system_option("FPS", "recipient_token", recipie
nt_token) | |
| 109 except(IOError): | |
| 110 boto.config.save_user_option("FPS", "recipient_token", recipient
_token) | |
| 111 | |
| 112 return recipient_token | |
| 113 else: | |
| 114 raise FPSResponseError(response.status, response.reason, body) | |
| 115 | |
| 116 def make_marketplace_registration_url(self, returnURL, pipelineName, maxFixe
dFee=0.0, maxVariableFee=0.0, recipientPaysFee='True', **params): | |
| 117 """ | |
| 118 Generate the URL with the signature required for signing up a recipient | |
| 119 """ | |
| 120 # use the sandbox authorization endpoint if we're using the | |
| 121 # sandbox for API calls. | |
| 122 endpoint_host = 'authorize.payments.amazon.com' | |
| 123 if 'sandbox' in self.host: | |
| 124 endpoint_host = 'authorize.payments-sandbox.amazon.com' | |
| 125 base = "/cobranded-ui/actions/start" | |
| 126 | |
| 127 params['callerKey'] = str(self.aws_access_key_id) | |
| 128 params['returnURL'] = str(returnURL) | |
| 129 params['pipelineName'] = str(pipelineName) | |
| 130 params['maxFixedFee'] = str(maxFixedFee) | |
| 131 params['maxVariableFee'] = str(maxVariableFee) | |
| 132 params['recipientPaysFee'] = str(recipientPaysFee) | |
| 133 params["signatureMethod"] = 'HmacSHA256' | |
| 134 params["signatureVersion"] = '2' | |
| 135 | |
| 136 if(not params.has_key('callerReference')): | |
| 137 params['callerReference'] = str(uuid.uuid4()) | |
| 138 | |
| 139 parts = '' | |
| 140 for k in sorted(params.keys()): | |
| 141 parts += "&%s=%s" % (k, urllib.quote(params[k], '~')) | |
| 142 | |
| 143 canonical = '\n'.join(['GET', | |
| 144 str(endpoint_host).lower(), | |
| 145 base, | |
| 146 parts[1:]]) | |
| 147 | |
| 148 signature = self._auth_handler.sign_string(canonical) | |
| 149 params["signature"] = signature | |
| 150 | |
| 151 urlsuffix = '' | |
| 152 for k in sorted(params.keys()): | |
| 153 urlsuffix += "&%s=%s" % (k, urllib.quote(params[k], '~')) | |
| 154 urlsuffix = urlsuffix[1:] # strip the first & | |
| 155 | |
| 156 fmt = "https://%(endpoint_host)s%(base)s?%(urlsuffix)s" | |
| 157 final = fmt % vars() | |
| 158 return final | |
| 159 | |
| 160 | |
| 161 def make_url(self, returnURL, paymentReason, pipelineName, transactionAmount
, **params): | |
| 162 """ | |
| 163 Generate the URL with the signature required for a transaction | |
| 164 """ | |
| 165 # use the sandbox authorization endpoint if we're using the | |
| 166 # sandbox for API calls. | |
| 167 endpoint_host = 'authorize.payments.amazon.com' | |
| 168 if 'sandbox' in self.host: | |
| 169 endpoint_host = 'authorize.payments-sandbox.amazon.com' | |
| 170 base = "/cobranded-ui/actions/start" | |
| 171 | |
| 172 params['callerKey'] = str(self.aws_access_key_id) | |
| 173 params['returnURL'] = str(returnURL) | |
| 174 params['paymentReason'] = str(paymentReason) | |
| 175 params['pipelineName'] = pipelineName | |
| 176 params['transactionAmount'] = transactionAmount | |
| 177 params["signatureMethod"] = 'HmacSHA256' | |
| 178 params["signatureVersion"] = '2' | |
| 179 | |
| 180 if(not params.has_key('callerReference')): | |
| 181 params['callerReference'] = str(uuid.uuid4()) | |
| 182 | |
| 183 parts = '' | |
| 184 for k in sorted(params.keys()): | |
| 185 parts += "&%s=%s" % (k, urllib.quote(params[k], '~')) | |
| 186 | |
| 187 canonical = '\n'.join(['GET', | |
| 188 str(endpoint_host).lower(), | |
| 189 base, | |
| 190 parts[1:]]) | |
| 191 | |
| 192 signature = self._auth_handler.sign_string(canonical) | |
| 193 params["signature"] = signature | |
| 194 | |
| 195 urlsuffix = '' | |
| 196 for k in sorted(params.keys()): | |
| 197 urlsuffix += "&%s=%s" % (k, urllib.quote(params[k], '~')) | |
| 198 urlsuffix = urlsuffix[1:] # strip the first & | |
| 199 | |
| 200 fmt = "https://%(endpoint_host)s%(base)s?%(urlsuffix)s" | |
| 201 final = fmt % vars() | |
| 202 return final | |
| 203 | |
| 204 def pay(self, transactionAmount, senderTokenId, | |
| 205 recipientTokenId=None, callerTokenId=None, | |
| 206 chargeFeeTo="Recipient", | |
| 207 callerReference=None, senderReference=None, recipientReference=None, | |
| 208 senderDescription=None, recipientDescription=None, callerDescription
=None, | |
| 209 metadata=None, transactionDate=None, reserve=False): | |
| 210 """ | |
| 211 Make a payment transaction. You must specify the amount. | |
| 212 This can also perform a Reserve request if 'reserve' is set to True. | |
| 213 """ | |
| 214 params = {} | |
| 215 params['SenderTokenId'] = senderTokenId | |
| 216 # this is for 2008-09-17 specification | |
| 217 params['TransactionAmount.Amount'] = str(transactionAmount) | |
| 218 params['TransactionAmount.CurrencyCode'] = "USD" | |
| 219 #params['TransactionAmount'] = str(transactionAmount) | |
| 220 params['ChargeFeeTo'] = chargeFeeTo | |
| 221 | |
| 222 params['RecipientTokenId'] = ( | |
| 223 recipientTokenId if recipientTokenId is not None | |
| 224 else boto.config.get("FPS", "recipient_token") | |
| 225 ) | |
| 226 params['CallerTokenId'] = ( | |
| 227 callerTokenId if callerTokenId is not None | |
| 228 else boto.config.get("FPS", "caller_token") | |
| 229 ) | |
| 230 if(transactionDate != None): | |
| 231 params['TransactionDate'] = transactionDate | |
| 232 if(senderReference != None): | |
| 233 params['SenderReference'] = senderReference | |
| 234 if(recipientReference != None): | |
| 235 params['RecipientReference'] = recipientReference | |
| 236 if(senderDescription != None): | |
| 237 params['SenderDescription'] = senderDescription | |
| 238 if(recipientDescription != None): | |
| 239 params['RecipientDescription'] = recipientDescription | |
| 240 if(callerDescription != None): | |
| 241 params['CallerDescription'] = callerDescription | |
| 242 if(metadata != None): | |
| 243 params['MetaData'] = metadata | |
| 244 if(callerReference == None): | |
| 245 callerReference = uuid.uuid4() | |
| 246 params['CallerReference'] = callerReference | |
| 247 | |
| 248 if reserve: | |
| 249 response = self.make_request("Reserve", params) | |
| 250 else: | |
| 251 response = self.make_request("Pay", params) | |
| 252 body = response.read() | |
| 253 if(response.status == 200): | |
| 254 rs = ResultSet() | |
| 255 h = handler.XmlHandler(rs, self) | |
| 256 xml.sax.parseString(body, h) | |
| 257 return rs | |
| 258 else: | |
| 259 raise FPSResponseError(response.status, response.reason, body) | |
| 260 | |
| 261 def get_transaction_status(self, transactionId): | |
| 262 """ | |
| 263 Returns the status of a given transaction. | |
| 264 """ | |
| 265 params = {} | |
| 266 params['TransactionId'] = transactionId | |
| 267 | |
| 268 response = self.make_request("GetTransactionStatus", params) | |
| 269 body = response.read() | |
| 270 if(response.status == 200): | |
| 271 rs = ResultSet() | |
| 272 h = handler.XmlHandler(rs, self) | |
| 273 xml.sax.parseString(body, h) | |
| 274 return rs | |
| 275 else: | |
| 276 raise FPSResponseError(response.status, response.reason, body) | |
| 277 | |
| 278 def cancel(self, transactionId, description=None): | |
| 279 """ | |
| 280 Cancels a reserved or pending transaction. | |
| 281 """ | |
| 282 params = {} | |
| 283 params['transactionId'] = transactionId | |
| 284 if(description != None): | |
| 285 params['description'] = description | |
| 286 | |
| 287 response = self.make_request("Cancel", params) | |
| 288 body = response.read() | |
| 289 if(response.status == 200): | |
| 290 rs = ResultSet() | |
| 291 h = handler.XmlHandler(rs, self) | |
| 292 xml.sax.parseString(body, h) | |
| 293 return rs | |
| 294 else: | |
| 295 raise FPSResponseError(response.status, response.reason, body) | |
| 296 | |
| 297 def settle(self, reserveTransactionId, transactionAmount=None): | |
| 298 """ | |
| 299 Charges for a reserved payment. | |
| 300 """ | |
| 301 params = {} | |
| 302 params['ReserveTransactionId'] = reserveTransactionId | |
| 303 if(transactionAmount != None): | |
| 304 params['TransactionAmount'] = transactionAmount | |
| 305 | |
| 306 response = self.make_request("Settle", params) | |
| 307 body = response.read() | |
| 308 if(response.status == 200): | |
| 309 rs = ResultSet() | |
| 310 h = handler.XmlHandler(rs, self) | |
| 311 xml.sax.parseString(body, h) | |
| 312 return rs | |
| 313 else: | |
| 314 raise FPSResponseError(response.status, response.reason, body) | |
| 315 | |
| 316 def refund(self, callerReference, transactionId, refundAmount=None, callerDe
scription=None): | |
| 317 """ | |
| 318 Refund a transaction. This refunds the full amount by default unless 're
fundAmount' is specified. | |
| 319 """ | |
| 320 params = {} | |
| 321 params['CallerReference'] = callerReference | |
| 322 params['TransactionId'] = transactionId | |
| 323 if(refundAmount != None): | |
| 324 params['RefundAmount'] = refundAmount | |
| 325 if(callerDescription != None): | |
| 326 params['CallerDescription'] = callerDescription | |
| 327 | |
| 328 response = self.make_request("Refund", params) | |
| 329 body = response.read() | |
| 330 if(response.status == 200): | |
| 331 rs = ResultSet() | |
| 332 h = handler.XmlHandler(rs, self) | |
| 333 xml.sax.parseString(body, h) | |
| 334 return rs | |
| 335 else: | |
| 336 raise FPSResponseError(response.status, response.reason, body) | |
| 337 | |
| 338 def get_recipient_verification_status(self, recipientTokenId): | |
| 339 """ | |
| 340 Test that the intended recipient has a verified Amazon Payments account. | |
| 341 """ | |
| 342 params ={} | |
| 343 params['RecipientTokenId'] = recipientTokenId | |
| 344 | |
| 345 response = self.make_request("GetRecipientVerificationStatus", params) | |
| 346 body = response.read() | |
| 347 if(response.status == 200): | |
| 348 rs = ResultSet() | |
| 349 h = handler.XmlHandler(rs, self) | |
| 350 xml.sax.parseString(body, h) | |
| 351 return rs | |
| 352 else: | |
| 353 raise FPSResponseError(response.status, response.reason, body) | |
| 354 | |
| 355 def get_token_by_caller_reference(self, callerReference): | |
| 356 """ | |
| 357 Returns details about the token specified by 'callerReference'. | |
| 358 """ | |
| 359 params ={} | |
| 360 params['callerReference'] = callerReference | |
| 361 | |
| 362 response = self.make_request("GetTokenByCaller", params) | |
| 363 body = response.read() | |
| 364 if(response.status == 200): | |
| 365 rs = ResultSet() | |
| 366 h = handler.XmlHandler(rs, self) | |
| 367 xml.sax.parseString(body, h) | |
| 368 return rs | |
| 369 else: | |
| 370 raise FPSResponseError(response.status, response.reason, body) | |
| 371 def get_token_by_caller_token(self, tokenId): | |
| 372 """ | |
| 373 Returns details about the token specified by 'callerReference'. | |
| 374 """ | |
| 375 params ={} | |
| 376 params['TokenId'] = tokenId | |
| 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 verify_signature(self, end_point_url, http_parameters): | |
| 389 params = dict( | |
| 390 UrlEndPoint = end_point_url, | |
| 391 HttpParameters = http_parameters, | |
| 392 ) | |
| 393 response = self.make_request("VerifySignature", params) | |
| 394 body = response.read() | |
| 395 if(response.status != 200): | |
| 396 raise FPSResponseError(response.status, response.reason, body) | |
| 397 rs = ResultSet() | |
| 398 h = handler.XmlHandler(rs, self) | |
| 399 xml.sax.parseString(body, h) | |
| 400 return rs | |
| OLD | NEW |