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
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"))
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
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
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)
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
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"))
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)
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
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
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)
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 )
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))
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) )
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): """ 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
def receipt_page_url(self): return urljoin(get_ecommerce_url(), reverse('redsys:execute'))
def get_redeem_url(self, obj): url = get_ecommerce_url('/coupons/offer/') return '{url}?code={code}'.format(url=url, code=obj.code)
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
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
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}
def client_side_payment_url(self): return urljoin(get_ecommerce_url(), reverse('flutterwave:execute'))
def cancel_page_url(self): return get_ecommerce_url(self.configuration['cancel_checkout_path'])
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
def test_get_ecommerce_url_with_no_current_request(self): with self.assertRaises(MissingRequestError): get_ecommerce_url()
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, }
def return_url(self): redirect_url = reverse('paystack:execute') ecommerce_base_url = get_ecommerce_url() return "{}{}".format(ecommerce_base_url, redirect_url)
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
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
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
def error_page_url(self): return get_ecommerce_url( self.configuration.get('urlko', '/checkout/error/'))