def post(url, params): """ Make a POST request to the URL using the key-value pairs. Return a set of key-value pairs. :url: URL to post to :params: Dict of parameters to include in post payload """ payload = urlencode(params) start_time = time.time() response = requests.post( url, payload, headers={'content-type': 'text/namevalue; charset=utf-8'}) if response.status_code != requests.codes.ok: raise exceptions.PayPalError("Unable to communicate with PayPal") # Convert response into a simple key-value format pairs = {} for key, value in parse_qsl(response.text): if isinstance(key, six.binary_type): key = key.decode('utf8') if isinstance(value, six.binary_type): value = value.decode('utf8') pairs[key] = value # Add audit information pairs['_raw_request'] = payload pairs['_raw_response'] = response.text pairs['_response_time'] = (time.time() - start_time) * 1000.0 return pairs
def post(url, params): """ Make a POST request to the URL using the key-value pairs. Return a set of key-value pairs. :url: URL to post to :params: Dict of parameters to include in post payload """ for k in params.keys(): if type(params[k]) == unicode: params[k] = params[k].encode('utf-8') payload = urllib.urlencode(params.items()) start_time = time.time() response = requests.post(url, payload) if response.status_code != requests.codes.ok: raise exceptions.PayPalError("Unable to communicate with PayPal") # Convert response into a simple key-value format pairs = {} for key, values in urlparse.parse_qs(response.content).items(): pairs[key] = values[0] # Add audit information pairs['_raw_request'] = payload pairs['_raw_response'] = response.content pairs['_response_time'] = (time.time() - start_time) * 1000.0 return pairs
def _post(url, params, headers=None): """ Make a POST request to the URL using the key-value pairs. Return a set of key-value pairs. :url: URL to post to :params: Dict of parameters to include in post payload :headers: Dict of headers """ if headers is None: headers = {} payload = urllib.urlencode(params) # Ensure correct headers are present if 'Content-type' not in headers: headers['Content-type'] = 'application/x-www-form-urlencoded' if 'Accepts' not in headers: headers['Accepts'] = 'text/plain' start_time = time.time() response = requests.post(url, payload, headers=headers) if response.status_code != requests.codes.ok: raise exceptions.PayPalError("Unable to communicate with PayPal") # Convert response into a simple key-value format pairs = {} for key, values in urlparse.parse_qs(response.content).items(): pairs[key] = values[0] # Add audit information pairs['_raw_request'] = payload pairs['_raw_response'] = response.content pairs['_response_time'] = (time.time() - start_time) * 1000.0 return pairs
def do_txn(payer_id, token, amount, currency, action=SALE): params = ( ('payKey', token), ('requestEnvelope.errorLanguage', 'en_US'), ) pairs = _fetch_response(DO_ADAPTIVE_CHECKOUT, params) txn = models.AdaptiveTransaction( method=DO_ADAPTIVE_CHECKOUT, ack=pairs['responseEnvelope.ack'], raw_request=pairs['_raw_request'], raw_response=pairs['_raw_response'], response_time=pairs['_response_time'], ) if txn.is_successful: txn.correlation_id = pairs['responseEnvelope.correlationId'] txn.pay_key = pairs['payKey'] txn.currency = pairs['currencyCode'] else: txn.error_code = txn.value('error(0).errorId') txn.error_message = txn.value('error(0).message') txn.save() if not txn.is_successful: msg = "Error %s - %s" % (txn.error_code, txn.error_message) logger.error(msg) raise exceptions.PayPalError(msg) return txn
def post(url, params): """ Make a POST request to the URL using the key-value pairs. Return a set of key-value pairs. :url: URL to post to :params: Dict of parameters to include in post payload """ for k in params.keys(): if type(params[k]) == unicode: params[k] = params[k].encode('utf-8') # PayPal is not expecting urlencoding (e.g. %, +), therefore don't use # urllib.urlencode(). payload = '&'.join(['%s=%s' % (key, val) for (key, val) in params.items()]) start_time = time.time() response = requests.post( url, payload, headers={'content-type': 'text/namevalue; charset=utf-8'}) if response.status_code != requests.codes.ok: raise exceptions.PayPalError("Unable to communicate with PayPal") # Convert response into a simple key-value format pairs = {} for key, values in urlparse.parse_qs(response.content).items(): pairs[key] = values[0] # Add audit information pairs['_raw_request'] = payload pairs['_raw_response'] = response.content pairs['_response_time'] = (time.time() - start_time) * 1000.0 return pairs
def _fetch_response(extra_params): """ Fetch the response from PayPal and return a transaction object """ # Build parameter string params = { 'METHOD': 'AddressVerify', 'VERSION': API_VERSION, 'USER': settings.PAYPAL_API_USERNAME, 'PWD': settings.PAYPAL_API_PASSWORD, 'SIGNATURE': settings.PAYPAL_API_SIGNATURE, } params.update(extra_params) if getattr(settings, 'PAYPAL_SANDBOX_MODE', True): url = 'https://api-3t.sandbox.paypal.com/nvp' else: url = 'https://api-3t.paypal.com/nvp' param_str = "\n".join(["%s: %s" % x for x in params.items()]) logger.debug("Making AddressVerify request to %s with params:\n%s", url, param_str) # Make HTTP request pairs = gateway.post(url, params) pairs_str = "\n".join([ "%s: %s" % x for x in sorted(pairs.items()) if not x[0].startswith('_') ]) logger.debug("Response with params:\n%s", pairs_str) if 'Failure' in pairs['ACK']: msg = "Error %s - %s" % (pairs['L_ERRORCODE0'], pairs['L_LONGMESSAGE0']) logger.error(msg) raise exceptions.PayPalError(msg) try: confcode = pairs['CONFIRMATIONCODE'] except KeyError: confcode = None try: street = pairs['STREETMATCH'] except KeyError: street = None try: zip = pairs['ZIPMATCH'] except KeyError: zip = None try: countrycode = pairs['COUNTRYCODE'] except KeyError: countrycode = None return confcode, street, zip, countrycode
def get_txn(pay_key): """ Fetch details of a transaction from PayPal using the token as an identifier. """ params = [ ('actionType', GET_ADAPTIVE_CHECKOUT), ('payKey', pay_key), ('requestEnvelope.errorLanguage', 'en_US'), ] pairs = _fetch_response(GET_ADAPTIVE_CHECKOUT, params) txn = models.AdaptiveTransaction( method=GET_ADAPTIVE_CHECKOUT, ack=pairs['responseEnvelope.ack'], raw_request=pairs['_raw_request'], raw_response=pairs['_raw_response'], response_time=pairs['_response_time'], ) if txn.is_successful: txn.correlation_id = pairs['responseEnvelope.correlationId'] txn.pay_key = pairs['payKey'] txn.currency = pairs['currencyCode'] else: txn.error_code = txn.value('error(0).errorId') txn.error_message = txn.value('error(0).message') txn.save() if not txn.is_successful: msg = "Error %s - %s" % (txn.error_code, txn.error_message) logger.error(msg) raise exceptions.PayPalError(msg) return txn
def _fetch_response(method, extra_params): """ Fetch the response from PayPal and return a transaction object """ # Build parameter string params = { 'METHOD': method, 'VERSION': API_VERSION, 'USER': settings.PAYPAL_API_USERNAME, 'PWD': settings.PAYPAL_API_PASSWORD, 'SIGNATURE': settings.PAYPAL_API_SIGNATURE, } params.update(extra_params) if getattr(settings, 'PAYPAL_SANDBOX_MODE', True): url = 'https://api-3t.sandbox.paypal.com/nvp' else: url = 'https://api-3t.paypal.com/nvp' # Print easy-to-read version of params for debugging param_str = "\n".join(["%s: %s" % x for x in sorted(params.items())]) logger.debug("Making %s request to %s with params:\n%s", method, url, param_str) # Make HTTP request pairs = gateway.post(url, params) pairs_str = "\n".join(["%s: %s" % x for x in sorted(pairs.items()) if not x[0].startswith('_')]) logger.debug("Response with params:\n%s", pairs_str) # Record transaction data - we save this model whether the txn # was successful or not txn = models.ExpressTransaction( method=method, version=API_VERSION, ack=pairs['ACK'], raw_request=pairs['_raw_request'], raw_response=pairs['_raw_response'], response_time=pairs['_response_time'], ) if txn.is_successful: txn.correlation_id = pairs['CORRELATIONID'] if method == SET_EXPRESS_CHECKOUT: txn.amount = params['PAYMENTREQUEST_0_AMT'] txn.currency = params['PAYMENTREQUEST_0_CURRENCYCODE'] txn.token = pairs['TOKEN'] elif method == GET_EXPRESS_CHECKOUT: txn.token = params['TOKEN'] txn.amount = D(pairs['PAYMENTREQUEST_0_AMT']) txn.currency = pairs['PAYMENTREQUEST_0_CURRENCYCODE'] elif method == DO_EXPRESS_CHECKOUT: txn.token = params['TOKEN'] txn.amount = D(pairs['PAYMENTINFO_0_AMT']) txn.currency = pairs['PAYMENTINFO_0_CURRENCYCODE'] else: # There can be more than one error, each with its own number. if 'L_ERRORCODE0' in pairs: txn.error_code = pairs['L_ERRORCODE0'] if 'L_LONGMESSAGE0' in pairs: txn.error_message = pairs['L_LONGMESSAGE0'] txn.save() if not txn.is_successful: msg = "Error %s - %s" % (txn.error_code, txn.error_message) logger.error(msg) raise exceptions.PayPalError(msg) return txn
def set_txn(basket, shipping_methods, currency, return_url, cancel_url, update_url=None, action=SALE, user=None, user_address=None, shipping_method=None, shipping_address=None, no_shipping=False): """ Register the transaction with PayPal to get a token which we use in the redirect URL. This is the 'SetExpressCheckout' from their documentation. There are quite a few options that can be passed to PayPal to configure this request - most are controlled by PAYPAL_* settings. """ # PayPal have an upper limit on transactions. It's in dollars which is a # fiddly to work with. Lazy solution - only check when dollars are used as # the PayPal currency. amount = basket.total_incl_tax if currency == 'USD' and amount > 10000: raise exceptions.PayPalError( 'PayPal can only be used for orders up to 10000 USD') # if amount <= 0: # raise exceptions.PayPalError('Zero value basket is not allowed') # PAYMENTREQUEST_0_AMT should include tax, shipping and handling params = { 'PAYMENTREQUEST_0_AMT': amount, 'PAYMENTREQUEST_0_CURRENCYCODE': currency, 'RETURNURL': return_url, 'CANCELURL': cancel_url, 'PAYMENTREQUEST_0_PAYMENTACTION': action, } # Add item details index = 0 for index, line in enumerate(basket.all_lines()): product = line.product params['L_PAYMENTREQUEST_0_NAME%d' % index] = product.get_title() params['L_PAYMENTREQUEST_0_NUMBER%d' % index] = (product.upc if product.upc else '') desc = '' if product.description: desc = truncatewords(strip_entities(strip_tags(product.description)), 12) params['L_PAYMENTREQUEST_0_DESC%d' % index] = desc # Note, we don't include discounts here - they are handled as separate # lines - see below params['L_PAYMENTREQUEST_0_AMT%d' % index] = _format_currency( line.unit_price_incl_tax) params['L_PAYMENTREQUEST_0_QTY%d' % index] = line.quantity # If the order has discounts associated with it, the way PayPal suggests # using the API is to add a separate item for the discount with the value # as a negative price. See "Integrating Order Details into the Express # Checkout Flow" # https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECCustomizing # Iterate over the 3 types of discount that can occur for discount in basket.offer_discounts: index += 1 name = _("Special Offer: %s") % discount['name'] params['L_PAYMENTREQUEST_0_NAME%d' % index] = name params['L_PAYMENTREQUEST_0_DESC%d' % index] = truncatewords(name, 12) params['L_PAYMENTREQUEST_0_AMT%d' % index] = _format_currency( -discount['discount']) params['L_PAYMENTREQUEST_0_QTY%d' % index] = 1 for discount in basket.voucher_discounts: index += 1 name = "%s (%s)" % (discount['voucher'].name, discount['voucher'].code) params['L_PAYMENTREQUEST_0_NAME%d' % index] = name params['L_PAYMENTREQUEST_0_DESC%d' % index] = truncatewords(name, 12) params['L_PAYMENTREQUEST_0_AMT%d' % index] = _format_currency( -discount['discount']) params['L_PAYMENTREQUEST_0_QTY%d' % index] = 1 for discount in basket.shipping_discounts: index += 1 name = _("Shipping Offer: %s") % discount['name'] params['L_PAYMENTREQUEST_0_NAME%d' % index] = name params['L_PAYMENTREQUEST_0_DESC%d' % index] = truncatewords(name, 12) params['L_PAYMENTREQUEST_0_AMT%d' % index] = _format_currency( -discount['discount']) params['L_PAYMENTREQUEST_0_QTY%d' % index] = 1 # We include tax in the prices rather than separately as that's how it's # done on most British/Australian sites. Will need to refactor in the # future no doubt. # Note that the following constraint must be met # # PAYMENTREQUEST_0_AMT = ( # PAYMENTREQUEST_0_ITEMAMT + # PAYMENTREQUEST_0_TAXAMT + # PAYMENTREQUEST_0_SHIPPINGAMT + # PAYMENTREQUEST_0_HANDLINGAMT) # # Hence, if tax is to be shown then it has to be aggregated up to the order # level. # params['PAYMENTREQUEST_0_ITEMAMT'] = _format_currency( # basket.total_incl_tax + ) params['PAYMENTREQUEST_0_TAXAMT'] = _format_currency(D('0.00')) # Customer services number customer_service_num = getattr( settings, 'PAYPAL_CUSTOMER_SERVICES_NUMBER', None) if customer_service_num: params['CUSTOMERSERVICENUMBER'] = customer_service_num # Display settings page_style = getattr(settings, 'PAYPAL_PAGESTYLE', None) header_image = getattr(settings, 'PAYPAL_HEADER_IMG', None) if page_style: params['PAGESTYLE'] = page_style elif header_image: params['LOGOIMG'] = header_image else: # Think these settings maybe deprecated in latest version of PayPal's # API display_params = { 'HDRBACKCOLOR': getattr(settings, 'PAYPAL_HEADER_BACK_COLOR', None), 'HDRBORDERCOLOR': getattr(settings, 'PAYPAL_HEADER_BORDER_COLOR', None), } params.update(x for x in display_params.items() if bool(x[1])) # Locale locale = getattr(settings, 'PAYPAL_LOCALE', None) if locale: valid_choices = ('AU', 'DE', 'FR', 'GB', 'IT', 'ES', 'JP', 'US') if locale not in valid_choices: raise ImproperlyConfigured( "'%s' is not a valid locale code" % locale) params['LOCALECODE'] = locale # Confirmed shipping address confirm_shipping_addr = getattr(settings, 'PAYPAL_CONFIRM_SHIPPING', None) if confirm_shipping_addr: params['REQCONFIRMSHIPPING'] = 1 # Instant update callback information if update_url: params['CALLBACK'] = update_url params['CALLBACKTIMEOUT'] = getattr( settings, 'PAYPAL_CALLBACK_TIMEOUT', 3) # Contact details and address details - we provide these as it would make # the PayPal registration process smoother is the user doesn't already have # an account. if user: params['EMAIL'] = user.email if user_address: params['SHIPTONAME'] = user_address.name() params['SHIPTOSTREET'] = user_address.line1 params['SHIPTOSTREET2'] = user_address.line2 params['SHIPTOCITY'] = user_address.city params['SHIPTOSTATE'] = US_ABBREVIATIONS.get(user_address.state.title(), user_address.state) params['SHIPTOZIP'] = user_address.postcode params['SHIPTOCOUNTRYCODE'] = user_address.country.iso_3166_1_a2 # Shipping details (if already set) - we override the SHIPTO* fields and # set a flag to indicate that these can't be altered on the PayPal side. if shipping_method and shipping_address: params['ADDROVERRIDE'] = 1 # It's recommend not to set 'confirmed shipping' if supplying the # shipping address directly. params['REQCONFIRMSHIPPING'] = 0 params['SHIPTONAME'] = shipping_address.name() params['SHIPTOSTREET'] = shipping_address.line1 params['SHIPTOSTREET2'] = shipping_address.line2 params['SHIPTOCITY'] = shipping_address.city params['SHIPTOSTATE'] = US_ABBREVIATIONS.get(shipping_address.state.title(), shipping_address.state) params['SHIPTOZIP'] = shipping_address.postcode params['SHIPTOCOUNTRYCODE'] = shipping_address.country.iso_3166_1_a2 elif no_shipping: params['NOSHIPPING'] = 1 # Allow customer to specify a shipping note allow_note = getattr(settings, 'PAYPAL_ALLOW_NOTE', True) if allow_note: params['ALLOWNOTE'] = 1 # Shipping charges #params['PAYMENTREQUEST_0_SHIPPINGAMT'] = _format_currency(D('0.00')) # max_charge = D('0.00') # for index, method in enumerate(shipping_methods): # is_default = index == 0 # params['L_SHIPPINGOPTIONISDEFAULT%d' % index] = 'true' if is_default else 'false' # charge = method.basket_charge_incl_tax() # if charge > max_charge: # max_charge = charge # if is_default: # params['PAYMENTREQUEST_0_SHIPPINGAMT'] = _format_currency(charge) # params['PAYMENTREQUEST_0_AMT'] += charge # params['L_SHIPPINGOPTIONNAME%d' % index] = unicode(method.name) # params['L_SHIPPINGOPTIONAMOUNT%d' % index] = _format_currency(charge) # Set shipping charge explicitly if it has been passed if shipping_method: index += 1 max_charge = charge = shipping_method.basket_charge_incl_tax() #params['PAYMENTREQUEST_0_SHIPPINGAMT'] = _format_currency(charge) params['L_PAYMENTREQUEST_0_NAME%d' % index] = "Shipping" params['L_PAYMENTREQUEST_0_NUMBER%d' % index] = "" desc = '' params['L_PAYMENTREQUEST_0_DESC%d' % index] = desc # Note, we don't include discounts here - they are handled as separate # lines - see below params['L_PAYMENTREQUEST_0_AMT%d' % index] = _format_currency( charge) params['L_PAYMENTREQUEST_0_QTY%d' % index] = 1 params['PAYMENTREQUEST_0_AMT'] += charge # Both the old version (MAXAMT) and the new version (PAYMENT...) are needed # here - think it's a problem with the API. params['PAYMENTREQUEST_0_MAXAMT'] = _format_currency(amount + max_charge) params['PAYMENTREQUEST_0_ITEMAMT'] = _format_currency(amount + max_charge) params['MAXAMT'] = _format_currency(amount + max_charge) # Handling set to zero for now - I've never worked on a site that needed a # handling charge. params['PAYMENTREQUEST_0_HANDLINGAMT'] = _format_currency(D('0.00')) # Ensure that the total is formatted correctly. params['PAYMENTREQUEST_0_AMT'] = _format_currency( params['PAYMENTREQUEST_0_AMT']) if getattr(settings, 'PAYPAL_NO_SHIPPING', False): params['NOSHIPPING'] = 1 txn = _fetch_response(SET_EXPRESS_CHECKOUT, params) # Construct return URL if getattr(settings, 'PAYPAL_SANDBOX_MODE', True): url = 'https://www.sandbox.paypal.com/webscr' else: url = 'https://www.paypal.com/webscr' params = (('cmd', '_express-checkout'), ('token', txn.token),) return '%s?%s' % (url, urllib.urlencode(params))
def set_txn(basket, shipping_methods, currency, return_url, cancel_url, update_url=None, action=SALE, user=None, user_address=None, shipping_method=None, shipping_address=None, no_shipping=False, paypal_params=None): """ Register the transaction with PayPal to get a token which we use in the redirect URL. This is the 'SetExpressCheckout' from their documentation. There are quite a few options that can be passed to PayPal to configure this request - most are controlled by PAYPAL_* settings. """ params = [ ('actionType', SET_ADAPTIVE_CHECKOUT), ('cancelUrl', cancel_url), ('currencyCode', currency), ('requestEnvelope.errorLanguage', 'en_US'), ('returnUrl', return_url), ] # PayPal have an upper limit on transactions. It's in dollars which is a # fiddly to work with. Lazy solution - only check when dollars are used as # the PayPal currency. amount = basket.total_incl_tax if currency == 'USD' and amount > 10000: msg = 'PayPal can only be used for orders up to 10000 USD' logger.error(msg) raise express_exceptions.InvalidBasket(_(msg)) if amount <= 0: msg = 'The basket total is zero so no payment is required' logger.error(msg) raise express_exceptions.InvalidBasket(_(msg)) receivers = _get_receivers(basket) for index, receiver in enumerate(receivers): params.append(('receiverList.receiver(%d).amount' % index, str(receiver.amount))) params.append(('receiverList.receiver(%d).email' % index, receiver.email)) params.append(('receiverList.receiver(%d).primary' % index, 'true' if receiver.is_primary else 'false')) pairs = _fetch_response(SET_ADAPTIVE_CHECKOUT, params) txn = models.AdaptiveTransaction( method=SET_ADAPTIVE_CHECKOUT, ack=pairs['responseEnvelope.ack'], raw_request=pairs['_raw_request'], raw_response=pairs['_raw_response'], response_time=pairs['_response_time'], ) if txn.is_successful: txn.correlation_id = pairs['responseEnvelope.correlationId'] txn.pay_key = pairs['payKey'] txn.amount = amount txn.currency = currency else: txn.error_code = txn.value('error(0).errorId') txn.error_message = txn.value('error(0).message') txn.save() if not txn.is_successful: msg = "Error %s - %s" % (txn.error_code, txn.error_message) logger.error(msg) raise exceptions.PayPalError(msg) if getattr(settings, 'PAYPAL_SANDBOX_MODE', True): url = 'https://www.sandbox.paypal.com/webscr' else: url = 'https://www.paypal.com/webscr' return url + '?cmd=_ap-payment&paykey=%s' % txn.pay_key