def edit_invoice_details(order): """ Update invoice with buyer's address and taxid """ invoice_dict = request.json.get('invoice') if not request.json or not invoice_dict: return api_error(message=_(u"Missing invoice details"), status_code=400) invoice = Invoice.query.get(request.json.get('invoice_id')) if invoice.is_final: return api_error(message=_( u"This invoice has been finalised and hence cannot be modified"), status_code=400) invoice_form = InvoiceForm.from_json(invoice_dict, meta={'csrf': False}) if not invoice_form.validate(): return api_error(message=_(u"Incorrect invoice details"), status_code=400, errors=invoice_form.errors) else: invoice_form.populate_obj(invoice) db.session.commit() return api_success(result={ 'message': 'Invoice updated', 'invoice': jsonify_invoice(invoice) }, doc=_(u"Invoice details added"), status_code=201)
def kharcha(): """ Accepts JSON containing an array of line_items, with the quantity and item_id set for each line_item. Returns JSON of line items in the format: {item_id: {'quantity': Y, 'final_amount': Z, 'discounted_amount': Z, 'discount_policy_ids': ['d1', 'd2']}} """ if not request.json or not request.json.get('line_items'): return api_error(message='Missing line items', status_code=400) line_item_forms = LineItemForm.process_list(request.json.get('line_items')) if not line_item_forms: return api_error(message='Missing line items', status_code=400) # Make line item splits and compute amounts and discounts line_items = LineItem.calculate( [{ 'item_id': li_form.data.get('item_id') } for li_form in line_item_forms for x in range(li_form.data.get('quantity'))], coupons=sanitize_coupons(request.json.get('discount_coupons'))) items_json = jsonify_line_items(line_items) order_final_amount = sum([ values['final_amount'] for values in items_json.values() if values['final_amount'] is not None ]) return jsonify(line_items=items_json, order={'final_amount': order_final_amount})
def process_partial_refund_for_order(order, form_dict): form = RefundTransactionForm.from_json(form_dict, meta={'csrf': False}) if form.validate(): requested_refund_amount = form.amount.data if not order.paid_amount: return api_error( message='Refunds can only be issued for paid orders', status_code=403, errors=['non cancellable']) if (order.refunded_amount + requested_refund_amount) > order.paid_amount: return api_error( message= 'Invalid refund amount, must be lesser than net amount paid for the order', status_code=403, errors=['excess partial refund']) payment = OnlinePayment.query.filter_by( order=order, pg_payment_status=RAZORPAY_PAYMENT_STATUS.CAPTURED).one() rp_resp = razorpay.refund_payment(payment.pg_paymentid, requested_refund_amount) if rp_resp.status_code == 200: rp_refund = rp_resp.json() transaction = PaymentTransaction( order=order, transaction_type=TRANSACTION_TYPE.REFUND, online_payment=payment, currency=CURRENCY.INR, pg_refundid=rp_refund['id'], refunded_at=func.utcnow()) form.populate_obj(transaction) db.session.add(transaction) db.session.commit() send_order_refund_mail.delay(order.id, transaction.amount, transaction.note_to_user) return api_success(result={'order_net_amount': order.net_amount}, doc=_(u"Refund processed for order"), status_code=200) else: raise PaymentGatewayError( "Refund failed for order - {order} with the following details - {msg}" .format(order=order.id, msg=rp_resp.content), 424, "Refund failed. Please try again or contact support at {email}." .format(email=order.organization.contact_email)) else: return api_error(message='Invalid input', status_code=403, errors=form.errors)
def free(order): """ Completes a order which has a final_amount of 0 """ order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) if order_amounts.final_amount == 0: order.confirm_sale() db.session.add(order) db.session.commit() for line_item in order.line_items: line_item.confirm() db.session.add(line_item) if line_item.discount_coupon: line_item.discount_coupon.update_used_count() db.session.add(line_item.discount_coupon) db.session.commit() send_receipt_mail.delay( order.id, subject="{item_collection_title}: Your registration is confirmed!". format(item_collection_title=order.item_collection.title), template='free_order_confirmation_mail.html.jinja2') return api_success(result={'order_id': order.id}, doc=_(u"Free order confirmed"), status_code=201) else: return api_error(message="Free order confirmation failed", status_code=402)
def process_partial_refund_for_order(data_dict): order = data_dict['order'] form = data_dict['form'] request_method = data_dict['request_method'] if request_method == 'GET': return jsonify(form_template=render_form(form=form, title=u"Partial refund", submit=u"Refund", with_chrome=False)) if form.validate_on_submit(): requested_refund_amount = form.amount.data payment = OnlinePayment.query.filter_by(order=order, pg_payment_status=RAZORPAY_PAYMENT_STATUS.CAPTURED).one() rp_resp = razorpay.refund_payment(payment.pg_paymentid, requested_refund_amount) rp_refund = rp_resp.json() if rp_resp.status_code == 200: transaction = PaymentTransaction(order=order, transaction_type=TRANSACTION_TYPE.REFUND, online_payment=payment, currency=CURRENCY.INR, pg_refundid=rp_refund['id'], refunded_at=func.utcnow()) form.populate_obj(transaction) db.session.add(transaction) db.session.commit() send_order_refund_mail.queue(order.id, transaction.amount, transaction.note_to_user) return api_success(result={'order_net_amount': order.net_amount}, doc=_(u"Refund processed for order"), status_code=200) else: raise PaymentGatewayError("Refund failed for order - {order} with the following details - {msg}".format(order=order.id, msg=rp_refund['error']['description']), 424, "Refund failed. {reason}. Please try again or contact support at {email}.".format(reason=rp_refund['error']['description'], email=order.organization.contact_email)) else: return api_error(message='Invalid input', status_code=403, errors=form.errors)
def admin_edit_discount_policy(discount_policy): discount_policy_error_msg = _( u"The discount could not be updated. Please rectify the indicated issues" ) if discount_policy.is_price_based and discount_policy.items: discount_policy_form = PriceBasedDiscountPolicyForm( obj=discount_policy, model=DiscountPolicy) discount_price = Price.query.filter_by( item=discount_policy.items[0], discount_policy=discount_policy).one() discount_price_form = DiscountPriceForm(obj=discount_price, model=Price, parent=discount_policy) if not discount_price_form.validate_on_submit(): return api_error(message=_( u"There was an issue with the price. Please rectify the indicated issues" ), status_code=400, errors=discount_price_form.errors) discount_price_form.populate_obj(discount_price) if discount_policy.items and discount_price.item is not discount_policy.items[ 0]: discount_policy.items = [discount_price.item] elif discount_policy.is_coupon: discount_policy_form = CouponBasedDiscountPolicyForm( obj=discount_policy, model=DiscountPolicy) elif discount_policy.is_automatic: discount_policy_form = AutomaticDiscountPolicyForm( obj=discount_policy, model=DiscountPolicy) else: return api_error(message=_(u"Incorrect discount type"), status_code=400) if discount_policy_form.validate_on_submit(): discount_policy_form.populate_obj(discount_policy) db.session.commit() return api_success(result={ 'discount_policy': jsonify_discount_policy(discount_policy) }, doc="Discount policy updated.", status_code=200) else: return api_error(message=discount_policy_error_msg, status_code=400, errors=discount_policy_form.errors)
def payment(order): """ Accepts JSON containing `pg_paymentid`. Creates a payment object, attempts to 'capture' the payment from Razorpay, and returns a JSON containing the result of the operation. A successful capture results in a `payment_transaction` registered against the order. """ if not request.json.get('pg_paymentid'): return api_error(message="Missing payment id", status_code=400) order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) online_payment = OnlinePayment( pg_paymentid=request.json.get('pg_paymentid'), order=order) rp_resp = razorpay.capture_payment(online_payment.pg_paymentid, order_amounts.final_amount) if rp_resp.status_code == 200: online_payment.confirm() db.session.add(online_payment) # Only INR is supported as of now transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.add(order) invoice_organization = order.organization.invoicer if order.organization.invoicer else order.organization invoice = Invoice(order=order, organization=invoice_organization) db.session.add(invoice) db.session.commit() for line_item in order.line_items: line_item.confirm() db.session.add(line_item) if line_item.discount_coupon: line_item.discount_coupon.update_used_count() db.session.add(line_item.discount_coupon) db.session.commit() send_receipt_mail.delay( order.id, subject= "{item_collection_title}: Thank you for your order (#{invoice_no})!" .format(item_collection_title=order.item_collection.title, invoice_no=order.invoice_no)) return api_success(result={'invoice_id': invoice.id}, doc=_(u"Payment verified"), status_code=201) else: online_payment.fail() db.session.add(online_payment) db.session.commit() raise PaymentGatewayError( "Online payment failed for order - {order} with the following details - {msg}" .format(order=order.id, msg=rp_resp.content), 424, 'Your payment failed. Please try again or contact us at {email}.'. format(email=order.organization.contact_email))
def cancel_line_item(line_item): if not line_item.is_cancellable(): return api_error(message='This ticket is not cancellable', status_code=403, errors=['non cancellable']) refund_amount = process_line_item_cancellation(line_item) send_line_item_cancellation_mail.queue(line_item.id, refund_amount) return api_success(result={'cancelled_at': json_date_format(line_item.cancelled_at)}, doc=_(u"Ticket cancelled"), status_code=200)
def cancel_line_item(line_item): if not line_item.is_cancellable(): return api_error(message='This ticket is not cancellable', status_code=403, errors=['non cancellable']) refund_amount = process_line_item_cancellation(line_item) send_line_item_cancellation_mail.delay(line_item.id, refund_amount) return api_success( result={'cancelled_at': json_date_format(line_item.cancelled_at)}, doc=_(u"Ticket cancelled"), status_code=200)
def kharcha(): """ Accepts JSON containing an array of line_items, with the quantity and item_id set for each line_item. Returns JSON of line items in the format: {item_id: {'quantity': Y, 'final_amount': Z, 'discounted_amount': Z, 'discount_policy_ids': ['d1', 'd2']}} """ if not request.json or not request.json.get('line_items'): return api_error(message='Missing line items', status_code=400) line_item_forms = LineItemForm.process_list(request.json.get('line_items')) if not line_item_forms: return api_error(message='Missing line items', status_code=400) # Make line item splits and compute amounts and discounts line_items = LineItem.calculate([{'item_id': li_form.data.get('item_id')} for li_form in line_item_forms for x in range(li_form.data.get('quantity'))], coupons=sanitize_coupons(request.json.get('discount_coupons'))) items_json = jsonify_line_items(line_items) order_final_amount = sum([values['final_amount'] for values in items_json.values() if values['final_amount'] is not None]) return jsonify(line_items=items_json, order={'final_amount': order_final_amount})
def edit_invoice_details(order): """ Update invoice with buyer's address and taxid """ if not order.is_confirmed: abort(404) invoice_dict = request.json.get('invoice') if not request.json or not invoice_dict: return api_error(message=_(u"Missing invoice details"), status_code=400) invoice = Invoice.query.get(request.json.get('invoice_id')) if invoice.is_final: return api_error(message=_(u"This invoice has been finalised and hence cannot be modified"), status_code=400) invoice_form = InvoiceForm.from_json(invoice_dict, meta={'csrf': False}) if not invoice_form.validate(): return api_error(message=_(u"Incorrect invoice details"), status_code=400, errors=invoice_form.errors) else: invoice_form.populate_obj(invoice) db.session.commit() return api_success(result={'message': 'Invoice updated', 'invoice': jsonify_invoice(invoice)}, doc=_(u"Invoice details added"), status_code=201)
def payment(order): """ Accepts JSON containing `pg_paymentid`. Creates a payment object, attempts to 'capture' the payment from Razorpay, and returns a JSON containing the result of the operation. A successful capture results in a `payment_transaction` registered against the order. """ if not request.json.get('pg_paymentid'): return api_error(message="Missing payment id", status_code=400) order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) online_payment = OnlinePayment(pg_paymentid=request.json.get('pg_paymentid'), order=order) rp_resp = razorpay.capture_payment(online_payment.pg_paymentid, order_amounts.final_amount) if rp_resp.status_code == 200: online_payment.confirm() db.session.add(online_payment) # Only INR is supported as of now transaction = PaymentTransaction(order=order, online_payment=online_payment, amount=order_amounts.final_amount, currency=CURRENCY.INR) db.session.add(transaction) order.confirm_sale() db.session.add(order) invoice_organization = order.organization.invoicer if order.organization.invoicer else order.organization invoice = Invoice(order=order, organization=invoice_organization) db.session.add(invoice) db.session.commit() for line_item in order.line_items: line_item.confirm() db.session.add(line_item) if line_item.discount_coupon: line_item.discount_coupon.update_used_count() db.session.add(line_item.discount_coupon) db.session.commit() send_receipt_mail.queue(order.id, subject="{item_collection_title}: Thank you for your order (#{invoice_no})!".format(item_collection_title=order.item_collection.title, invoice_no=order.invoice_no)) return api_success(result={'invoice_id': invoice.id}, doc=_(u"Payment verified"), status_code=201) else: online_payment.fail() db.session.add(online_payment) db.session.commit() raise PaymentGatewayError("Online payment failed for order - {order} with the following details - {msg}".format(order=order.id, msg=rp_resp.content), 424, 'Your payment failed. Please try again or contact us at {email}.'.format(email=order.organization.contact_email))
def admin_new_coupon(discount_policy): coupon_form = DiscountCouponForm(parent=discount_policy) coupons = [] if not coupon_form.validate_on_submit(): return api_error(message=_( u"The coupon could not be created. Please rectify the indicated issues" ), status_code=400, errors=coupon_form.errors) if coupon_form.count.data > 1: # Create a signed discount coupon code for x in range(coupon_form.count.data): # No need to store these coupon codes since they are signed coupons.append(discount_policy.gen_signed_code()) else: coupon = DiscountCoupon(discount_policy=discount_policy) coupon_form.populate_obj(coupon) db.session.add(coupon) db.session.commit() coupons.append(coupon.code) return api_success(result={'coupons': coupons}, doc=_(u"Discount coupon created"), status_code=201)
def free(order): """ Completes a order which has a final_amount of 0 """ order_amounts = order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER) if order_amounts.final_amount == 0: order.confirm_sale() db.session.add(order) db.session.commit() for line_item in order.line_items: line_item.confirm() db.session.add(line_item) if line_item.discount_coupon: line_item.discount_coupon.update_used_count() db.session.add(line_item.discount_coupon) db.session.commit() send_receipt_mail.queue(order.id, subject="{item_collection_title}: Your registration is confirmed!".format(item_collection_title=order.item_collection.title), template='free_order_confirmation_mail.html.jinja2') return api_success(result={'order_id': order.id}, doc=_(u"Free order confirmed"), status_code=201) else: return api_error(message="Free order confirmation failed", status_code=402)
def admin_new_discount_policy(organization): discount_policy = DiscountPolicy(organization=organization) discount_policy_form = DiscountPolicyForm(model=DiscountPolicy) discount_policy_form.populate_obj(discount_policy) discount_policy_error_msg = _( u"The discount could not be created. Please rectify the indicated issues" ) if discount_policy.is_price_based: discount_policy_form = PriceBasedDiscountPolicyForm( model=DiscountPolicy, parent=discount_policy.organization) with db.session.no_autoflush: if not discount_policy_form.validate_on_submit(): return api_error(message=discount_policy_error_msg, status_code=400, errors=discount_policy_form.errors) discount_policy_form.populate_obj(discount_policy) discount_policy.make_name() discount_price_form = DiscountPriceForm(model=Price, parent=discount_policy) if not discount_price_form.validate_on_submit(): return api_error(message=_( u"There was an issue with the price. Please rectify the indicated issues" ), status_code=400, errors=discount_price_form.errors) discount_price = Price(discount_policy=discount_policy) discount_price_form.populate_obj(discount_price) discount_price.make_name() db.session.add(discount_price) discount_policy.items.append(discount_price.item) elif discount_policy.is_coupon: discount_policy_form = CouponBasedDiscountPolicyForm( model=DiscountPolicy, parent=discount_policy.organization) with db.session.no_autoflush: if not discount_policy_form.validate_on_submit(): return api_error(message=discount_policy_error_msg, status_code=400, errors=discount_policy_form.errors) discount_policy_form.populate_obj(discount_policy) discount_policy.make_name() elif discount_policy.is_automatic: discount_policy_form = AutomaticDiscountPolicyForm( model=DiscountPolicy, parent=discount_policy.organization) with db.session.no_autoflush: if not discount_policy_form.validate_on_submit(): return api_error(message=discount_policy_error_msg, status_code=400, errors=discount_policy_form.errors) discount_policy_form.populate_obj(discount_policy) discount_policy.make_name() else: return api_error(message=_(u"Incorrect discount type"), status_code=400) db.session.add(discount_policy) db.session.commit() return api_success( result={'discount_policy': jsonify_discount_policy(discount_policy)}, doc=_(u"New discount policy created"), status_code=201)
def order(item_collection): """ Accepts JSON containing an array of line_items with the quantity and item_id set for each item, and a buyer hash containing `email`, `fullname` and `phone`. Creates a purchase order, and returns a JSON containing the final_amount, order id and the URL to be used to register a payment against the order. """ if not request.json or not request.json.get('line_items'): return api_error(message='Missing line items', status_code=400) line_item_forms = LineItemForm.process_list(request.json.get('line_items')) if not line_item_forms: return api_error(message='Invalid line items', status_code=400) # See comment in LineItemForm about CSRF buyer_form = BuyerForm.from_json(request.json.get('buyer'), meta={'csrf': False}) if not buyer_form.validate(): return api_error(message='Invalid buyer details', status_code=400, errors=buyer_form.errors) invalid_quantity_error_msg = _( u'Selected quantity for ‘{item}’ is not available. Please edit the order and update the quantity' ) item_dicts = Item.get_availability([ line_item_form.data.get('item_id') for line_item_form in line_item_forms ]) for line_item_form in line_item_forms: title_quantity_count = item_dicts.get( line_item_form.data.get('item_id')) if title_quantity_count: item_title, item_quantity_total, line_item_count = title_quantity_count if (line_item_count + line_item_form.data.get('quantity')) > item_quantity_total: return api_error( message=invalid_quantity_error_msg.format(item=item_title), status_code=400, errors=['order calculation error']) else: item = Item.query.get(line_item_form.data.get('item_id')) if line_item_form.data.get('quantity') > item.quantity_total: return api_error( message=invalid_quantity_error_msg.format(item=item.title), status_code=400, errors=['order calculation error']) user = User.query.filter_by(email=buyer_form.email.data).first() order = Order(user=user, organization=item_collection.organization, item_collection=item_collection, buyer_email=buyer_form.email.data, buyer_fullname=buyer_form.fullname.data, buyer_phone=buyer_form.phone.data) line_item_tups = LineItem.calculate( [{ 'item_id': li_form.data.get('item_id') } for li_form in line_item_forms for x in range(li_form.data.get('quantity'))], coupons=sanitize_coupons(request.json.get('discount_coupons'))) for idx, line_item_tup in enumerate(line_item_tups): item = Item.query.get(line_item_tup.item_id) if item.is_available: if line_item_tup.discount_policy_id: policy = DiscountPolicy.query.get( line_item_tup.discount_policy_id) else: policy = None if line_item_tup.discount_coupon_id: coupon = DiscountCoupon.query.get( line_item_tup.discount_coupon_id) else: coupon = None line_item = LineItem( order=order, item=item, discount_policy=policy, line_item_seq=idx + 1, discount_coupon=coupon, ordered_at=datetime.utcnow(), base_amount=line_item_tup.base_amount, discounted_amount=line_item_tup.discounted_amount, final_amount=line_item_tup.base_amount - line_item_tup.discounted_amount) db.session.add(line_item) else: return api_error( message=_(u'‘{item}’ is no longer available.').format( item=item.title), status_code=400, errors=['order calculation error']) db.session.add(order) if request.json.get('order_session'): order_session_form = OrderSessionForm.from_json( request.json.get('order_session'), meta={'csrf': False}) if order_session_form.validate(): order_session = OrderSession(order=order) order_session_form.populate_obj(order_session) db.session.add(order_session) db.session.commit() return api_success(doc=_(u"New purchase order created"), result={ 'order_id': order.id, 'order_access_token': order.access_token, 'payment_url': url_for('payment', order=order.id), 'free_order_url': url_for('free', order=order.id), 'final_amount': order.get_amounts( LINE_ITEM_STATUS.PURCHASE_ORDER).final_amount }, status_code=201)
def order(item_collection): """ Accepts JSON containing an array of line_items with the quantity and item_id set for each item, and a buyer hash containing `email`, `fullname` and `phone`. Creates a purchase order, and returns a JSON containing the final_amount, order id and the URL to be used to register a payment against the order. """ if not request.json or not request.json.get('line_items'): return api_error(message='Missing line items', status_code=400) line_item_forms = LineItemForm.process_list(request.json.get('line_items')) if not line_item_forms: return api_error(message='Invalid line items', status_code=400) # See comment in LineItemForm about CSRF buyer_form = BuyerForm.from_json(request.json.get('buyer'), meta={'csrf': False}) if not buyer_form.validate(): return api_error(message='Invalid buyer details', status_code=400, errors=buyer_form.errors) invalid_quantity_error_msg = _(u'Selected quantity for ‘{item}’ is not available. Please edit the order and update the quantity') item_dicts = Item.get_availability([line_item_form.data.get('item_id') for line_item_form in line_item_forms]) for line_item_form in line_item_forms: title_quantity_count = item_dicts.get(line_item_form.data.get('item_id')) if title_quantity_count: item_title, item_quantity_total, line_item_count = title_quantity_count if (line_item_count + line_item_form.data.get('quantity')) > item_quantity_total: return api_error(message=invalid_quantity_error_msg.format(item=item_title), status_code=400, errors=['order calculation error']) else: item = Item.query.get(line_item_form.data.get('item_id')) if line_item_form.data.get('quantity') > item.quantity_total: return api_error(message=invalid_quantity_error_msg.format(item=item.title), status_code=400, errors=['order calculation error']) user = User.query.filter_by(email=buyer_form.email.data).first() order = Order(user=user, organization=item_collection.organization, item_collection=item_collection, buyer_email=buyer_form.email.data, buyer_fullname=buyer_form.fullname.data, buyer_phone=buyer_form.phone.data) sanitized_coupon_codes = sanitize_coupons(request.json.get('discount_coupons')) line_item_tups = LineItem.calculate([{'item_id': li_form.data.get('item_id')} for li_form in line_item_forms for x in range(li_form.data.get('quantity'))], coupons=sanitized_coupon_codes) for idx, line_item_tup in enumerate(line_item_tups): item = Item.query.get(line_item_tup.item_id) if item.restricted_entry: if not sanitized_coupon_codes or not DiscountPolicy.is_valid_access_coupon(item, sanitized_coupon_codes): # Skip adding a restricted item to the cart without the proper access code break if item.is_available: if line_item_tup.discount_policy_id: policy = DiscountPolicy.query.get(line_item_tup.discount_policy_id) else: policy = None if line_item_tup.discount_coupon_id: coupon = DiscountCoupon.query.get(line_item_tup.discount_coupon_id) else: coupon = None line_item = LineItem(order=order, item=item, discount_policy=policy, line_item_seq=idx+1, discount_coupon=coupon, ordered_at=datetime.utcnow(), base_amount=line_item_tup.base_amount, discounted_amount=line_item_tup.discounted_amount, final_amount=line_item_tup.base_amount-line_item_tup.discounted_amount) db.session.add(line_item) else: return api_error(message=_(u'‘{item}’ is no longer available.').format(item=item.title), status_code=400, errors=['order calculation error']) db.session.add(order) if request.json.get('order_session'): order_session_form = OrderSessionForm.from_json(request.json.get('order_session'), meta={'csrf': False}) if order_session_form.validate(): order_session = OrderSession(order=order) order_session_form.populate_obj(order_session) db.session.add(order_session) db.session.commit() return api_success(doc=_(u"New purchase order created"), result={'order_id': order.id, 'order_access_token': order.access_token, 'payment_url': url_for('payment', order=order.id), 'free_order_url': url_for('free', order=order.id), 'final_amount': order.get_amounts(LINE_ITEM_STATUS.PURCHASE_ORDER).final_amount}, status_code=201)