예제 #1
0
파일: alipay.py 프로젝트: CrewS/ecommerce
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        approval_url
        """
        trade_id = create_trade_id(basket.id)
        body = "BUY {amount} {currency}".format(amount=basket.total_incl_tax,
                                                currency=basket.currency)
        subject = "BUY COURSE"
        total_fee = str_to_specify_digits(str(basket.total_incl_tax))
        http_host = request.META.get('HTTP_HOST')
        extra_common_param = urljoin(get_ecommerce_url(),
                                     reverse('alipay:execute'))

        approval_url = create_direct_pay_by_user(
            trade_id,
            body,
            subject,
            total_fee,
            http_host,
            extra_common_param=extra_common_param)
        if not PaymentProcessorResponse.objects.filter(
                processor_name=self.NAME,
                basket=basket).update(transaction_id=trade_id):
            self.record_processor_response({},
                                           transaction_id=trade_id,
                                           basket=basket)

        parameters = {
            'payment_page_url': approval_url,
        }
        return parameters
예제 #2
0
    def assert_report_row(self, row, coupon, voucher):
        """ Verify that the row fields contain the right data. """
        offer = voucher.offers.all().first()
        discount_data = get_voucher_discount_info(
            offer.benefit,
            offer.condition.range.catalog.stock_records.first().price_excl_tax)
        coupon_type = _('Discount') if discount_data['is_discounted'] else _(
            'Enrollment')
        discount_percentage = _("{percentage} %").format(
            percentage=discount_data['discount_percentage'])
        discount_amount = currency(discount_data['discount_value'])

        self.assertEqual(row['Coupon Type'], coupon_type)
        self.assertEqual(
            row['Category'],
            ProductCategory.objects.get(product=coupon).category.name)
        self.assertEqual(row['Discount Percentage'], discount_percentage)
        self.assertEqual(row['Discount Amount'], discount_amount)
        self.assertEqual(row['Client'], coupon.client.name)
        self.assertEqual(
            row['URL'],
            get_ecommerce_url() + self.REDEMPTION_URL.format(voucher.code))
        self.assertEqual(row['Note'], coupon.attr.note)
        self.assertEqual(row['Created By'],
                         coupon.history.first().history_user.full_name)
        self.assertEqual(
            row['Create Date'],
            coupon.history.latest().history_date.strftime("%b %d, %y"))
        self.assertEqual(row['Coupon Start Date'],
                         voucher.start_datetime.strftime("%b %d, %y"))
        self.assertEqual(row['Coupon Expiry Date'],
                         voucher.end_datetime.strftime("%b %d, %y"))
예제 #3
0
    def get_authorizenet_payment_settings(self, basket):
        """
            return AuthorizeNet_sdk Setting Instance containing required transaction settings to control the
            receipt page urls and buttons.
            Visit https://developer.authorize.net/api/reference/features/accept_hosted.html for more detail.
        """
        course_id = basket.all_lines()[0].product.course_id
        course_id_hash = base64.b64encode(course_id.encode())

        redirect_url = reverse('authorizenet:redirect')
        ecommerce_base_url = get_ecommerce_url()

        return_url = '{}{}?course={}'.format(ecommerce_base_url, redirect_url, course_id_hash)

        # Create Authorizenet Settings object
        payment_button_setting = apicontractsv1.settingType()
        payment_button_setting.settingName = apicontractsv1.settingNameEnum.hostedPaymentButtonOptions
        payment_button_setting.settingValue = json.dumps({'text': 'Pay'})

        payment_return_setting = apicontractsv1.settingType()
        payment_return_setting.settingName = apicontractsv1.settingNameEnum.hostedPaymentReturnOptions
        payment_return_configrations = {
            'url': return_url,
            'urlText': 'Continue',
            'cancelUrl': self.cancel_url,
            'cancelUrlText': 'Cancel'
        }
        payment_return_setting.settingValue = json.dumps(payment_return_configrations)

        settings = apicontractsv1.ArrayOfSetting()
        settings.setting.append(payment_button_setting)
        settings.setting.append(payment_return_setting)
        return settings
예제 #4
0
def _get_voucher_info_for_coupon_report(voucher):
    offer = voucher.offers.first()
    status = _get_voucher_status(voucher, offer)
    path = '{path}?code={code}'.format(path=reverse('coupons:offer'), code=voucher.code)
    url = get_ecommerce_url(path)

    # Set the max_uses_count for single-use vouchers to 1,
    # for other usage limitations (once per customer and multi-use)
    # which don't have the max global applications limit set,
    # set the max_uses_count to 10000 which is the arbitrary limit Oscar sets:
    # https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/offer/abstract_models.py#L253
    redemption_count = offer.num_applications
    if voucher.usage == Voucher.SINGLE_USE:
        max_uses_count = 1
        redemption_count = voucher.num_orders
    elif voucher.usage != Voucher.SINGLE_USE and offer.max_global_applications is None:
        max_uses_count = 10000
    else:
        max_uses_count = offer.max_global_applications

    coupon_data = {
        'Code': voucher.code,
        'Maximum Coupon Usage': max_uses_count,
        'Redemption Count': redemption_count,
        'Status': status,
        'URL': url
    }

    return coupon_data
예제 #5
0
 def send_email(self, order):
     """ Sends an email with enrollment code order information. """
     # Note (multi-courses): Change from a course_name to a list of course names.
     product = order.lines.first().product
     course = Course.objects.get(id=product.attr.course_key)
     send_notification(order.user,
                       'ORDER_WITH_CSV',
                       context={
                           'contact_url':
                           get_lms_url('/contact'),
                           'course_name':
                           course.name,
                           'download_csv_link':
                           get_ecommerce_url(
                               reverse('coupons:enrollment_code_csv',
                                       args=[order.number])),
                           'enrollment_code_title':
                           product.title,
                           'order_number':
                           order.number,
                           'partner_name':
                           order.site.siteconfiguration.partner.name,
                           'lms_url':
                           get_lms_url(),
                           'receipt_page_url':
                           get_lms_url('{}?orderNum={}'.format(
                               settings.RECEIPT_PAGE_PATH, order.number)),
                       },
                       site=order.site)
예제 #6
0
def _get_voucher_info_for_coupon_report(voucher):
    offer = voucher.best_offer
    status = _get_voucher_status(voucher, offer)
    path = '{path}?code={code}'.format(path=reverse('coupons:offer'),
                                       code=voucher.code)
    url = get_ecommerce_url(path)

    # Set the max_uses_count for single-use vouchers to 1,
    # for other usage limitations (once per customer and multi-use)
    # which don't have the max global applications limit set,
    # set the max_uses_count to 10000 which is the arbitrary limit Oscar sets:
    # https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/offer/abstract_models.py#L253
    redemption_count = offer.num_applications
    if voucher.usage == Voucher.SINGLE_USE:
        max_uses_count = 1
        redemption_count = voucher.num_orders
    elif voucher.usage != Voucher.SINGLE_USE and offer.max_global_applications is None:
        max_uses_count = OFFER_MAX_USES_DEFAULT
    else:
        max_uses_count = offer.max_global_applications

    coupon_data = {
        _('Code'): voucher.code,
        _('Maximum Coupon Usage'): max_uses_count,
        _('Redemption Count'): redemption_count,
        _('Status'): status,
        _('URL'): url
    }

    return coupon_data
예제 #7
0
    def assert_report_row(self, row, coupon, voucher):
        """ Verify that the row fields contain the right data. """
        offer = voucher.offers.all().first()
        discount_data = get_voucher_discount_info(
            offer.benefit,
            offer.condition.range.catalog.stock_records.first().price_excl_tax
        )
        coupon_type = _('Discount') if discount_data['is_discounted'] else _('Enrollment')
        discount_percentage = _("{percentage} %").format(percentage=discount_data['discount_percentage'])
        discount_amount = currency(discount_data['discount_value'])

        self.assertEqual(row['Coupon Type'], coupon_type)
        self.assertEqual(row['Category'], ProductCategory.objects.get(product=coupon).category.name)
        self.assertEqual(row['Discount Percentage'], discount_percentage)
        self.assertEqual(row['Discount Amount'], discount_amount)
        self.assertEqual(row['Client'], coupon.client.name)
        self.assertEqual(
            row['URL'],
            get_ecommerce_url() + self.REDEMPTION_URL.format(voucher.code)
        )
        self.assertEqual(row['Note'], coupon.attr.note)
        self.assertEqual(row['Created By'], coupon.history.first().history_user.full_name)
        self.assertEqual(row['Create Date'], coupon.history.latest().history_date.strftime("%b %d, %y"))
        self.assertEqual(row['Coupon Start Date'], voucher.start_datetime.strftime("%b %d, %y"))
        self.assertEqual(row['Coupon Expiry Date'], voucher.end_datetime.strftime("%b %d, %y"))
예제 #8
0
    def test_get_authorizenet_payment_settings(self):
        """
            Verify the processor returns the required Authorize Net (sdk) setting object properly
        """
        course_id = self.basket.all_lines()[0].product.course_id
        course_id_hash = base64.b64encode(course_id.encode())

        redirect_url = reverse('authorizenet:redirect')
        ecommerce_base_url = get_ecommerce_url()
        return_url = '{}{}?course={}'.format(ecommerce_base_url, redirect_url, course_id_hash)

        payment_button_expected_setting_name = apicontractsv1.settingNameEnum.hostedPaymentButtonOptions
        payment_button_expected_setting_value = json.dumps({'text': 'Pay'})

        payment_return_expected_setting_name = apicontractsv1.settingNameEnum.hostedPaymentReturnOptions
        payment_return_configrations = {
            'url': return_url,
            'urlText': 'Continue',
            'cancelUrl': self.processor.cancel_url,
            'cancelUrlText': 'Cancel'
        }
        payment_return_expected_setting_value = json.dumps(payment_return_configrations)
        actual_settings = self.processor.get_authorizenet_payment_settings(self.basket)

        self.assertEqual(actual_settings.setting[0].settingName, payment_button_expected_setting_name)
        self.assertEqual(actual_settings.setting[0].settingValue, payment_button_expected_setting_value)
        self.assertEqual(actual_settings.setting[1].settingName, payment_return_expected_setting_name)
        self.assertEqual(actual_settings.setting[1].settingValue, payment_return_expected_setting_value)
예제 #9
0
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        """
        out_trade_no = create_trade_id(basket.id)
        body = "BUY {amount} {currency}".format(amount=basket.total_incl_tax,
                                                currency=basket.currency)
        order_price = basket.total_incl_tax
        total_fee = int(order_price * 100)
        attach_data = urljoin(get_ecommerce_url(),
                              reverse('wechatpay:execute'))

        wxpayconf_pub = WxPayConf_pub()
        unifiedorder_pub = UnifiedOrder_pub()
        unifiedorder_pub.setParameter("body", body)
        unifiedorder_pub.setParameter("out_trade_no", out_trade_no)
        unifiedorder_pub.setParameter("total_fee", str(total_fee))
        unifiedorder_pub.setParameter("notify_url", wxpayconf_pub.NOTIFY_URL)
        unifiedorder_pub.setParameter("trade_type", "NATIVE")
        unifiedorder_pub.setParameter("attach", attach_data)

        code_url = unifiedorder_pub.getCodeUrl()
        approval_url = get_ecommerce_url() + reverse("wechatpay:page")
        if not PaymentProcessorResponse.objects.filter(
                processor_name=self.NAME,
                basket=basket).update(transaction_id=out_trade_no):
            self.record_processor_response({},
                                           transaction_id=out_trade_no,
                                           basket=basket)

        parameters = {
            'payment_page_url':
            approval_url,
            'code_url':
            code_url,
            'basket_id':
            basket.id,
            'request_page':
            '{payurl}?out_trade_no={param}'.format(
                payurl=(get_ecommerce_url() + reverse("wechatpay:result")),
                param=out_trade_no),
        }
        return parameters
예제 #10
0
class AliPay(BasePaymentProcessor):

    NAME = 'alipay'
    DEFAULT_PROFILE_NAME = 'default'

    def __init__(self, site):
        super(AliPay, self).__init__(site)

    @property
    def cancel_url(self):
        return get_ecommerce_url(self.configuration['cancel_checkout_path'])

    @property
    def error_url(self):
        return get_ecommerce_url(self.configuration['error_path'])

    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        approval_url
        """
        trade_id = create_trade_id(basket.id)
        try:
            course_data = get_course_info_from_catalog(
                request.site,
                basket.all_lines()[0].product)
            subject = body = course_data.get('title')
        except Exception, e:
            logger.exception(e)
            subject = body = 'buy course'
        total_fee = str_to_specify_digits(str(basket.total_incl_tax))
        http_host = request.META.get('HTTP_HOST')
        extra_common_param = urljoin(get_ecommerce_url(),
                                     reverse('alipay:execute'))

        approval_url = create_direct_pay_by_user(
            trade_id,
            body,
            subject,
            total_fee,
            http_host,
            extra_common_param=extra_common_param)
        if not PaymentProcessorResponse.objects.filter(
                processor_name=self.NAME,
                basket=basket).update(transaction_id=trade_id):
            self.record_processor_response({},
                                           transaction_id=trade_id,
                                           basket=basket)

        parameters = {
            'payment_page_url': approval_url,
        }
        return parameters
예제 #11
0
    def test_get_transaction_parameters(self):
        """Verify the processor returns the appropriate parameters required to complete a transaction."""
        self.mock_oauth2_response()
        response = self.mock_payment_creation_response(self.basket)

        self._assert_transaction_parameters()
        self.assert_processor_response_recorded(self.processor.NAME, self.PAYMENT_ID, response, basket=self.basket)

        last_request_body = json.loads(httpretty.last_request().body)
        expected = urljoin(get_ecommerce_url(), reverse('paypal_execute'))
        self.assertEqual(last_request_body['redirect_urls']['return_url'], expected)
예제 #12
0
 def send_email(self, order):
     """ Sends an email with enrollment code order information. """
     # Note (multi-courses): Change from a course_name to a list of course names.
     product = order.lines.first().product
     course = Course.objects.get(id=product.attr.course_key)
     send_notification(
         order.user,
         'ORDER_WITH_CSV',
         context={
             'contact_url': get_lms_url('/contact'),
             'course_name': course.name,
             'download_csv_link': get_ecommerce_url(reverse('coupons:enrollment_code_csv', args=[order.number])),
             'enrollment_code_title': product.title,
             'order_number': order.number,
             'partner_name': order.site.siteconfiguration.partner.name,
             'lms_url': get_lms_url(),
             'receipt_page_url': get_lms_url('{}?orderNum={}'.format(settings.RECEIPT_PAGE_PATH, order.number)),
         },
         site=order.site
     )
예제 #13
0
    def assert_report_row(self, row, voucher):
        """
        Verify that the row fields contain the right data.
        Args:
            row (list): Non first row in report
            coupon (Product): Coupon for which the report is generated
            voucher (Voucher): Voucher associated with the Coupon
        """
        offer = voucher.offers.first()
        if voucher.usage == Voucher.SINGLE_USE:
            max_uses_count = 1
        elif voucher.usage != Voucher.SINGLE_USE and offer.max_global_applications is None:
            max_uses_count = 10000
        else:
            max_uses_count = offer.max_global_applications

        self.assertEqual(row['Maximum Coupon Usage'], max_uses_count)
        self.assertEqual(row['Code'], voucher.code)
        self.assertEqual(
            row['URL'],
            get_ecommerce_url() + self.REDEMPTION_URL.format(voucher.code))
예제 #14
0
    def assert_report_row(self, row, voucher):
        """
        Verify that the row fields contain the right data.
        Args:
            row (list): Non first row in report
            coupon (Product): Coupon for which the report is generated
            voucher (Voucher): Voucher associated with the Coupon
        """
        offer = voucher.offers.first()
        if voucher.usage == Voucher.SINGLE_USE:
            max_uses_count = 1
        elif voucher.usage != Voucher.SINGLE_USE and offer.max_global_applications is None:
            max_uses_count = 10000
        else:
            max_uses_count = offer.max_global_applications

        self.assertEqual(row['Maximum Coupon Usage'], max_uses_count)
        self.assertEqual(row['Code'], voucher.code)
        self.assertEqual(
            row['URL'],
            get_ecommerce_url() + self.REDEMPTION_URL.format(voucher.code)
        )
예제 #15
0
 def error_url(self):
     return get_ecommerce_url(self.configuration['error_path'])
예제 #16
0
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        Create a new PayPal payment.

        Arguments:
            basket (Basket): The basket of products being purchased.
            request (Request, optional): A Request object which is used to construct PayPal's `return_url`.
            use_client_side_checkout (bool, optional): This value is not used.
            **kwargs: Additional parameters; not used by this method.

        Returns:
            dict: PayPal-specific parameters required to complete a transaction. Must contain a URL
                to which users can be directed in order to approve a newly created payment.

        Raises:
            GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented
                a payment from being created.
        """
        # PayPal requires that item names be at most 127 characters long.
        PAYPAL_FREE_FORM_FIELD_MAX_SIZE = 127
        return_url = urljoin(get_ecommerce_url(), reverse('paypal:execute'))
        data = {
            'intent':
            'sale',
            'redirect_urls': {
                'return_url': return_url,
                'cancel_url': self.cancel_url,
            },
            'payer': {
                'payment_method': 'paypal',
            },
            'transactions': [{
                'amount': {
                    'total': str(basket.total_incl_tax),
                    'currency': basket.currency,
                },
                # Paypal allows us to send additional transaction related data in 'description' & 'custom' field
                # Free form field, max length 127 characters
                # description : program_id:<program_id>
                'description':
                "program_id:{}".format(get_basket_program_uuid(basket)),
                'item_list': {
                    'items': [
                        {
                            'quantity':
                            line.quantity,
                            # PayPal requires that item names be at most 127 characters long.
                            # for courseid we're using 'name' field along with title,
                            # concatenated field will be 'courseid|title'
                            'name':
                            middle_truncate(self.get_courseid_title(line),
                                            PAYPAL_FREE_FORM_FIELD_MAX_SIZE),
                            # PayPal requires that the sum of all the item prices (where price = price * quantity)
                            # equals to the total amount set in amount['total'].
                            'price':
                            str(line.line_price_incl_tax_incl_discounts /
                                line.quantity),
                            'currency':
                            line.stockrecord.price_currency,
                        } for line in basket.all_lines()
                    ],
                },
                'invoice_number':
                basket.order_number,
            }],
        }

        if waffle.switch_is_active('create_and_set_webprofile'):
            locale_code = self.resolve_paypal_locale(
                request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME))
            web_profile_id = self.create_temporary_web_profile(locale_code)
            if web_profile_id is not None:
                data['experience_profile_id'] = web_profile_id
        else:
            try:
                web_profile = PaypalWebProfile.objects.get(
                    name=self.DEFAULT_PROFILE_NAME)
                data['experience_profile_id'] = web_profile.id
            except PaypalWebProfile.DoesNotExist:
                pass

        available_attempts = 1
        if waffle.switch_is_active('PAYPAL_RETRY_ATTEMPTS'):
            available_attempts = self.retry_attempts

        for i in range(1, available_attempts + 1):
            try:
                payment = paypalrestsdk.Payment(data, api=self.paypal_api)
                payment.create()
                if payment.success():
                    break
                if i < available_attempts:
                    logger.warning(
                        u"Creating PayPal payment for basket [%d] was unsuccessful. Will retry.",
                        basket.id,
                        exc_info=True)
                else:
                    error = self._get_error(payment)
                    # pylint: disable=unsubscriptable-object
                    entry = self.record_processor_response(
                        error, transaction_id=error['debug_id'], basket=basket)
                    logger.error(u"%s [%d], %s [%d].",
                                 "Failed to create PayPal payment for basket",
                                 basket.id,
                                 "PayPal's response recorded in entry",
                                 entry.id,
                                 exc_info=True)
                    raise GatewayError(error)

            except:  # pylint: disable=bare-except
                if i < available_attempts:
                    logger.warning(
                        u"Creating PayPal payment for basket [%d] resulted in an exception. Will retry.",
                        basket.id,
                        exc_info=True)
                else:
                    logger.exception(
                        u"After %d retries, creating PayPal payment for basket [%d] still experienced exception.",
                        i, basket.id)
                    raise

        entry = self.record_processor_response(payment.to_dict(),
                                               transaction_id=payment.id,
                                               basket=basket)
        logger.info(
            "Successfully created PayPal payment [%s] for basket [%d].",
            payment.id, basket.id)

        for link in payment.links:
            if link.rel == 'approval_url':
                approval_url = link.href
                break
        else:
            logger.error(
                "Approval URL missing from PayPal payment [%s]. PayPal's response was recorded in entry [%d].",
                payment.id, entry.id)
            raise GatewayError(
                'Approval URL missing from PayPal payment response. See entry [{}] for details.'
                .format(entry.id))

        parameters = {
            'payment_page_url': approval_url,
        }

        return parameters
예제 #17
0
 def receipt_page_url(self):
     return urljoin(get_ecommerce_url(), reverse('redsys:execute'))
예제 #18
0
 def get_redeem_url(self, obj):
     url = get_ecommerce_url('/coupons/offer/')
     return '{url}?code={code}'.format(url=url, code=obj.code)
예제 #19
0
    def get(self, request, number):
        """
        Creates a CSV for the order. The structure of the CSV looks like this:

           > Order Number:,EDX-100001

           > Seat in Demo with verified certificate (and ID verification)
           > Code,Redemption URL
           > J4HDI5OAUGCSUJJ3,ecommerce.server?code=J4HDI5OAUGCSUJJ3
           > OZCRR6WXLWGAFWZR,ecommerce.server?code=OZCRR6WXLWGAFWZR
           > 6KPYL6IO6Y3XL7SI,ecommerce.server?code=6KPYL6IO6Y3XL7SI
           > NPIJWIKNLRURYVU2,ecommerce.server?code=NPIJWIKNLRURYVU2
           > 6SZULKPZQYACAODC,ecommerce.server?code=6SZULKPZQYACAODC
           >

        Args:
            request (Request): The GET request
            number (str): Number of the order

        Returns:
            HttpResponse

        Raises:
            Http404: When an order number for a non-existing order is passed.
            PermissionDenied: When a user tries to download a CSV for an order that he did not make.

        """
        try:
            order = Order.objects.get(number=number)
        except Order.DoesNotExist:
            raise Http404('Order not found.')

        if request.user != order.user and not request.user.is_staff:
            raise PermissionDenied

        file_name = 'Enrollment code CSV order num {}'.format(order.number)
        file_name = '{filename}.csv'.format(filename=slugify(file_name))

        response = HttpResponse(content_type='text/csv')
        response[
            'Content-Disposition'] = 'attachment; filename={filename}'.format(
                filename=file_name)

        redeem_url = get_ecommerce_url(reverse('coupons:offer'))
        voucher_field_names = ('Code', 'Redemption URL', 'Name Of Employee',
                               'Date Of Distribution', 'Employee Email')
        voucher_writer = csv.DictWriter(response,
                                        fieldnames=voucher_field_names)

        writer = csv.writer(response)
        writer.writerow(('Order Number:', order.number))
        writer.writerow([])

        order_line_vouchers = OrderLineVouchers.objects.filter(
            line__order=order)
        for order_line_voucher in order_line_vouchers:
            writer.writerow([order_line_voucher.line.product.title])
            voucher_writer.writeheader()

            for voucher in order_line_voucher.vouchers.all():
                voucher_writer.writerow({
                    voucher_field_names[0]:
                    voucher.code,
                    voucher_field_names[1]:
                    '{url}?code={code}'.format(url=redeem_url,
                                               code=voucher.code)
                })
            writer.writerow([])
        return response
예제 #20
0
    def get_transaction_parameters(self, basket, request=None):
        """
        Create a new PayPal payment.

        Arguments:
            basket (Basket): The basket of products being purchased.

        Keyword Arguments:
            request (Request): A Request object which is used to construct PayPal's `return_url`.

        Returns:
            dict: PayPal-specific parameters required to complete a transaction. Must contain a URL
                to which users can be directed in order to approve a newly created payment.

        Raises:
            GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented
                a payment from being created.
        """
        return_url = urljoin(get_ecommerce_url(), reverse('paypal_execute'))
        data = {
            'intent': 'sale',
            'redirect_urls': {
                'return_url': return_url,
                'cancel_url': self.cancel_url,
            },
            'payer': {
                'payment_method': 'paypal',
            },
            'transactions': [{
                'amount': {
                    'total': unicode(basket.total_incl_tax),
                    'currency': basket.currency,
                },
                'item_list': {
                    'items': [
                        {
                            'quantity': line.quantity,
                            # PayPal requires that item names be at most 127 characters long.
                            'name': middle_truncate(line.product.title, 127),
                            # PayPal requires that the sum of all the item prices (where price = price * quantity)
                            # equals to the total amount set in amount['total'].
                            'price': unicode(line.line_price_incl_tax_incl_discounts / line.quantity),
                            'currency': line.stockrecord.price_currency,
                        }
                        for line in basket.all_lines()
                    ],
                },
                'invoice_number': basket.order_number,
            }],
        }

        try:
            web_profile = PaypalWebProfile.objects.get(name=self.DEFAULT_PROFILE_NAME)
            data['experience_profile_id'] = web_profile.id
        except PaypalWebProfile.DoesNotExist:
            pass

        payment = paypalrestsdk.Payment(data, api=self.paypal_api)
        payment.create()

        # Raise an exception for payments that were not successfully created. Consuming code is
        # responsible for handling the exception.
        if not payment.success():
            error = self._get_error(payment)
            entry = self.record_processor_response(error, transaction_id=error['debug_id'], basket=basket)  # pylint: disable=unsubscriptable-object

            logger.error(
                u"Failed to create PayPal payment for basket [%d]. PayPal's response was recorded in entry [%d].",
                basket.id,
                entry.id
            )

            raise GatewayError(error)

        entry = self.record_processor_response(payment.to_dict(), transaction_id=payment.id, basket=basket)
        logger.info(u"Successfully created PayPal payment [%s] for basket [%d].", payment.id, basket.id)

        for link in payment.links:
            if link.rel == 'approval_url':
                approval_url = link.href
                break
        else:
            logger.error(
                u"Approval URL missing from PayPal payment [%s]. PayPal's response was recorded in entry [%d].",
                payment.id,
                entry.id
            )
            raise GatewayError(
                'Approval URL missing from PayPal payment response. See entry [{}] for details.'.format(entry.id))

        parameters = {
            'payment_page_url': approval_url,
        }

        return parameters
예제 #21
0
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        Fetch a payment page url from the Saferpay API and redirect the user to this url.

        Documentation:
        https://saferpay.github.io/sndbx/Integration_PP.html#pp-initialize
        https://saferpay.github.io/jsonapi/index.html#Payment_v1_PaymentPage_Initialize
        """
        # Create PPR early to obtain an ID that can be passed to the return urls
        success_payment_processor_response = self.record_processor_response(
            {}, transaction_id=None, basket=basket)
        description = "\n".join(
            [line.product.title for line in basket.lines.all()])
        data = {
            "TerminalId": self.terminal_id,
            "Payment": {
                "Amount": {
                    # Amount in cents
                    "Value": str(int(100 * basket.total_incl_tax)),
                    "CurrencyCode": basket.currency,
                },
                "OrderId": str(basket.order_number),
                "Description": description,
            },
            "ReturnUrls": {
                "Success":
                get_ecommerce_url(
                    reverse(
                        "saferpay:callback_success",
                        kwargs={
                            "ppr_id": success_payment_processor_response.id
                        },
                    )),
                "Fail":
                get_ecommerce_url(self.cancel_url),
            },
        }
        response_data = self.make_api_json_request(
            "Payment/v1/PaymentPage/Initialize",
            method="POST",
            data=data,
            basket=basket)

        try:
            payment_page_url = response_data["RedirectUrl"]
            transaction_id = response_data["Token"]
        except KeyError:
            message = "Could not parse RedirectUrl field from response: content={}".format(
                json.dumps(response_data))
            self.raise_api_error(basket, message)

        # Save payment processor response
        success_payment_processor_response.transaction_id = transaction_id
        success_payment_processor_response.response = response_data
        success_payment_processor_response.save()

        logger.info(
            "Saferpay payment: obtained token=%s for basket=%d",
            transaction_id,
            basket.id,
        )
        return {"payment_page_url": payment_page_url}
예제 #22
0
 def client_side_payment_url(self):
     return urljoin(get_ecommerce_url(), reverse('flutterwave:execute'))
예제 #23
0
 def cancel_page_url(self):
     return get_ecommerce_url(self.configuration['cancel_checkout_path'])
예제 #24
0
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        Create a new Alipay payment.

        Arguments:
            basket (Basket): The basket of products being purchased.
            request (Request, optional): A Request object which is used to construct PayPal's `return_url`.
            use_client_side_checkout (bool, optional): This value is not used.
            **kwargs: Additional parameters; not used by this method.

        Returns:
            dict: PayPal-specific parameters required to complete a transaction. Must contain a URL
                to which users can be directed in order to approve a newly created payment.

        Raises:
            GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented
                a payment from being created.
        """
        return_url = urljoin(get_ecommerce_url(), reverse('alipay:execute'))
        data = {
            'intent':
            'sale',
            'redirect_urls': {
                'return_url': return_url,
                'cancel_url': self.cancel_url,
            },
            'payer': {
                'payment_method': 'alipay',
            },
            'transactions': [{
                'amount': {
                    'total': unicode(basket.total_incl_tax),
                    'currency': basket.currency,
                },
                'item_list': {
                    'items': [
                        {
                            'quantity':
                            line.quantity,
                            # PayPal requires that item names be at most 127 characters long.
                            'name':
                            middle_truncate(line.product.title, 127),
                            # PayPal requires that the sum of all the item prices (where price = price * quantity)
                            # equals to the total amount set in amount['total'].
                            'price':
                            unicode(line.line_price_incl_tax_incl_discounts /
                                    line.quantity),
                            'currency':
                            line.stockrecord.price_currency,
                        } for line in basket.all_lines()
                    ],
                },
                'invoice_number': basket.order_number,
            }],
        }

        if waffle.switch_is_active('create_and_set_webprofile'):
            locale_code = self.resolve_alipay_locale(
                request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME))
            web_profile_id = self.create_temporary_web_profile(locale_code)
            if web_profile_id is not None:
                data['experience_profile_id'] = web_profile_id
        else:
            try:
                web_profile = AlipayWebProfile.objects.get(
                    name=self.DEFAULT_PROFILE_NAME)
                data['experience_profile_id'] = web_profile.id
            except AlipayWebProfile.DoesNotExist:
                pass

        available_attempts = 1
        if waffle.switch_is_active('PAYPAL_RETRY_ATTEMPTS'):
            available_attempts = self.retry_attempts

        for i in range(1, available_attempts + 1):
            try:
                payment = alipay_sdk.Payment(data, api=self.alipay_api)
                payment.create()
                if payment.success():
                    break
                else:
                    if i < available_attempts:
                        logger.warning(
                            u"Creating AliPay payment for basket [%d] was unsuccessful. Will retry.",
                            basket.id,
                            exc_info=True)
                    else:
                        error = self._get_error(payment)
                        # pylint: disable=unsubscriptable-object
                        entry = self.record_processor_response(
                            error,
                            transaction_id=error['debug_id'],
                            basket=basket)
                        logger.error(
                            u"%s [%d], %s [%d].",
                            "Failed to create AliPay payment for basket",
                            basket.id,
                            "AliPay's response recorded in entry",
                            entry.id,
                            exc_info=True)
                        raise GatewayError(error)

            except:  # pylint: disable=bare-except
                if i < available_attempts:
                    logger.warning(
                        u"Creating AliPay payment for basket [%d] resulted in an exception. Will retry.",
                        basket.id,
                        exc_info=True)
                else:
                    logger.exception(
                        u"After %d retries, creating AliPay payment for basket [%d] still experienced exception.",
                        i, basket.id)
                    raise

        entry = self.record_processor_response(payment.to_dict(),
                                               transaction_id=payment.id,
                                               basket=basket)
        logger.info(
            "Successfully created AliPay payment [%s] for basket [%d].",
            payment.id, basket.id)
        id = payment.id
        order_string = self.alipay_api.api_alipay_trade_page_pay(
            out_trade_no=payment.id,
            total_amount=unicode(basket.total_incl_tax),  #0.01,
            subject=middle_truncate(line.product.title, 127),
            return_url=return_url,
        )

        parameters = {
            'payment_page_url':
            self.alipay_api.default_endpoint() + '?' + order_string,
            #'payment_page_url': 'error_test',
        }

        return parameters
예제 #25
0
 def test_get_ecommerce_url_with_no_current_request(self):
     with self.assertRaises(MissingRequestError):
         get_ecommerce_url()
예제 #26
0
 def get_redeem_url(self, obj):
     url = get_ecommerce_url('/coupons/offer/')
     return '{url}?code={code}'.format(url=url, code=obj.code)
예제 #27
0
    def get_transaction_parameters(self, basket, request=None):
        """
        Create a new PayPal payment.

        Arguments:
            basket (Basket): The basket of products being purchased.

        Keyword Arguments:
            request (Request): A Request object which is used to construct PayPal's `return_url`.

        Returns:
            dict: PayPal-specific parameters required to complete a transaction. Must contain a URL
                to which users can be directed in order to approve a newly created payment.

        Raises:
            GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented
                a payment from being created.
        """
        return_url = urljoin(get_ecommerce_url(), reverse('paypal_execute'))
        data = {
            'intent': 'sale',
            'redirect_urls': {
                'return_url': return_url,
                'cancel_url': self.cancel_url,
            },
            'payer': {
                'payment_method': 'paypal',
            },
            'transactions': [{
                'amount': {
                    'total': unicode(basket.total_incl_tax),
                    'currency': basket.currency,
                },
                'item_list': {
                    'items': [
                        {
                            'quantity': line.quantity,
                            # PayPal requires that item names be at most 127 characters long.
                            'name': middle_truncate(line.product.title, 127),
                            # PayPal requires that the sum of all the item prices (where price = price * quantity)
                            # equals to the total amount set in amount['total'].
                            'price': unicode(line.line_price_incl_tax_incl_discounts / line.quantity),
                            'currency': line.stockrecord.price_currency,
                        }
                        for line in basket.all_lines()
                    ],
                },
                'invoice_number': basket.order_number,
            }],
        }

        try:
            web_profile = PaypalWebProfile.objects.get(name=self.DEFAULT_PROFILE_NAME)
            data['experience_profile_id'] = web_profile.id
        except PaypalWebProfile.DoesNotExist:
            pass

        payment = paypalrestsdk.Payment(data, api=self.paypal_api)
        payment.create()

        # Raise an exception for payments that were not successfully created. Consuming code is
        # responsible for handling the exception.
        if not payment.success():
            error = self._get_error(payment)
            entry = self.record_processor_response(error, transaction_id=error['debug_id'], basket=basket)  # pylint: disable=unsubscriptable-object

            logger.error(
                u"Failed to create PayPal payment for basket [%d]. PayPal's response was recorded in entry [%d].",
                basket.id,
                entry.id
            )

            raise GatewayError(error)

        entry = self.record_processor_response(payment.to_dict(), transaction_id=payment.id, basket=basket)
        logger.info(u"Successfully created PayPal payment [%s] for basket [%d].", payment.id, basket.id)

        for link in payment.links:
            if link.rel == 'approval_url':
                approval_url = link.href
                break
        else:
            logger.error(
                u"Approval URL missing from PayPal payment [%s]. PayPal's response was recorded in entry [%d].",
                payment.id,
                entry.id
            )
            raise GatewayError(
                'Approval URL missing from PayPal payment response. See entry [{}] for details.'.format(entry.id))

        parameters = {
            'payment_page_url': approval_url,
        }

        return parameters
예제 #28
0
def _get_info_for_coupon_report(coupon, voucher):
    offer = voucher.offers.all().first()
    if offer.condition.range.catalog:
        coupon_stockrecord = StockRecord.objects.get(product=coupon)
        invoiced_amount = currency(coupon_stockrecord.price_excl_tax)
        seat_stockrecord = offer.condition.range.catalog.stock_records.first()
        course_id = seat_stockrecord.product.attr.course_key
        course_organization = CourseKey.from_string(course_id).org
        price = currency(seat_stockrecord.price_excl_tax)
        discount_data = get_voucher_discount_info(
            offer.benefit, seat_stockrecord.price_excl_tax)
    else:
        # Note (multi-courses): Need to account for multiple seats.
        coupon_stockrecord = None
        invoiced_amount = None
        seat_stockrecord = None
        course_id = None
        course_organization = None
        price = None
        discount_data = None

    history = coupon.history.first()

    if discount_data:
        coupon_type = _('Discount') if discount_data['is_discounted'] else _(
            'Enrollment')
        discount_percentage = _("{percentage} %").format(
            percentage=discount_data['discount_percentage'])
        discount_amount = currency(discount_data['discount_value'])
    else:
        coupon_type = None
        discount_percentage = None
        discount_amount = None

    status = _get_voucher_status(voucher, offer)

    path = '{path}?code={code}'.format(path=reverse('coupons:offer'),
                                       code=voucher.code)
    url = get_ecommerce_url(path)
    author = history.history_user.full_name

    try:
        note = coupon.attr.note
    except AttributeError:
        note = ''

    product_categories = ProductCategory.objects.filter(product=coupon)
    if product_categories:
        category_names = ', '.join(
            [pc.category.name for pc in product_categories])
    else:
        category_names = ''

    # Set the max_uses_count for single-use vouchers to 1,
    # for other usage limitations (once per customer and multi-use in the future)
    # which don't have the max global applications limit set,
    # set the max_uses_count to 10000 which is the arbitrary limit Oscar sets:
    # https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/offer/abstract_models.py#L253
    if voucher.usage == Voucher.SINGLE_USE:
        max_uses_count = 1
    elif voucher.usage != Voucher.SINGLE_USE and offer.max_global_applications is None:
        max_uses_count = 10000
    else:
        max_uses_count = offer.max_global_applications

    return {
        'Coupon Name': voucher.name,
        'Code': voucher.code,
        'Coupon Type': coupon_type,
        'URL': url,
        'Course ID': course_id,
        'Organization': course_organization,
        'Category': category_names,
        'Note': note,
        'Price': price,
        'Invoiced Amount': invoiced_amount,
        'Discount Percentage': discount_percentage,
        'Discount Amount': discount_amount,
        'Status': status,
        'Created By': author,
        'Create Date': history.history_date.strftime("%b %d, %y"),
        'Coupon Start Date': voucher.start_datetime.strftime("%b %d, %y"),
        'Coupon Expiry Date': voucher.end_datetime.strftime("%b %d, %y"),
        'Maximum Coupon Usage': max_uses_count,
        'Redemption Count': offer.num_applications,
    }
예제 #29
0
 def return_url(self):
     redirect_url = reverse('paystack:execute')
     ecommerce_base_url = get_ecommerce_url()
     return "{}{}".format(ecommerce_base_url, redirect_url)
예제 #30
0
def _get_info_for_coupon_report(coupon, voucher):
    offer = voucher.offers.all().first()
    if offer.condition.range.catalog:
        coupon_stockrecord = StockRecord.objects.get(product=coupon)
        invoiced_amount = currency(coupon_stockrecord.price_excl_tax)
        seat_stockrecord = offer.condition.range.catalog.stock_records.first()
        course_id = seat_stockrecord.product.attr.course_key
        course_organization = CourseKey.from_string(course_id).org
        price = currency(seat_stockrecord.price_excl_tax)
        discount_data = get_voucher_discount_info(offer.benefit, seat_stockrecord.price_excl_tax)
    else:
        # Note (multi-courses): Need to account for multiple seats.
        catalog_query = offer.condition.range.catalog_query
        course_seat_types = offer.condition.range.course_seat_types
        course_id = None
        coupon_stockrecord = None
        invoiced_amount = None
        seat_stockrecord = None
        course_organization = None
        price = None
        discount_data = None

    history = coupon.history.first()
    coupon_type, discount_percentage, discount_amount = _get_discount_info(discount_data)
    status = _get_voucher_status(voucher, offer)
    path = '{path}?code={code}'.format(path=reverse('coupons:offer'), code=voucher.code)
    url = get_ecommerce_url(path)
    author = history.history_user.full_name.encode('utf8')

    try:
        note = coupon.attr.note.encode('utf8')
    except AttributeError:
        note = ''

    product_categories = ProductCategory.objects.filter(product=coupon)
    if product_categories:
        category_names = ', '.join([pc.category.name for pc in product_categories])
    else:
        category_names = ''

    # Set the max_uses_count for single-use vouchers to 1,
    # for other usage limitations (once per customer and multi-use)
    # which don't have the max global applications limit set,
    # set the max_uses_count to 10000 which is the arbitrary limit Oscar sets:
    # https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/offer/abstract_models.py#L253
    redemption_count = offer.num_applications
    if voucher.usage == Voucher.SINGLE_USE:
        max_uses_count = 1
        redemption_count = voucher.num_orders
    elif voucher.usage != Voucher.SINGLE_USE and offer.max_global_applications is None:
        max_uses_count = 10000
    else:
        max_uses_count = offer.max_global_applications

    coupon_data = {
        'Coupon Name': voucher.name.encode('utf8'),
        'Code': voucher.code,
        'Coupon Type': coupon_type,
        'URL': url,
        'Category': category_names,
        'Note': note,
        'Price': price,
        'Invoiced Amount': invoiced_amount,
        'Discount Percentage': discount_percentage,
        'Discount Amount': discount_amount,
        'Status': status,
        'Created By': author,
        'Create Date': history.history_date.strftime("%b %d, %y"),
        'Coupon Start Date': voucher.start_datetime.strftime("%b %d, %y"),
        'Coupon Expiry Date': voucher.end_datetime.strftime("%b %d, %y"),
        'Maximum Coupon Usage': max_uses_count,
        'Redemption Count': redemption_count,
    }

    if course_id:
        coupon_data['Course ID'] = course_id
        coupon_data['Organization'] = course_organization
    else:
        coupon_data['Catalog Query'] = catalog_query
        coupon_data['Course Seat Types'] = course_seat_types

    return coupon_data
예제 #31
0
    def get_transaction_parameters(self,
                                   basket,
                                   request=None,
                                   use_client_side_checkout=False,
                                   **kwargs):
        """
        """
        # Get the basket, and make sure it belongs to the current user.
        try:
            basket = request.user.baskets.get(id=basket.id)
        except ObjectDoesNotExist:
            return {}

        try:
            # Freeze the basket so that it cannot be modified
            basket.strategy = request.strategy
            Applicator().apply(basket, request.user, request)
            # basket.freeze()
            if basket.total_incl_tax <= 0:
                return {}

            out_trade_no = create_trade_id(basket.id)
            try:
                course_data = get_course_info_from_catalog(
                    request.site,
                    basket.all_lines()[0].product)
                body = course_data.get('title')
            except Exception, e:
                logger.exception(e)
                body = 'buy course'
            order_price = basket.total_incl_tax
            total_fee = int(order_price * 100)
            attach_data = urljoin(get_ecommerce_url(),
                                  reverse('wechatpay:execute'))

            wxpayconf_pub = WxPayConf_pub()
            unifiedorder_pub = UnifiedOrder_pub()
            unifiedorder_pub.setParameter("body", body)
            unifiedorder_pub.setParameter("out_trade_no", out_trade_no)
            unifiedorder_pub.setParameter("total_fee", str(total_fee))
            unifiedorder_pub.setParameter("notify_url",
                                          wxpayconf_pub.NOTIFY_URL)
            unifiedorder_pub.setParameter("trade_type", "NATIVE")
            unifiedorder_pub.setParameter("attach", attach_data)

            code_url = unifiedorder_pub.getCodeUrl()
            img = qrcode.make(code_url)
            buf = BytesIO()
            img.save(buf, format="PNG")
            qrcode_img = base64.b64encode(buf.getvalue())
            if not PaymentProcessorResponse.objects.filter(
                    processor_name=self.NAME,
                    basket=basket).update(transaction_id=out_trade_no):
                self.record_processor_response({},
                                               transaction_id=out_trade_no,
                                               basket=basket)

            parameters = {
                'qrcode_img': qrcode_img,
            }
            return parameters
예제 #32
0
 def cancel_page_url(self):
     return get_ecommerce_url(self.configuration['cancel_checkout_path'])
예제 #33
0
파일: views.py 프로젝트: EDUlib/ecommerce
    def get(self, request, number):
        """
        Creates a CSV for the order. The structure of the CSV looks like this:

           > Order Number:,EDX-100001

           > Seat in Demo with verified certificate (and ID verification)
           > Code,Redemption URL
           > J4HDI5OAUGCSUJJ3,ecommerce.server?code=J4HDI5OAUGCSUJJ3
           > OZCRR6WXLWGAFWZR,ecommerce.server?code=OZCRR6WXLWGAFWZR
           > 6KPYL6IO6Y3XL7SI,ecommerce.server?code=6KPYL6IO6Y3XL7SI
           > NPIJWIKNLRURYVU2,ecommerce.server?code=NPIJWIKNLRURYVU2
           > 6SZULKPZQYACAODC,ecommerce.server?code=6SZULKPZQYACAODC
           >

        Args:
            request (Request): The GET request
            number (str): Number of the order

        Returns:
            HttpResponse

        Raises:
            Http404: When an order number for a non-existing order is passed.
            PermissionDenied: When a user tries to download a CSV for an order that he did not make.

        """
        try:
            order = Order.objects.get(number=number)
        except Order.DoesNotExist:
            raise Http404('Order not found.')

        if request.user != order.user and not request.user.is_staff:
            raise PermissionDenied

        file_name = 'Enrollment code CSV order num {}'.format(order.number)
        file_name = '{filename}.csv'.format(filename=slugify(file_name))

        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename={filename}'.format(filename=file_name)

        redeem_url = get_ecommerce_url(reverse('coupons:offer'))
        voucher_field_names = ('Code', 'Redemption URL')
        voucher_writer = csv.DictWriter(response, fieldnames=voucher_field_names)

        writer = csv.writer(response)
        writer.writerow(('Order Number:', order.number))
        writer.writerow([])

        order_line_vouchers = OrderLineVouchers.objects.filter(line__order=order)
        for order_line_voucher in order_line_vouchers:
            writer.writerow([order_line_voucher.line.product.title])
            voucher_writer.writeheader()

            for voucher in order_line_voucher.vouchers.all():
                voucher_writer.writerow({
                    voucher_field_names[0]: voucher.code,
                    voucher_field_names[1]: '{url}?code={code}'.format(url=redeem_url, code=voucher.code)
                })
            writer.writerow([])
        return response
예제 #34
0
 def error_page_url(self):
     return get_ecommerce_url(
         self.configuration.get('urlko', '/checkout/error/'))
예제 #35
0
 def test_get_ecommerce_url_with_no_current_request(self):
     with self.assertRaises(MissingRequestError):
         get_ecommerce_url()