示例#1
0
 def __init__(self, request, donation=None, subscription=None, **kwargs):
     super().__init__(request, donation, subscription)
     # set paypal settings object
     self.settings = getPayPalSettings()
     # init paypal http client
     self.client = PayPalHttpClient(self.settings.environment)
     # saves all remaining kwargs into the manager, e.g. order_id, order_status
     self.__dict__.update(kwargs)
示例#2
0
def listProducts(session):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/catalogs/products?page_size=20'
    if paypalSettings.sandbox_mode and session.get('negtest_listProducts',
                                                   None):
        api_url += '&total_required=' + session.get('negtest_listProducts')
    return curlPaypal(api_url, common_headers(session['paypal_token']))
示例#3
0
def checkAccessTokenExpiry(session):
    paypalSettings = getPayPalSettings()
    token_uri = '/v1/oauth2/token'
    # check if the paypal_token_expiry stored in session has passed or not
    current_time = round(time.time())
    if 'paypal_token_expiry' not in session or current_time > session[
            'paypal_token_expiry']:
        saveNewAccessToken(session, paypalSettings.api_url + token_uri,
                           paypalSettings.client_id, paypalSettings.secret_key)
    _debug("Paypal Auth Token: " + session['paypal_token'])
示例#4
0
def getSubscriptionDetails(session, subscription_id):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}'.format(
        subscription_id)
    if paypalSettings.sandbox_mode and session.get(
            'negtest_getSubscriptionDetails', None):
        api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}'.format(
            session.get('negtest_getSubscriptionDetails'))
    return curlPaypal(api_url, common_headers(session['paypal_token']))
示例#5
0
def create_paypal_order(session, donation):
    paypalSettings = getPayPalSettings()
    client = PayPalHttpClient(paypalSettings.environment)

    req = OrdersCreateRequest()
    # set dictionary object in session['extra_test_headers'] in TestCases
    if session.get('extra_test_headers', None) and donation.is_test:
        for key, value in session.get('extra_test_headers').items():
            req.headers[key] = value
    req.prefer('return=representation')
    req.request_body(build_onetime_request_body(donation))
    return client.execute(req)
示例#6
0
def suspendSubscription(session, subscription_id):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}/suspend'.format(
        subscription_id)
    if paypalSettings.sandbox_mode and session.get(
            'negtest_suspendSubscription', None):
        api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}/suspend'.format(
            session.get('negtest_suspendSubscription'))
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      verb='POST')
示例#7
0
def cancelSubscription(session, subscription_id):
    '''paypal returns 404 if the subscription has not passed the approval stage'''
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}/cancel'.format(
        subscription_id)
    if paypalSettings.sandbox_mode and session.get(
            'negtest_cancelSubscription', None):
        api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}/cancel'.format(
            session.get('negtest_cancelSubscription'))
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      verb='POST')
示例#8
0
def createProduct(session, product_dict={}):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/catalogs/products'
    if not product_dict:
        product_dict = {
            "name": "Newstream Donations",
            "description": "Newstream Donations(PayPal)",
            "type": "SERVICE",
            "category": "BOOKS_PERIODICALS_AND_NEWSPAPERS"
        }
    if paypalSettings.sandbox_mode and session.get('negtest_createProduct',
                                                   None):
        product_dict['name'] = session.get('negtest_createProduct')
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      post_data=json.dumps(product_dict))
示例#9
0
def createSubscription(session, plan_id, donation, is_test=False):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/subscriptions'
    subscription_dict = {
        "plan_id": plan_id,
        "quantity": "1",
        "subscriber": {
            "name": {
                "given_name": getUserGivenName(donation.user),
                "surname": donation.user.last_name or ''
            },
            "email_address": donation.user.email
        },
        "application_context": {
            "brand_name":
            get_site_name(),
            "locale":
            "en-US",
            "shipping_preference":
            "NO_SHIPPING",
            "user_action":
            "SUBSCRIBE_NOW",
            "payment_method": {
                "payer_selected": "PAYPAL",
                "payee_preferred": "IMMEDIATE_PAYMENT_REQUIRED"
            },
            "return_url":
            reverse_with_site_url('donations:return-from-paypal')
            if not is_test else 'http://example.com/return/',
            "cancel_url":
            reverse_with_site_url('donations:cancel-from-paypal')
            if not is_test else 'http://example.com/cancel/'
        },
        "custom_id": str(donation.id)
    }
    if paypalSettings.sandbox_mode and session.get(
            'negtest_createSubscription', None):
        subscription_dict['plan_id'] = session.get(
            'negtest_createSubscription')
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      post_data=json.dumps(subscription_dict))
示例#10
0
def updateSubscription(session, subscription_id, new_amount, currency):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}'.format(
        subscription_id)
    patch_body = [{
        "op": "replace",
        "path": "/plan/billing_cycles/@sequence==1/pricing_scheme/fixed_price",
        "value": {
            "currency_code": currency,
            "value": new_amount
        }
    }]
    if paypalSettings.sandbox_mode and session.get(
            'negtest_updateSubscription', None):
        api_url = paypalSettings.api_url + '/v1/billing/subscriptions/{}'.format(
            session.get('negtest_updateSubscription'))
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      post_data=json.dumps(patch_body),
                      verb='PATCH')
示例#11
0
def createPlan(session, product_id, donation):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/plans'
    plan_dict = {
        "product_id":
        product_id,
        "name":
        "Newstream Donation Plan for %s" % (donation.user.fullname),
        "description":
        "Newstream Donation Plan for %s" % (donation.user.fullname),
        "status":
        "ACTIVE",
        "billing_cycles": [{
            "frequency": {
                "interval_unit": "MONTH",
                "interval_count": 1
            },
            "tenure_type": "REGULAR",
            "sequence": 1,
            "total_cycles": 0,
            "pricing_scheme": {
                "fixed_price": {
                    "value": str(donation.donation_amount),
                    "currency_code": donation.currency
                }
            }
        }],
        "payment_preferences": {
            "auto_bill_outstanding": 'true',
            "payment_failure_threshold": 3
        }
    }
    if paypalSettings.sandbox_mode and session.get('negtest_createPlan', None):
        plan_dict['name'] = session.get('negtest_createPlan')
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      post_data=json.dumps(plan_dict))
示例#12
0
def capture_paypal_order(donation, order_id):
    """Method to capture order using order_id"""
    paypalSettings = getPayPalSettings()
    client = PayPalHttpClient(paypalSettings.environment)

    req = OrdersCaptureRequest(order_id)
    _debug('PayPal: Capture Order')
    try:
        response = client.execute(req)
    except IOError as ioe:
        fail_reason = str(ioe)
        if isinstance(ioe, HttpError):
            # Something went wrong server-side
            httpError = json.loads(ioe.message)
            if 'details' in httpError and len(httpError['details']) > 0:
                fail_reason = process_capture_failure(
                    donation, httpError['details'][0]['issue'],
                    httpError['details'][0]['description'])
        # update donation status to failed
        donation.payment_status = STATUS_FAILED
        donation.save()
        raise IOError(fail_reason)
    return response.result
示例#13
0
def create_paypal_transaction(request):
    """ When the user reaches last step after confirming the donation,
        user is redirected via gatewayManager.redirect_to_gateway_url(), which renders redirection_paypal.html
        
        This function calls to PayPal Api to create a PayPal subscription object or PayPal order(one-time donation),
        then this function returns the approval_link to frontend js and to redirect to PayPal's checkout page

        Sample (JSON) request: {
            'csrfmiddlewaretoken': 'LZSpOsb364pn9R3gEPXdw2nN3dBEi7RWtMCBeaCse2QawCFIndu93fD3yv9wy0ij'
        }
        
        @todo: revise error handling, avoid catching all exceptions at the end
    """

    errorObj = {
        "issue": "Exception",
        "description": ""
    }
    result = {}
    try:
        paypalSettings = getPayPalSettings()

        donation_id = request.session.pop('donation_id', None)
        if not donation_id:
            raise ValueError(_("Missing donation_id in session"))
        donation = Donation.objects.get(pk=int(donation_id))

        if donation.is_recurring:
            # Product should have been created by admin manually at the dashboard/setup wizard
            # if no product exists, create one here(double safety net)
            # todo: make sure the product_id in site_settings has been set by some kind of configuration enforcement before site is launched
            product_list = listProducts(request.session)
            product = None
            if len(product_list['products']) == 0:
                product = createProduct(request.session)
            else:
                # get the product, should aim at the product with the specific product id
                for prod in product_list['products']:
                    if prod['id'] == paypalSettings.product_id:
                        product = prod
            if product == None:
                raise ValueError(_('Cannot initialize/get the paypal product object'))
            # Create plan and subscription
            plan = createPlan(request.session, product['id'], donation)
            if plan['status'] == 'ACTIVE':
                subscription = createSubscription(request.session, plan['id'], donation)
                result['subscription_id'] = subscription['id']
                for link in subscription['links']:
                    if link['rel'] == 'approve':
                        result['approval_link'] = link['href']
            else:
                raise ValueError(_("Newly created PayPal plan is not active, status: %(status)s") % {'status': plan['status']})
        # else: one-time donation
        else:
            response = create_paypal_order(request.session, donation)
            ppresult = response.result
            _debug('PayPal: Order Created Status: '+ppresult.status)
            # set approval_link attribute
            for link in ppresult.links:
                _debug('PayPal: --- {}: {} ---'.format(link.rel, link.href))
                if link.rel == 'approve':
                    result['approval_link'] = link.href
    except ValueError as e:
        _exception(str(e))
        errorObj['issue'] = "ValueError"
        errorObj['description'] = str(e)
        return JsonResponse(object_to_json(errorObj), status=500)
    except Donation.DoesNotExist:
        _exception("Donation.DoesNotExist")
        errorObj['issue'] = "Donation.DoesNotExist"
        errorObj['description'] = str(_("Donation object not found by id: %(id)s") % {'id': donation_id})
        return JsonResponse(object_to_json(errorObj), status=500)
    except HttpError as ioe:
        # Catching exceptions from the paypalclient execution, HttpError is a subclass of IOError
        httpError = json.loads(ioe.message)
        if 'details' in httpError and len(httpError['details']) > 0:
            errorObj["issue"] = httpError['details'][0]['issue']
            errorObj["description"] = httpError['details'][0]['description']
            _exception(errorObj["description"])
        # update donation status to failed
        donation.payment_status = STATUS_FAILED
        donation.save()
        return JsonResponse(object_to_json(errorObj), status=ioe.status_code)
    except Exception as error:
        errorObj['description'] = str(error)
        _exception(errorObj["description"])
        return JsonResponse(object_to_json(errorObj), status=500)
    return JsonResponse(object_to_json(result))
示例#14
0
    def initGatewayByVerification(request):
        paypalSettings = getPayPalSettings()

        # The payload body sent in the webhook event
        event_body = request.body.decode()
        json_data = json.loads(request.body)
        _debug('Event Type: ' + json_data['event_type'])
        # printvars(request.headers)
        # Paypal-Transmission-Id in webhook payload header
        transmission_id = request.headers['Paypal-Transmission-Id']
        # Paypal-Transmission-Time in webhook payload header
        timestamp = request.headers['Paypal-Transmission-Time']
        # Webhook id created
        webhook_id = paypalSettings.webhook_id
        # Paypal-Transmission-Sig in webhook payload header
        actual_signature = request.headers['Paypal-Transmission-Sig']
        # Paypal-Cert-Url in webhook payload header
        cert_url = request.headers['Paypal-Cert-Url']
        # PayPal-Auth-Algo in webhook payload header
        auth_algo = request.headers['PayPal-Auth-Algo']

        response = WebhookEvent.verify(transmission_id, timestamp, webhook_id,
                                       event_body, cert_url, actual_signature,
                                       auth_algo)
        if not response:
            _debug('Webhook verification result: ' + str(response))

        if response:
            donation_id = None
            subscription = None
            subscription_obj = None
            kwargs = {}
            expected_events = [
                EVENT_PAYMENT_CAPTURE_COMPLETED,
                EVENT_BILLING_SUBSCRIPTION_ACTIVATED,
                EVENT_BILLING_SUBSCRIPTION_UPDATED,
                EVENT_PAYMENT_SALE_COMPLETED,
                EVENT_BILLING_SUBSCRIPTION_CANCELLED
            ]

            # one-time donation payment captured
            if json_data['event_type'] == EVENT_PAYMENT_CAPTURE_COMPLETED:
                if 'custom_id' in json_data['resource']:
                    donation_id = json_data['resource']['custom_id']
                else:
                    raise ValueError(
                        _('Missing custom_id(donation_id) in json_data.resource'
                          ))

            # subscription activated
            if json_data['event_type'] == EVENT_BILLING_SUBSCRIPTION_ACTIVATED:
                subscription_obj = json_data['resource']
                if 'custom_id' in json_data['resource']:
                    donation_id = json_data['resource']['custom_id']
                else:
                    raise ValueError(
                        _('Missing custom_id(donation_id) in json_data.resource'
                          ))

            # subscription updated
            if json_data['event_type'] == EVENT_BILLING_SUBSCRIPTION_UPDATED:
                subscription_obj = json_data['resource']
                if 'custom_id' in json_data['resource']:
                    donation_id = json_data['resource']['custom_id']
                else:
                    raise ValueError(
                        _('Missing custom_id(donation_id) in json_data.resource'
                          ))

            # subscription payment sale completed
            if json_data['event_type'] == EVENT_PAYMENT_SALE_COMPLETED:
                subscription_id = json_data['resource']['billing_agreement_id']
                subscription_obj = getSubscriptionDetails(
                    request.session, subscription_id)
                if 'custom_id' in subscription_obj:
                    donation_id = subscription_obj['custom_id']
                else:
                    raise ValueError(
                        _('Missing custom_id(donation_id) in curlPaypal-returned subscription_obj'
                          ))

            # subscription cancelled
            if json_data['event_type'] == EVENT_BILLING_SUBSCRIPTION_CANCELLED:
                subscription_obj = json_data['resource']
                if 'custom_id' in json_data['resource']:
                    donation_id = json_data['resource']['custom_id']
                else:
                    raise ValueError(
                        _('Missing custom_id(donation_id) in json_data.resource'
                          ))

            if json_data['event_type'] in expected_events and not donation_id:
                raise ValueError(
                    _("Missing donation_id after processing events from paypal"
                      ))
            if json_data['event_type'] not in expected_events:
                raise WebhookNotProcessedError(
                    _("PayPal Event not expected for processing at the moment")
                )
            try:
                donation = Donation.objects.get(pk=donation_id)
                kwargs['payload'] = json_data['resource']
                kwargs['event_type'] = json_data['event_type']
                kwargs['subscription_obj'] = subscription_obj
                return Factory_Paypal.initGateway(request, donation,
                                                  subscription, **kwargs)
            except Donation.DoesNotExist:
                raise ValueError(
                    _("Donation object not found by id: ") + str(donation_id))
        else:
            raise ValueError(_("PayPal Webhook verification failed."))
示例#15
0
    def initGatewayByReturn(request):
        # a get param named 'token' contains the order_id
        paypalSettings = getPayPalSettings()
        client = PayPalHttpClient(paypalSettings.environment)
        donation_id = None
        subscription_obj = {}
        kwargs = {}

        if request.GET.get('subscription_id', None):
            # recurring payment
            subscription_obj = getSubscriptionDetails(
                request.session, request.GET.get('subscription_id'))
            kwargs['subscription_obj'] = subscription_obj
            if 'custom_id' in subscription_obj:
                donation_id = subscription_obj['custom_id']
            else:
                raise ValueError(
                    _('Missing custom_id(donation_id) in curlPaypal-returned subscription'
                      ))
        elif request.GET.get('token', None):
            # onetime payment
            req = OrdersGetRequest(request.GET.get('token'))
            # might throw IOError
            response = client.execute(req)
            _debug('PayPal: Returns from Gateway')
            _debug('PayPal: Order status: ' + response.result.status)
            donation_id = response.result.purchase_units[0].custom_id
            kwargs['order_id'] = request.GET.get('token')
            kwargs['order_status'] = response.result.status
            if not donation_id:
                raise ValueError(
                    _("Missing donation_id in purchase_units custom_id attribute"
                      ))
        else:
            raise ValueError(_("Missing token from PayPal request"))

        try:
            donation = Donation.objects.get(pk=donation_id)

            if request.GET.get('subscription_id', None):
                # raise error if paypal_subscription_id already found in DonationPaymentMeta
                dpm = DonationPaymentMeta.objects.filter(
                    donation=donation,
                    field_key='paypal_subscription_id',
                    field_value=request.GET.get('subscription_id'))
                if len(dpm) >= 1:
                    raise ValueError(
                        _("PayPal subscription id found. Return request from PayPal is already invalid."
                          ))
                else:
                    dpm = DonationPaymentMeta(
                        donation=donation,
                        field_key='paypal_subscription_id',
                        field_value=request.GET.get('subscription_id'))
                    dpm.save()
            elif request.GET.get('token', None):
                # raise error if paypal_token already found in DonationPaymentMeta
                dpm = DonationPaymentMeta.objects.filter(
                    donation=donation,
                    field_key='paypal_token',
                    field_value=request.GET.get('token'))
                if len(dpm) >= 1:
                    raise ValueError(
                        _("PayPal token found. Return request from PayPal is already invalid."
                          ))
                else:
                    dpm = DonationPaymentMeta(
                        donation=donation,
                        field_key='paypal_token',
                        field_value=request.GET.get('token'))
                    dpm.save()
            return Factory_Paypal.initGateway(request, donation, None,
                                              **kwargs)
        except Donation.DoesNotExist:
            raise ValueError(
                _("Donation object not found by id: ") + str(donation_id))