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
示例#2
0
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
示例#3
0
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
示例#4
0
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
示例#5
0
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
示例#6
0
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
示例#7
0
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
示例#8
0
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
示例#9
0
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))
示例#10
0
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