# coding=utf-8
from __future__ import unicode_literals, absolute_import

import datetime
import json
import math
from xml.etree import ElementTree

from django.db import transaction
from django.conf import settings
from gm_types.gaia import PAYMENT_CHANNEL, COUPON_TYPES, ORDER_STATUS
from gm_types.gaia import SETTLEMENT_TYPE, BENEFIT_TYPE
from gm_types.trade import INSURANCE_TYPE
from gm_types.trade import SETTLEMENT_PAY_MODE
from helios.rpc.exceptions import RPCFaultException

import point

from api.models import SpecialSeckillService, OrderUserInfo
from api.models import Service, ServiceItem, Tag, ServiceTag, TAG_TYPE, Doctor
from api.models.types import ORDER_SOURCE
from api.models import SettlementItem
from api.models import SERVICE_PAYMENT_TYPE
from api.models import POINTS_TYPE
from api.business.tag import TagControl
from api.manager import order_manager, groupbuy_manager
from api.manager.order_manager import place_order, place_order_v2
from api.models import CouponInfo
from api.models import FenxiaoOrderWanghong
from api.models import ORDER_OPERATION_ROLE
from api.models import ORDER_OPERATION_TYPE
from api.models import SETTLEMENT_STATUS
from api.models import Settlement
from api.tasks import settlement_task, order_task
from api.tool.log_tool import alipay_pay_logger
from api.tool.log_tool import apple_pay_logger
from api.tool.log_tool import logging_exception
from api.tool.log_tool import wechat_pay_logger
from rpc.tool.log_tool import info_logger
from api.tool.notification_tool import send_notification
from api.tool.service_tool import get_serviceitem_by_option_id
from api.tool.user_tool import get_user_extra_by_user_id
from api.tool.user_tool import get_user_from_context
from api.tool.user_tool import is_merchant_user
from api.tool.buryiny_point_tool import paid_success
from api.tasks.sign_push_ontime import sign_pay_finish_task

from pay.tool.purchase_tool import settlement_paid_with_log
from pay.tool.purchase_tool import after_settlement_paid, after_settlement_paid_commit
from pay.tool.purchase_tool import send_order_message_to_doctor
from pay.models import PaymentOrder
from pay.models import WechatSettlement
from pay.models import AlipaySettlement
from pay.models import SettlementExtra
from pay.manager import settlement_manager
from pay.manager.settlement_manager import can_cancled_settlement_2_paid
from pay.tool import apple_tool
from pay.tool import own_tool
from pay.tool import purchase_tool, alipay_tool
from pay.tool.alipay_tool import AlipayAppPayTools, AlipayWapPayTools

from rpc.all import get_rpc_remote_invoker
from rpc.decorators import bind_context
from rpc.exceptions import GaiaRPCFaultException
from rpc.tool.error_code import gen, CODES
from rpc.tool.protocol import PushUrlProtocol
from services.custom_phone_service import PhoneService


def _get_services_can_use_coupon(services, coupon_info):
    """
    返回可用此美券的美购/美购多属性
    gengmei_price
    """
    services_avaliable_dict = {}
    has_threshold = coupon_info.coupon.has_threshold
    if has_threshold:
        # 结算单满减券
        flag, error_code = coupon_info.can_be_frozon_for_settlement(services)
        if not flag:
            # 二次校验
            raise gen(CODES.COUPON_UNAVAILABLE)

    for k, v in services.iteritems():
        service = v[0]
        ik_obj = v[1]
        for_settlement = True if has_threshold else False
        if not coupon_info.can_be_frozon(service, ik_obj, for_settlement)[0]:
            continue
        item_key = None if not ik_obj else ik_obj.key
        gengmei_price = service.gengmei_price if not ik_obj else ik_obj.gengmei_price
        seckill = SpecialSeckillService.fetch(service)
        if seckill:
            gengmei_price = seckill.seckill_price
        services_avaliable_dict[(service.id, item_key)] = gengmei_price
    return services_avaliable_dict


def _compute_coupon_shared_per_service(services, coupon_info):
    # 计算每个符合条件的美购应该分得多少金额
    # 按比例分配
    if not coupon_info:
        return {}
    services_avaliable_dict = _get_services_can_use_coupon(services, coupon_info)
    services_shared_dict = {}
    total_value = sum([v for v in services_avaliable_dict.values()])
    left_shared_value = coupon_info.value
    for k, gengmei_price in services_avaliable_dict.iteritems():
        shared_value = math.ceil(gengmei_price / float(total_value) * coupon_info.value)
        if left_shared_value - shared_value > 0:
            # 够分
            services_shared_dict[k] = shared_value
        else:
            services_shared_dict[k] = left_shared_value
        left_shared_value = left_shared_value - shared_value

    return services_shared_dict


def _compute_points_deduction_per_service(services, user_total_points_yuan, services_shared_dict):
    # 计算每个美购应该分得多少积分
    # 用户现存积分排着扣积分,每个必须分得
    """compute deduction per service.
    args:
        services: service_id:(service_obj, item obj) pair, list of tuple
    """
    service_deduction_dict = {}

    for k, v in services.iteritems():
        # if deduction is less than 1, break this loop, points should be used
        # by base 100
        if user_total_points_yuan <= 0:
            break

        service = v[0]
        service_item_obj = v[1]

        item_key = None
        if service_item_obj:
            item_key = service_item_obj.key

        # 单属性
        points_deduction_yuan = service.points_deduction_yuan
        prepayment_price = service.pre_payment_price
        # 多属性
        if item_key:
            points_deduction_yuan = service_item_obj.points_deduction_yuan
            prepayment_price = service_item_obj.pre_payment_price

        seckill = SpecialSeckillService.fetch(service, item_key)
        if seckill:
            prepayment_price = seckill.pre_payment_price
            points_deduction_yuan = 0
        _deduction = 0

        coupon_deduction = services_shared_dict.get(k, 0)
        if coupon_deduction:
            _deduction = min(points_deduction_yuan, user_total_points_yuan, prepayment_price - coupon_deduction - 1)
            if _deduction < 0:
                _deduction = 0
        else:
            _deduction = min(points_deduction_yuan, user_total_points_yuan)

        # 判断是不是双十一专场 如果是 不能使用美分
        unusual, _ = service.is_unusual_special()
        if unusual and not service.is_item_seckill(item_key):
            _deduction = 0
        user_total_points_yuan -= _deduction
        service_deduction_dict[(service.id, item_key)] = _deduction
    return service_deduction_dict


def compute_points_deduction_per_service_v2(services, user_total_points_yuan, services_shared_dict):
    # 计算每个美购应该分得多少积分
    # 用户现存积分排着扣积分,每个必须分得
    """compute deduction per service.
    args:
        services: service_id:(service_obj, item obj) pair, list of tuple
    """
    service_deduction_dict = {}

    for k, v in services.iteritems():
        # if deduction is less than 1, break this loop, points should be used
        # by base 100
        if user_total_points_yuan <= 0:
            break

        service_item_obj = v[1]

        # 默认可以使用美分
        can_use_points = True

        points_deduction_yuan = service_item_obj.points_deduction_yuan
        prepayment_price = service_item_obj.pre_payment_price
        current_price_info = service_item_obj.get_current_price_info()

        if 'selling_rule' in current_price_info and 'can_use_points' in current_price_info['selling_rule']:
            can_use_points = current_price_info['selling_rule']['can_use_points']

        _deduction = 0

        if can_use_points:
            s_k = (k[0], service_item_obj.id)
            coupon_deduction = services_shared_dict.get(s_k, 0)
            if coupon_deduction:
                _deduction = min(points_deduction_yuan, user_total_points_yuan, prepayment_price - coupon_deduction - 1)
            else:
                _deduction = min(points_deduction_yuan, user_total_points_yuan)

        if _deduction < 0:
            _deduction = 0

        # seckill = SpecialSeckillService.fetch(service, item_key)
        # if seckill:
        #     prepayment_price = seckill.pre_payment_price
        #     points_deduction_yuan = 0
        # _deduction = 0
        #
        # coupon_deduction = services_shared_dict.get(k, 0)
        # if coupon_deduction:
        #     _deduction = min(points_deduction_yuan, user_total_points_yuan, prepayment_price - coupon_deduction - 1)
        #     if _deduction < 0:
        #         _deduction = 0
        # else:
        #     _deduction = min(points_deduction_yuan, user_total_points_yuan)
        #
        # # 判断是不是双十一专场 如果是 不能使用美分
        # unusual, _ = service.is_unusual_special()
        # if unusual and not service.is_item_seckill(item_key):
        #     _deduction = 0

        user_total_points_yuan -= _deduction
        service_deduction_dict[k] = _deduction
    return service_deduction_dict


def _bind_order_to_settlement(settlement, orders):
    all_settlementitems = []
    for order in orders:
        item = SettlementItem(
            settlement=settlement,
            order=order,
        )
        all_settlementitems.append(item)

    SettlementItem.objects.bulk_create(all_settlementitems)

    for order in orders:
        # always set order.status as not paid
        order.operate(order.user.person, ORDER_OPERATION_TYPE.NOT_PAID, ORDER_OPERATION_ROLE.USER)
        order.save()


def _create_settlement(person, coupon_info, orders, settlement_name, use_insurance, insurance_type, insurances):
    # TODO: 去掉order.name
    settlement = settlement_manager.create_settlement(person=person)
    settlement.coupon = coupon_info
    settlement.real_payment = sum([o.real_payment for o in orders])  # 实际付款额,扣除美券,扣除美分
    settlement.service_price = sum([o.service_price for o in orders])
    settlement.points_deduction = sum([o.points_deduction for o in orders])  # 美分抵扣的金额
    settlement.coupon_deduction = sum([o.coupon_deduction for o in orders])  # 美劵抵扣的金额
    settlement.name = settlement_name
    settlement.service_payment_type = orders[0].service.payment_type
    if len(orders) == 1 and orders[0].service.is_stage:
        settlement.pay_mode = SETTLEMENT_PAY_MODE.INSTALLMENT
    if settlement.payment < 0:
        settlement.payment = 0

    if len(orders) == 1 and use_insurance:  # 老逻辑 下次上线可干掉
        try:
            order = orders[0]
            result = get_rpc_remote_invoker()['plutus/insurance/create'](
                order_id=order.id,
                insurance_type=insurance_type or INSURANCE_TYPE.YINUO,
                product_id=order.service.zhongan_type,
                phone=order.phone,
                hospital_name=order.service.doctor.hospital.name,
                service_name=order.service.name,
            ).unwrap()
            settlement.real_payment += result.get('premium', 0)
        except:
            logging_exception()
    else:
        result = _create_insurance(insurances)
        settlement.real_payment += result.get('premium', 0)

    settlement.payment = settlement.real_payment
    if settlement.payment <= 0:
        settlement.payment = 1

    settlement.save()
    return settlement


def _update_settlement(settlement, coupon_info, orders, settlement_name, insurances, related_coupon_info_ids,
                       device_id=None, auto_create_diary=True,form_id=""):
    settlement_related_coupon_info_ids = []
    if related_coupon_info_ids:
        settlement_related_coupon_info_ids = related_coupon_info_ids
    elif coupon_info:
        #  原有的写入继续，新的也会写入
        settlement.coupon = coupon_info
        settlement_related_coupon_info_ids = [coupon_info.id]

    settlement.coupon_info_ids_data = json.dumps(settlement_related_coupon_info_ids)

    settlement.real_payment = sum([o.real_payment for o in orders])  # 实际付款额,扣除美券,扣除美分
    settlement.service_price = sum([o.service_price for o in orders])
    settlement.points_deduction = sum([o.points_deduction for o in orders])  # 美分抵扣的金额
    settlement.coupon_deduction = sum([o.coupon_deduction for o in orders])  # 美劵抵扣的金额
    settlement.name = settlement_name
    settlement.service_payment_type = orders[0].service.payment_type
    settlement.device_id = device_id if device_id else ''
    settlement.auto_create_diary = auto_create_diary

    # 零元单
    if settlement_related_coupon_info_ids:
        for coupon_info_id in settlement_related_coupon_info_ids:
            try:
                coupon_obj = CouponInfo.objects.get(id=coupon_info_id)
                benefit_type = coupon_obj.coupon.benefit_type
                discount_rate = coupon_obj.coupon.discount_rate
                if benefit_type == BENEFIT_TYPE.DISCOUNT and discount_rate == 0:
                    settlement.settlement_type = SETTLEMENT_TYPE.ZERO_ORDER
                    settlement.payment = 0
            except Exception:
                logging_exception()
                continue

    if len(orders) == 1 and orders[0].service.is_stage:
        settlement.pay_mode = SETTLEMENT_PAY_MODE.INSTALLMENT

    result = _create_insurance(insurances)
    settlement.real_payment += result.get('premium', 0)

    settlement.payment = settlement.real_payment
    settlement.form_id=form_id
    if settlement.payment <= 0 and settlement.settlement_type != SETTLEMENT_TYPE.ZERO_ORDER:
        settlement.payment = 1
        settlement.real_payment = 1

    settlement.save()


def create_orders(user, source, services, service_points_deduction, services_shared_dict, coupon_info, is_doctor_see,
                  insurance_info):
    orders = []
    insurances = []
    for k, v in services.iteritems():
        s_obj = v[0]
        ik_obj = v[1]
        item_key = ik_obj and ik_obj.key or None

        _points_deduction = service_points_deduction.get((s_obj.id, item_key), 0)
        coupon_deduction = services_shared_dict.get((s_obj.id, item_key), 0)
        _couponinfo_id = coupon_info.id if coupon_deduction else None

        order = place_order(
            user=user,
            service_id=s_obj.id,
            itemkey=item_key,
            source=source,
            coupon_info_id=_couponinfo_id,
            coupon_deduction=coupon_deduction,
            points_deduction=_points_deduction,
            is_doctor_see=is_doctor_see,
        )

        point.remove(
            user_id=user.id,
            reason=POINTS_TYPE.BUY_SERVICE,
            point=point.deduction_to_points(order.points_deduction),
            order_id=order.id
        )

        if (s_obj.id, item_key) in insurance_info or (s_obj.id, None) in insurance_info:
            insurances.append({
                'order_id': order.id,
                'phone': order.phone,
                'hospital_name': s_obj.doctor.hospital.name,
                'service_name': s_obj.name,
                'insurance_service_id': s_obj.insurance,
            })

        orders.append(order)
    return orders, insurances


def create_orders_v2(user, source, service_item_id_to_ordering_info, service_item_id_to_points_deduction,
                     services_shared_dict, coupon_info, is_doctor_see,
                     service_item_id_to_insurance_id, settlement_id):
    orders = []
    insurances = []

    # 按照service_item_id进行处理，主要是避免因为库存导致的潜在死锁
    # 在取消结算单的时候也是按照一样的次序进行处理
    sorted_services = sorted(service_item_id_to_ordering_info.iteritems(), key=lambda kv: kv[0])

    for k, v in sorted_services:
        service_item_id = k
        service_obj = v.service_obj
        service_item_obj = v.service_item_obj

        assert v.number == 1

        point_list = service_item_id_to_points_deduction.get(service_item_id, [])
        assert len(point_list) == 0 or len(point_list) == v.number
        _points_deduction = point_list[0] if len(point_list) > 0 else 0

        coupon_deduction_list = services_shared_dict.get(service_item_id, [])
        assert len(coupon_deduction_list) == 0 or len(coupon_deduction_list) == v.number
        coupon_deduction = coupon_deduction_list[0] if len(coupon_deduction_list) > 0 else 0

        _couponinfo_id = coupon_info.id if coupon_deduction else None

        order = place_order_v2(
            user=user,
            service_obj=service_obj,
            service_item_obj=service_item_obj,
            source=source,
            coupon_info_id=_couponinfo_id,
            coupon_deduction=coupon_deduction,
            points_deduction=_points_deduction,
            is_doctor_see=is_doctor_see,
        )

        point.remove(
            user_id=user.id,
            reason=POINTS_TYPE.BUY_SERVICE,
            point=point.deduction_to_points(order.points_deduction),
            order_id=order.id
        )

        if service_item_id in service_item_id_to_insurance_id:
            insurances.append({
                'order_id': order.id,
                'phone': order.phone,
                'hospital_name': service_obj.doctor.hospital.name,
                'service_name': service_obj.name,
                'insurance_service_id': service_obj.insurance,
            })

        orders.append(order)
    return orders, insurances


def create_orders_v3(user, source, service_item_id_to_ordering_info, service_item_id_to_points_deduction,
                     services_shared_dict, coupon_info, is_doctor_see,
                     service_item_id_to_insurance_id, invite_code, use_point,
                     platform_coupon_id=None, doctor_coupon_id=None, groupbuy_team_id=None):
    """
        记得包含在事务中...

    :return: success, service_item_id_to_error_text={3:"该美购已卖完~"}, orders=[], insurances = [],
             related_coupon_info_ids = [] 实际用上的coupon_info_id
    """

    coupon_and_coupon_info_dict, services_shared_dict, service_item_id_to_points_deduction, \
    related_coupon_info_ids, platform_coupon_and_coupon_info_dict, services_platform_shared_dict, \
    doctor_coupon_and_coupon_info_dict, services_doctor_shared_dict = _get_coupon_related_data(
        user, service_item_id_to_ordering_info, use_point,
        coupon_info, services_shared_dict, service_item_id_to_points_deduction,
        platform_coupon_id, doctor_coupon_id
    )

    # 按照service_item_id进行处理，主要是避免因为库存导致的潜在死锁
    # 在取消结算单的时候也是按照一样的次序进行处理
    sorted_services = sorted(service_item_id_to_ordering_info.items(), key=lambda kv: kv[0])

    lack_stock_service_item_ids = []

    all_sku_ids = [service_item_id for service_item_id, oi in sorted_services]
    # 这里的obj只能用于进行库存操作
    sku_id_to_sku_obj = {si.id: si for si in ServiceItem.objects.filter(id__in=all_sku_ids)}

    # 减库存
    for service_item_id, ordering_info in sorted_services:
        ordering_price_info = ordering_info.ordering_price_info
        number = ordering_info.number

        op = sku_id_to_sku_obj[service_item_id]

        assert number > 0

        price_id = ordering_price_info['id']

        # 应该先对可销售数量进行处理，然后对库存进行处理。
        decrease_price_sale_limit_success = op.decrease_price_sale_limit(
            price_id=price_id, count=number, sale_limit_must_gte_count=True
        )

        decrease_stock_success = False
        if decrease_price_sale_limit_success:
            decrease_stock_success = op.decrease_stock(count=number, sku_stock_must_gte_count=True)

        if decrease_stock_success is False or decrease_price_sale_limit_success is False:
            lack_stock_service_item_ids.append(service_item_id)

    if len(lack_stock_service_item_ids) > 0:
        service_item_id_to_error_text = {
                siid: settlement_manager._ordering_info_error_text_service_sold_out
                for siid in lack_stock_service_item_ids
            }

        return False, service_item_id_to_error_text, [], [], related_coupon_info_ids

    to_create_order_info_list = []

    user_extra_dict = {}
    user_extra = get_user_extra_by_user_id(user.id)
    if user_extra:
        user_extra_dict['name'] = user_extra.name
        user_extra_dict['phone'] = user_extra.phone
        user_extra_dict['address'] = user_extra.address

    all_service_id_set = set([ordering_info.service_obj.id for service_item_id, ordering_info in sorted_services])

    service_id_to_tag_ids = _get_service_id_to_online_3level_tag_ids(all_service_id_set)

    for service_item_id, ordering_info in sorted_services:
        service_obj = ordering_info.service_obj
        service_item_ordering_dict = ordering_info.service_item_ordering_dict
        ordering_price_info = ordering_info.ordering_price_info
        number = ordering_info.number

        for index in range(number):
            point_list = service_item_id_to_points_deduction.get(service_item_id, [])
            assert len(point_list) == 0 or len(point_list) == ordering_info.number
            _points_deduction = point_list[index] if len(point_list) > 0 else 0

            coupon_deduction_list = services_shared_dict.get(service_item_id, [])
            assert len(coupon_deduction_list) == 0 or len(coupon_deduction_list) == ordering_info.number
            coupon_deduction = coupon_deduction_list[index] if len(coupon_deduction_list) > 0 else 0

            platform_coupon_deduction_list = services_platform_shared_dict.get(service_item_id, [])
            assert len(platform_coupon_deduction_list) == 0 or len(
                platform_coupon_deduction_list) == ordering_info.number
            platform_coupon_deduction = platform_coupon_deduction_list[index] if len(
                platform_coupon_deduction_list) > 0 else 0

            doctor_coupon_deduction_list = services_doctor_shared_dict.get(service_item_id, [])
            assert len(doctor_coupon_deduction_list) == 0 or len(doctor_coupon_deduction_list) == ordering_info.number
            doctor_coupon_deduction = doctor_coupon_deduction_list[index] if len(
                doctor_coupon_deduction_list) > 0 else 0

            to_create_order_info = order_manager.create_v3(
                user_id=user.id,
                service_obj=service_obj,
                service_item_ordering_dict=service_item_ordering_dict,
                ordering_price_info=ordering_price_info,
                source=source,
                coupon_deduction=coupon_deduction,
                points_deduction=_points_deduction,
                is_doctor_see=is_doctor_see,
                user_extra_dict=user_extra_dict,
                coupon_and_coupon_info_dict=coupon_and_coupon_info_dict,
                invite_code=invite_code,
                service_id_to_tag_ids=service_id_to_tag_ids,
                platform_coupon_deduction=platform_coupon_deduction,
                platform_coupon_and_coupon_info_dict=platform_coupon_and_coupon_info_dict,
                doctor_coupon_deduction=doctor_coupon_deduction,
                doctor_coupon_and_coupon_info_dict=doctor_coupon_and_coupon_info_dict,
                groupbuy_team_id=groupbuy_team_id,
            )

            to_create_order_info_list.append(to_create_order_info)

    # 确保数据确实能dump成json之后再还原，同时必须确保重新dump出来的数据不依赖除了dict之外的数据

    to_create_order_info_list_json = json.dumps(to_create_order_info_list)
    restore_to_create_order_info_list = json.loads(to_create_order_info_list_json)

    chunk_size = 5
    orders = []
    insurances = []

    for to_create_order_info_chunk in [restore_to_create_order_info_list[i:i + chunk_size] for i
                                       in range(0, len(restore_to_create_order_info_list), chunk_size)]:
        inserted_orders = order_manager.bulk_create_order_and_service_snapshot(to_create_order_info_chunk)
        for o in inserted_orders:
            orders.append(o)

    for order in orders:
        if order.points_deduction > 0:
            point.remove(
                user_id=user.id,
                reason=POINTS_TYPE.BUY_SERVICE,
                point=point.deduction_to_points(order.points_deduction),
                order_id=order.id
            )

        service_item_id = order.service_item_id
        if service_item_id in service_item_id_to_insurance_id:
            insurances.append({
                'order_id': order.id,
                'phone': order.phone,
                'hospital_name': order.service.doctor.hospital.name,
                'service_name': order.service.name,
                'insurance_service_id': order.service.insurance,
            })

    siids = [k for k, v in service_item_id_to_ordering_info.items()]

    sku_stock_data = ServiceItem.objects.filter(id__in=siids).values_list('id', 'service_id', 'sku_stock')

    service_id_to_sku_stocks = {}
    for id, service_id, sku_stock in sku_stock_data:
        if service_id not in service_id_to_sku_stocks:
            service_id_to_sku_stocks[service_id] = []
        service_id_to_sku_stocks[service_id].append(sku_stock)

    service_id_to_service_obj = {}
    for k, v in service_item_id_to_ordering_info.items():
        service_id = v.service_obj.id
        if service_id not in service_id_to_service_obj:
            service_id_to_service_obj[service_id] = v.service_obj

    now = datetime.datetime.now()
    for service_id, sku_stocks in service_id_to_sku_stocks.items():
        if service_id in service_id_to_service_obj:
            service_obj = service_id_to_service_obj[service_id]
            order_manager.stock_monitor_v3(service_obj, sku_stocks, now)

    return True, {}, orders, insurances, related_coupon_info_ids


def _get_coupon_related_data(user, service_item_id_to_ordering_info, use_point,
                             coupon_info, services_shared_dict, service_item_id_to_points_deduction,
                             platform_coupon_id, doctor_coupon_id):
    # 只有新的券在实际用上了（freeze）才能设置 related_coupon_info_ids ！！！旧的基于couponinfo_id的不要设置这个数组
    def _get_coupon_and_coupon_info_dict(coupon_info):
        coupon_and_coupon_info_dict = {}
        if coupon_info:
            coupon_info_data = coupon_info.coupon_info_data()

            coupon_and_coupon_info_dict['coupon_id'] = coupon_info.coupon.id
            coupon_and_coupon_info_dict['coupon_info_id'] = coupon_info_data['id']
            coupon_and_coupon_info_dict['gengmei_percent'] = coupon_info.coupon.gengmei_percent
            coupon_and_coupon_info_dict['name'] = coupon_info_data['name']
            coupon_and_coupon_info_dict['coupon_type'] = coupon_info_data['coupon_type']
            coupon_and_coupon_info_dict['value'] = coupon_info_data['value']
        return coupon_and_coupon_info_dict

    if platform_coupon_id is not None or doctor_coupon_id is not None:
        # 在传入新参数的时候，会将无论是不是错传的旧参数coupon_and_coupon_info_dict, services_shared_dict 清空
        services_platform_shared_dict = {}
        platform_coupon_and_coupon_info_dict = {}

        services_doctor_shared_dict = {}
        doctor_coupon_and_coupon_info_dict = {}

        _shared_dict = {}

        _service_item_id_to_points_deduction = {}
        related_coupon_info_ids = []

        couponinfo_ids = []
        if platform_coupon_id is not None:
            couponinfo_ids.append(platform_coupon_id)
        if doctor_coupon_id is not None:
            couponinfo_ids.append(doctor_coupon_id)

        couponinfos = own_tool.check_coupon_user_then_select_for_update_all_couponinfo(user.id, couponinfo_ids)

        for coupon_info in couponinfos:
            if coupon_info.id == platform_coupon_id:
                if coupon_info.coupon.coupon_type != COUPON_TYPES.PLATFORM:
                    return gen(CODES.PARAMS_INVALID)

                services_platform_shared_dict = CouponInfo.get_shared(service_item_id_to_ordering_info, coupon_info)
                platform_coupon_and_coupon_info_dict = _get_coupon_and_coupon_info_dict(coupon_info)
                if services_platform_shared_dict:
                    coupon_info.freeze_coupon()
                    related_coupon_info_ids.append(coupon_info.id)

                    _shared_dict = services_platform_shared_dict

            if coupon_info.id == doctor_coupon_id:
                if coupon_info.coupon.coupon_type != COUPON_TYPES.DOCTOR:
                    return gen(CODES.PARAMS_INVALID)

                services_doctor_shared_dict = CouponInfo.get_shared(service_item_id_to_ordering_info, coupon_info)
                doctor_coupon_and_coupon_info_dict = _get_coupon_and_coupon_info_dict(coupon_info)
                if services_doctor_shared_dict:
                    coupon_info.freeze_coupon()
                    related_coupon_info_ids.append(coupon_info.id)

        if use_point:
            _service_item_id_to_points_deduction = settlement_manager.compute_points_deduction_per_service_item(
                service_item_id_to_ordering_info=service_item_id_to_ordering_info,
                user_total_points_yuan=user.person.points_deduction_yuan,
                service_item_coupon_shared_list_dict=_shared_dict
            )

        return {}, {}, _service_item_id_to_points_deduction, related_coupon_info_ids, \
               platform_coupon_and_coupon_info_dict, services_platform_shared_dict, \
               doctor_coupon_and_coupon_info_dict, services_doctor_shared_dict
    else:
        _coupon_and_coupon_info_dict = _get_coupon_and_coupon_info_dict(coupon_info)

        return _coupon_and_coupon_info_dict, services_shared_dict, service_item_id_to_points_deduction, [], \
               {}, {}, {}, {}


def _get_service_id_to_online_3level_tag_ids(all_service_ids):
    # TODO: 如果有时间的话，这里可以考虑自己做遍历...这样子的就可以批量化service和tag关系的查询
    result = {}
    service_ids = set(all_service_ids)

    for service_id in service_ids:
        tag_data = list(ServiceTag.objects.filter(service_id=service_id).values_list('service_id', 'tag_id'))
        all_tags_id = set([tag_id for service_id, tag_id in tag_data])
        tags_types = [TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI]
        all_tags = list(Tag.objects.filter(id__in=all_tags_id, tag_type__in=tags_types, is_online=True))
        closure_tags = TagControl.get_ancestors(
            initial_set=all_tags,
            exclude_init=False,
            is_online_only=True)

        tag_ids = [t.id for t in closure_tags if t.tag_type in tags_types]
        result[service_id] = tag_ids

    return result


def purchase_notification(settlement, user):
    # send notification
    notification_content_format = (
        '您的【{}】美购下单成功！请在30分钟之内付款，逾期订单将自动取消！'
        '如有疑问，请拨打更美客服：{}转6666'
    )

    # service.doctor.hospital.city.name
    service_ids = SettlementItem.objects.filter(
        settlement_id=settlement.id
    ).select_related('order__service_id').values_list('order__service_id', flat=True).distinct()

    doctor_ids = list(Service.objects.filter(id__in=service_ids).values_list('doctor', flat=True).distinct())

    doctor_id_to_data = {d[0]: (d[1], d[2]) for d in Doctor.objects.filter(
        id__in=doctor_ids
    ).select_related('hospital__city').values_list('id', 'name', 'hospital__city__name')}

    all_notification_contents = []

    for doctor_id, d in doctor_id_to_data.items():
        name, city_name = d
        if name:
            s = name
            content = notification_content_format.format(s, PhoneService.get_phone_prefix('6666'), )
            all_notification_contents.append(content)

    pushurl = (
        settlement.service_payment_type == SERVICE_PAYMENT_TYPE.EXCHANGE_GIFT and
        PushUrlProtocol.SETTLEMENT_DETAIL_GIFT_EXCHANGE or
        PushUrlProtocol.SETTLEMENT_DETAIL
    )

    for notification_content in all_notification_contents:
        send_notification(
            user.id,
            '下单成功',
            notification_content,
            pushurl.format(id=settlement.id)
        )


def _create_insurance(insurances):
    try:
        if insurances:
            result = get_rpc_remote_invoker()['plutus/insurance/batch_create'](insurances=insurances).unwrap()
            premium = result['premium']
        else:
            premium = 0
    except RPCFaultException:
        raise
    except:
        logging_exception()
        premium = 0
    return {
        'premium': premium
    }


@bind_context('api/settlement/create/id', login_required=True)
def settlement_create_id(ctx, current_city_id=None, channel=None, cart_item_ids=[], service_id=None,
                         item_key=None, coupon_id=None, use_point=False, source=ORDER_SOURCE.UNKNOW,
                         use_insurance=False, device_id=None, idfa=None, idfv=None, is_doctor_see=False,
                         insurance_type=None, insurance=None):
    if item_key:
        item_key = get_serviceitem_by_option_id(item_key, service_id).key
        if not item_key:
            return gen(CODES.SERVICE_NOT_EXSIT)

    return ctx.gaia_local['api/settlement/create'](
        current_city_id=current_city_id,
        channel=channel,
        cart_item_ids=cart_item_ids,
        service_id=service_id,
        item_key=item_key,
        coupon_id=coupon_id,
        use_point=use_point,
        source=source,
        use_insurance=use_insurance,
        device_id=device_id,
        idfa=idfa,
        idfv=idfv,
        is_doctor_see=is_doctor_see,
        insurance_type=insurance_type,
        insurance=insurance,
    ).unwrap()


@bind_context('api/settlement/create/id/v2', login_required=True)
def settlement_create_id_v2(ctx, current_city_id=None, channel=None, cart_item_ids=[], service_id=None,
                            item_key=None, coupon_id=None, use_point=False, source=ORDER_SOURCE.UNKNOW,
                            use_insurance=False, device_id=None, idfa=None, idfv=None, is_doctor_see=False,
                            insurance_type=None, insurance=None, cart_item_info={}, service_item_id=None, number=1, ):
    if item_key:
        item_key = get_serviceitem_by_option_id(item_key, service_id).key
        if not item_key:
            return gen(CODES.SERVICE_NOT_EXSIT)

    return ctx.gaia_local['api/settlement/create/v2'](
        current_city_id=current_city_id,
        channel=channel,
        cart_item_ids=cart_item_ids,
        service_id=service_id,
        item_key=item_key,
        coupon_id=coupon_id,
        use_point=use_point,
        source=source,
        use_insurance=use_insurance,
        device_id=device_id,
        idfa=idfa,
        idfv=idfv,
        is_doctor_see=is_doctor_see,
        insurance_type=insurance_type,
        insurance=insurance,
        cart_item_info=cart_item_info,
        service_item_id=service_item_id,
        number=number,
    ).unwrap()


@bind_context('api/settlement/create', login_required=True)
def settlement_create(ctx, current_city_id=None, channel=None, cart_item_ids=[], service_id=None,
                      item_key=None, coupon_id=None, use_point=False, source=ORDER_SOURCE.UNKNOW,
                      use_insurance=False, device_id=None, idfa=None, idfv=None, is_doctor_see=False,
                      insurance_type=None, insurance=None):

    # 加入代码阻止通过旧版API下单，之后会将这里涉及的所有无用代码去除
    return gen(CODES.SETTLEMENT_CREATE_FAIL)

    couponinfo_id = coupon_id
    user = get_user_from_context(ctx)

    services = settlement_manager.get_services_from_cart_or_service_id_itemkey(
        person=user.person,
        cart_item_ids=cart_item_ids,
        service_id=service_id,
        item_key=item_key
    )
    settlement_manager.check_gift(services=services, use_point=use_point)
    coupon_info = own_tool.is_my_coupon(user, couponinfo_id)
    if insurance:
        insurance_info = {
            (t['service_id'], t['item_key']): t['insurance_id']
            for t in insurance
        }
    else:
        insurance_info = {}

    services_shared_dict = _compute_coupon_shared_per_service(services, coupon_info)

    # 先把积分算了
    if use_point:
        service_points_deduction = _compute_points_deduction_per_service(
            services=services,
            user_total_points_yuan=user.person.points_deduction_yuan,
            services_shared_dict=services_shared_dict
        )
    else:
        service_points_deduction = {}

    # 1. 没用券
    # 2. 用了满减券
    try:
        with transaction.atomic():
            '''
            创建订单
            冻结美券
            创建结算单
            结算单订单关联
            '''
            orders, insurances = create_orders(user, source, services, service_points_deduction,
                                               services_shared_dict, coupon_info, is_doctor_see, insurance_info)
            for o in orders:
                FenxiaoOrderWanghong.create(o)

            if coupon_info:
                coupon_info.freeze_coupon()
            settlement_name = ''.join([v[0].name for v in services.values()])
            settlement = _create_settlement(
                person=user.person,
                coupon_info=coupon_info,
                orders=orders,
                settlement_name=settlement_name,
                use_insurance=use_insurance,
                insurance_type=insurance_type,
                insurances=insurances,
            )
            if source in (ORDER_SOURCE.IOS, ORDER_SOURCE.ANDROID) and current_city_id:
                # current_city_id 这次上线为了兼容
                # 下个迭代去掉
                SettlementExtra.objects.create(settlement, current_city_id, channel, device_id, idfa, idfv)
            _bind_order_to_settlement(settlement, orders)

    except (ServiceItem.DoesNotExist, Service.DoesNotExist):
        return gen(CODES.SERVICE_NOT_FOUND)

    except GaiaRPCFaultException:
        raise
    except RPCFaultException as e:
        raise GaiaRPCFaultException(error=e.error, message=e.message, data={})
    except:
        logging_exception()
        return gen(CODES.SETTLEMENT_CREATE_FAIL)

    purchase_notification(settlement, user)
    # create async task, if dont pay in 30 mins, cancel this settlement
    settlement_task.settlement_create_event.delay(settlement.id)

    ctx.logger.app(settlement_create={
        'settlement_id': settlement.id,
    })

    return {'id': settlement.id, 'status': settlement.status}


@bind_context('api/settlement/create/v2', login_required=True)
def settlement_create_v2(ctx, current_city_id=None, channel=None, cart_item_ids=[], service_id=None,
                         item_key=None, coupon_id=None, use_point=False, source=ORDER_SOURCE.UNKNOW,
                         use_insurance=False, device_id=None, idfa=None, idfv=None, is_doctor_see=False,
                         insurance_type=None, insurance=None, cart_item_info={}, service_item_id=None, number=1, ):
    # 加入代码阻止通过旧版API下单，之后会将这里涉及的所有无用代码去除
    return gen(CODES.SETTLEMENT_CREATE_FAIL)

    couponinfo_id = coupon_id
    user = get_user_from_context(ctx)

    # 将旧版本按照数字传入的购物车id，全部改成字符串，保证购物车id和新版一样
    cart_item_ids = [str(i) for i in cart_item_ids] if cart_item_ids else []

    service_item_id_to_ordering_info = settlement_manager.get_service_item_id_to_ordering_info(cart_item_info,
                                                                                               service_item_id,
                                                                                               number, user)

    any_number_neq_1 = any([True for oi in service_item_id_to_ordering_info.values() if oi.number != 1])
    if any_number_neq_1:
        gen(CODES.PARAMS_INVALID)

    # 获取当前价格信息，本次之后所有的ServiceItem应该都是这一次的实例（因为价格信息经过一次读取之后会保存在实例中）
    for k, v in service_item_id_to_ordering_info.iteritems():
        service_obj = v.service_obj
        service_item_obj = None

        service_can_sell = service_obj.is_can_be_sold(check_any_sku_has_stock=False)
        if service_can_sell is not True:
            gen(CODES.SERVICE_NOT_EXSIT)

        price_info = service_item_obj.get_current_price_info()
        if price_info is None:
            gen(CODES.SERVICE_NOT_EXSIT)

    settlement_manager.check_gift(services=service_item_id_to_ordering_info, use_point=use_point)

    service_item_id_to_insurance_id = {}
    if insurance:
        for i in insurance:
            iid = i['insurance_id']
            siid = i.get('service_item_id', 0)
            if not siid:
                sid = i['service_id']
                key = i.get('item_key', None)

                items = ServiceItem.objects.filter(service_id=sid, is_delete=False)
                if key:
                    items = items.filter(key=key)

                if len(items) != 1:
                    return gen(CODES.PARAMS_INVALID)

                siid = items[0].id
            service_item_id_to_insurance_id[siid] = iid

    # 1. 没用券
    # 2. 用了满减券
    try:
        with transaction.atomic():
            '''
            创建订单
            冻结美券
            创建结算单
            结算单订单关联
            '''
            # 将关于券和积分的处理移入事务中，通过select_for_update避免券被多次使用
            coupon_info = own_tool.is_my_coupon(user, couponinfo_id, select_for_update=True)

            services_shared_dict = CouponInfo.get_shared(service_item_id_to_ordering_info, coupon_info)

            # 先把积分算了
            if use_point:
                _shared_dict = services_shared_dict
                if coupon_info and coupon_info.coupon.coupon_type != COUPON_TYPES.PLATFORM:
                    # 医生券不参与积分计算
                    _shared_dict = {}
                service_item_id_to_points_deduction = settlement_manager.compute_points_deduction_per_service_item(
                    service_item_id_to_ordering_info=service_item_id_to_ordering_info,
                    user_total_points_yuan=user.person.points_deduction_yuan,
                    service_item_coupon_shared_list_dict=_shared_dict
                )
            else:
                service_item_id_to_points_deduction = {}

            settlement = settlement_manager.create_settlement(person=user.person)

            orders, insurances = create_orders_v2(user, source, service_item_id_to_ordering_info,
                                                  service_item_id_to_points_deduction,
                                                  services_shared_dict, coupon_info, is_doctor_see,
                                                  service_item_id_to_insurance_id,
                                                  settlement.id)

            for o in orders:
                FenxiaoOrderWanghong.create(o)

            if coupon_info:
                coupon_info.freeze_coupon()

            settlement_name = ''.join([v.service_obj.name for v in service_item_id_to_ordering_info.values()])
            _update_settlement(
                settlement=settlement,
                coupon_info=coupon_info,
                orders=orders,
                settlement_name=settlement_name,
                insurances=insurances,
            )
            if source in (ORDER_SOURCE.IOS, ORDER_SOURCE.ANDROID) and current_city_id:
                # current_city_id 这次上线为了兼容
                # 下个迭代去掉
                SettlementExtra.objects.create(settlement, current_city_id, channel, device_id, idfa, idfv)
            _bind_order_to_settlement(settlement, orders)
    except (ServiceItem.DoesNotExist, Service.DoesNotExist):
        return gen(CODES.SERVICE_NOT_FOUND)

    except GaiaRPCFaultException:
        raise
    except RPCFaultException as e:
        raise GaiaRPCFaultException(error=e.error, message=e.message, data={})
    except:
        logging_exception()
        return gen(CODES.SETTLEMENT_CREATE_FAIL)

    purchase_notification(settlement, user)
    # create async task, if dont pay in 30 mins, cancel this settlement
    settlement_task.settlement_create_event.delay(settlement.id)

    ctx.logger.app(settlement_create={
        'settlement_id': settlement.id,
    })

    return {'id': settlement.id, 'status': settlement.status}


@bind_context('api/settlement/create/v3', login_required=True)
def settlement_create_v3(ctx, current_city_id=None, channel=None,
                         coupon_id=None, use_point=False, source=ORDER_SOURCE.UNKNOW, invite_code=None,
                         device_id=None, idfa=None, idfv=None, is_doctor_see=False,
                         insurance=None, cart_item_info=None, service_item_id=None, number=1,
                         platform_coupon_id=None, doctor_coupon_id=None,
                         groupbuy_code=None, create_groupbuy_price_id=None, auto_create_diary=True,form_id="",
                         ip=None):
    couponinfo_id = coupon_id
    user = get_user_from_context(ctx)
    is_merchant = is_merchant_user(user)
    if is_merchant and (platform_coupon_id or use_point):
        return gen(CODES.CAN_NOT_USE_DISCOUNT)
    tongdun_data = {}

    service_item_id_to_insurance_id = {}
    if insurance:
        for i in insurance:
            iid = i['insurance_id']
            siid = i.get('service_item_id', 0)
            if not siid:
                return gen(CODES.PARAMS_INVALID)

            service_item_id_to_insurance_id[siid] = iid

    groupbuy_team_id, groupbuy_price_id = settlement_manager.try_get_groupbuy_team_id_and_groupbuy_price_id(
        user.id, groupbuy_code, create_groupbuy_price_id
    )

    success, service_item_id_to_ordering_info, service_item_id_to_cart_item_id, \
        service_item_id_to_error_text, cart_item_id_to_error_text = \
            settlement_manager.try_get_service_item_id_to_ordering_info(
                cart_item_info,
                service_item_id,
                number, user, groupbuy_price_id
            )

    if not success:
        errors = {}
        for siid, error_text in service_item_id_to_error_text.items():
            if siid in service_item_id_to_cart_item_id:
                errors[service_item_id_to_cart_item_id[siid]] = error_text
        for cid, error_text in cart_item_id_to_error_text.items():
            errors[cid] = error_text

        return {'id': None, 'status': None, 'errors': errors, 'tongdun_data': tongdun_data}

    # 同一个SKU只能买一份保险
    if service_item_id_to_insurance_id:
        for siid, iid in service_item_id_to_insurance_id.items():
            if siid in service_item_id_to_ordering_info:
                number = service_item_id_to_ordering_info[siid].number
                if number > 1:
                    return gen(CODES.PARAMS_INVALID)

    settlement_manager.check_gift(services=service_item_id_to_ordering_info, use_point=use_point)
    info_logger.info("接收到的form_id:{0}".format(form_id))
    info_logger.info("触发代码部署---")
    # 1. 没用券
    # 2. 用了满减券
    try:
        with transaction.atomic():
            '''
            创建订单
            冻结美券
            创建结算单
            结算单订单关联
            '''

            groupbuy_team = None
            if groupbuy_team_id:
                groupbuy_team, error_code = groupbuy_manager.try_join_groupbuy_team(
                    user.id, groupbuy_code, lock_groupbuy_team=True
                )

                if groupbuy_team:
                    assert groupbuy_team.id == groupbuy_team_id
                    assert groupbuy_team.price_id == groupbuy_price_id

                    #保存form_id
                    #groupbuy_team.form_id=form_id
                    #groupbuy_team.save()

                    # 是否需要在这里再次确保?
                    # groupbuy_team.min_user_number == skupricerule.groupbuy_nums
                    # groupbuy_team.gb_type == skupricerule.groupbuy_type
                else:
                    return gen(error_code)

            coupon_info = None
            services_shared_dict = {}
            service_item_id_to_points_deduction = {}

            #  只有完全没有新的券参数的时候才会去检查旧的单券参数
            if platform_coupon_id is not None or doctor_coupon_id is not None:
                if platform_coupon_id is not None and not (
                            isinstance(platform_coupon_id, int) and platform_coupon_id > 0):
                    return gen(CODES.PARAMS_INVALID)
                if doctor_coupon_id is not None and not (
                            isinstance(doctor_coupon_id, int) and doctor_coupon_id > 0):
                    return gen(CODES.PARAMS_INVALID)
                if platform_coupon_id is not None and doctor_coupon_id is not None and platform_coupon_id == doctor_coupon_id:
                    return gen(CODES.PARAMS_INVALID)

            elif couponinfo_id or use_point is True:
                coupon_info, service_item_id_to_points_deduction, services_shared_dict = _get_coupon_and_point_data(
                    couponinfo_id, user, service_item_id_to_ordering_info, use_point)

            # 只有新的券在实际用上了（freeze）才能设置 related_coupon_info_ids ！！！旧的基于couponinfo_id的不要设置这个数组
            success, service_item_id_to_error_text, orders, insurances, related_coupon_info_ids = create_orders_v3(
                user, source,
                service_item_id_to_ordering_info,
                service_item_id_to_points_deduction,
                services_shared_dict, coupon_info,
                is_doctor_see, service_item_id_to_insurance_id, invite_code, use_point,
                platform_coupon_id, doctor_coupon_id,
                groupbuy_team_id=groupbuy_team_id,
            )
            #记录用户的设备标识,ip
            order_task.record_user_information.delay(orders, '', device_id, ip, ORDER_STATUS.NOT_PAID)

            if not success:
                gen(CODES.SETTLEMENT_CREATE_FAIL)

            settlement = settlement_manager.create_settlement(person=user.person)

            max_settlement_name_length = 50
            settlement_name = u''.join([v.service_obj.name for v in service_item_id_to_ordering_info.values()])
            settlement_name = settlement_name[:max_settlement_name_length] if len(
                settlement_name) > max_settlement_name_length else settlement_name

            _update_settlement(
                settlement=settlement,
                coupon_info=coupon_info,
                orders=orders,
                settlement_name=settlement_name,
                insurances=insurances,
                related_coupon_info_ids=related_coupon_info_ids,
                device_id=device_id,
                auto_create_diary=auto_create_diary,
                form_id=form_id
            )
            if source in (ORDER_SOURCE.IOS, ORDER_SOURCE.ANDROID) and current_city_id:
                # current_city_id 这次上线为了兼容
                # 下个迭代去掉
                SettlementExtra.objects.create(settlement, current_city_id, channel, device_id, idfa, idfv)
            _bind_order_to_settlement(settlement, orders)

            if groupbuy_team:
                order_ids = [o.id for o in orders]
                assert len(orders) == 1
                order_id = order_ids[0]
                groupbuy_manager.join_groupbuy(user.id, groupbuy_team, order_id)
            # 给医生发私信
            send_order_message_to_doctor(settlement)
    except GaiaRPCFaultException as ex:
        if ex.error == CODES.SETTLEMENT_CREATE_FAIL:

            errors = {}
            for siid, error_text in service_item_id_to_error_text.items():
                if siid in service_item_id_to_cart_item_id:
                    errors[service_item_id_to_cart_item_id[siid]] = error_text
            return {'id': None, 'status': None, 'errors': errors, 'tongdun_data': tongdun_data}

        else:
            raise
    except RPCFaultException as e:
        raise GaiaRPCFaultException(error=e.error, message=e.message, data={})
    except:
        logging_exception()
        return gen(CODES.SETTLEMENT_CREATE_FAIL)

    purchase_notification(settlement, user)
    # create async task, if dont pay in 30 mins, cancel this settlement
    settlement_task.settlement_create_event.delay(settlement.id)

    ctx.logger.app(settlement_create={
        'settlement_id': settlement.id,
    })


    if groupbuy_team_id:
        from api.models import GroupBuyTeam, User, Person

        gbt = GroupBuyTeam.objects.get(id=groupbuy_team_id)
        gbt.form_id=form_id
        gbt.save()

        u = User.objects.get(id=gbt.creator_user_id)
        p = Person.get_or_create(u)
        person_id = p.id.hex

        sku_id = str(gbt.service_item_id)

        tongdun_data = {
            "ext_groupid": person_id,  # 发起拼团的用户的PersonId
            "ext_item_id": sku_id,  # 拼团的SkuId
            "ext_item_type": "2",  # 下单的类型，目前预定 1 代表普通下单 2 代表参团下单 （ 开团暂时先不需要 ）
            "ext_team_id": groupbuy_code  # 拼团的对外用的Code，不是表的数字id
        }

    return {'id': settlement.id, 'status': settlement.status, 'errors': {}, 'tongdun_data': tongdun_data}


def _get_coupon_and_point_data(couponinfo_id, user, service_item_id_to_ordering_info, use_point):
    coupon_info = None
    services_shared_dict = {}

    if couponinfo_id:
        # 将关于券和积分的处理移入事务中，通过select_for_update避免券被多次使用
        coupon_info = own_tool.is_my_coupon(user, couponinfo_id, select_for_update=True)

        services_shared_dict = CouponInfo.get_shared(service_item_id_to_ordering_info, coupon_info)
        # 一定要算完可用情况，并且有shared的数据才去freeze_coupon
        if services_shared_dict:
            coupon_info.freeze_coupon()
        else:
            #  不能用，或者券状态不对
            coupon_info = None

    service_item_id_to_points_deduction = {}
    # 先把积分算了
    if use_point:
        _shared_dict = services_shared_dict
        if coupon_info and coupon_info.coupon.coupon_type != COUPON_TYPES.PLATFORM:
            # 医生券不参与积分计算
            _shared_dict = {}
        service_item_id_to_points_deduction = settlement_manager.compute_points_deduction_per_service_item(
            service_item_id_to_ordering_info=service_item_id_to_ordering_info,
            user_total_points_yuan=user.person.points_deduction_yuan,
            service_item_coupon_shared_list_dict=_shared_dict
        )
    return coupon_info, service_item_id_to_points_deduction, services_shared_dict


@bind_context('api/settlement/wechat_notify')
def settlement_wechatcallback(ctx, trade_data):
    """settlement wechat pay callback.

    args:trade_data
    {
        "openid": "oIqEAs8LgUxje8RjhgNgrmM9pw60",
        "trade_type": "APP",
        "is_subscribe": "N",
        "nonce_str": "782cfmadt8imufepbwhoui44s3nzx7mr",
        "return_code": "SUCCESS",
        "sign": "065194B60B65488B8B016FA2CEEB8002",
        "bank_type": "CFT",
        "attach": null,
        "mch_id": "1227203101",
        "out_trade_no": "108887300108",
        "time_end": "20180819154524",
        "result_code": "SUCCESS",
        "total_fee": "3000",
        "appid": "wx4326da3ad2429149",
        "fee_type": "CNY",
        "cash_fee": "3000",
        "transaction_id": "4200000169201808196126023946"
    }
    """
    wechat_pay_logger.info(json.dumps(trade_data))

    if trade_data['return_code'] != "SUCCESS":
        return gen(CODES.SUCCESS)

    out_trade_no = trade_data['out_trade_no']
    sign_pay_finish_task.delay(trade_data['out_trade_no'])

    with transaction.atomic():
        settlement = Settlement.objects.select_for_update().get(id=out_trade_no)
        total_fee = float(trade_data['total_fee']) / 100
        settlement_payment = settlement.real_payment
        purchase_tool.is_callback_payment_real(settlement_payment, total_fee)

        # check we has accroding wechat order for each order in settlement
        items = settlement.items.all()
        order_id = items[0].order.id
        if PaymentOrder.objects.filter(orders=order_id).exists():
            return gen(CODES.SUCCESS)

        can_cancled_settlement_2_paid(settlement)
        WechatSettlement.objects.create_record(settlement, trade_data)

        pay_start_time = settlement.created_at
        pay_time = datetime.datetime.strptime(trade_data["time_end"], '%Y%m%d%H%M%S')

        order_ids = set(SettlementItem.objects.filter(
            settlement_id=settlement.id
        ).values_list('order_id', flat=True))

        PaymentOrder.objects.save_from_wx(
            order_ids=order_ids, trade_data=trade_data, pay_time=pay_time, pay_start_time=pay_start_time,
        )

        # 微信之前还有个保存WechatOrder的行为，先转移过来，之后再看怎么处理
        from pay.models import WechatOrder

        base_data = {
            "appid": trade_data["appid"],
            "mch_id": trade_data["mch_id"],
            "nonce_str": trade_data["nonce_str"],
            "sign": trade_data["sign"],
            "result_code": trade_data["result_code"],
            "bank_type": trade_data["bank_type"],
            "cash_fee": trade_data["cash_fee"],
            "fee_type": trade_data["fee_type"],
            "is_subscribe": trade_data["is_subscribe"],
            "openid": trade_data["openid"],
            "out_trade_no": trade_data["out_trade_no"],
            "time_end": trade_data["time_end"],
            "total_fee": float(trade_data["total_fee"]),  # 以后要改成元!!!
            "trade_type": trade_data["trade_type"],
            "transaction_id": trade_data["transaction_id"],
        }

        to_inserrt_wos = []
        for order_id in order_ids:
            wos = WechatOrder(**base_data)
            wos.order_no = order_id
            to_inserrt_wos.append(wos)

        WechatOrder.objects.bulk_create(to_inserrt_wos)
        wechat_pay_logger.info("start...settlement_id: {settlement_id}".format(settlement_id=settlement.id))
        settlement_paid_with_log(
            ctx=ctx,
            settlement=settlement,
            payment_channel=PAYMENT_CHANNEL.WECHAT,
            pay_time=pay_time,
        )
        wechat_pay_logger.info("change order status success")
        wechat_pay_logger.info("start to execute after_settlement_paid()")
        after_settlement_paid(settlement)
        wechat_pay_logger.info("after_settlement_paid() execute success")

    # 这个要在事务成功提交之后才能触发
    wechat_pay_logger.info("start to execute after_settlement_paid_commit()")
    after_settlement_paid_commit(settlement)
    wechat_pay_logger.info("after_settlement_paid_commit() execute success")

    return gen(CODES.SUCCESS)


@bind_context('api/settlement/alipay_callback')
def settlement_alipay_callback(ctx, trade_data):
    """settlement callback after pay(alipay).
    客户端,backedn 支付宝异步通知
    args:
        trade_data:
        {
            "seller_email": "zhengxuan@wanmeizhensuo.com",
            "sign": "OWo8tmaXSkvbhhBhXhO/2ZZZMVsUGuAxEymYZ8jqcwSKjda8DY/LCiqdqyNSSM3TiQfcfavqb9ulADH16exZ92n8ef39FDQwAGQfaY/5KqiToczz60/hjEPvIXPB24dQtJx5ILjDcrN8bck5EfLw7YHz+u5+m35mvWiAglEWL80=",
            "subject": "【北京@北京丽雅医疗美容诊所】玻尿酸祛黑眼圈",
            "is_total_fee_adjust": "N",
            "gmt_create": "2018-08-19 17:59:25",
            "out_trade_no": "100996025656",
            "sign_type": "RSA",
            "body": "【北京@北京丽雅医疗美容诊所】玻尿酸祛黑眼圈",
            "price": "262.00",
            "buyer_email": "qia***@yeah.net",
            "discount": "0.00",
            "trade_status": "TRADE_SUCCESS",
            "gmt_payment": "2018-08-19 17:59:25",
            "trade_no": "2018081921001004130515560145",
            "seller_id": "2088211612339882",
            "use_coupon": "N",
            "payment_type": "1",
            "total_fee": "262.00",
            "notify_time": "2018-08-19 17:59:26",
            "quantity": "1",
            "notify_id": "2018081900222175925054130522537882",
            "notify_type": "trade_status_sync",
            "buyer_id": "2088002665233135"
        }
    """
    alipay_pay_logger.info(json.dumps(trade_data))

    if not _AlipayAppPayTools:
        _init_AlipayAppPayTools()

    _oldAlipayAppPayTools = _AlipayAppPayTools["old"]

    valid = _oldAlipayAppPayTools.verify_alipay_notify_sign(trade_data)

    if not valid:
        alipay_pay_logger.error("verify_alipay_callback_sign_fail. trade_data:" + json.dumps(trade_data))
        raise gen(CODES.VERIFY_FAILED)

    param_notify_id = trade_data.get("notify_id", None)
    param_seller_id = trade_data.get("seller_id", None)

    trade_status = trade_data.get("trade_status", None)
    refund_status = trade_data.get("refund_status", None)

    if 'REFUND_SUCCESS' == refund_status:
        alipay_pay_logger.info("alipay_callback_success. trade_data_notify_id:" + param_notify_id)
        return gen(CODES.SUCCESS)

    seller_id_valid = _oldAlipayAppPayTools.verify_seller_id(param_seller_id)
    if not seller_id_valid:
        alipay_pay_logger.error("verify_alipay_callback_seller_id_fail. trade_data:" + json.dumps(trade_data))
        raise gen(CODES.VERIFY_FAILED)

    after_settlement_paid_success = False

    with transaction.atomic():
        try:
            settlement = Settlement.objects.select_for_update().get(pk=trade_data['out_trade_no'])
        except Settlement.DoesNotExist:
            alipay_pay_logger.error(
                "alipay_callback_fail. CODES.SETTLEMENT_DOES_NOT_EXIST. trade_data:" + json.dumps(trade_data))
            return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

        if trade_status != 'TRADE_SUCCESS':
            # 其余逻辑(退款等)发给了支付接口不处理, 直接返回正常
            alipay_pay_logger.info("alipay_callback_success. trade_data_notify_id:" + param_notify_id)
            return gen(CODES.SUCCESS)

        if (trade_status == 'TRADE_SUCCESS' and
                    settlement.status in (SETTLEMENT_STATUS.PAYING, SETTLEMENT_STATUS.NOT_PAID, SETTLEMENT_STATUS.CANCEL)):
            # 支付宝付款成功, 更新订单状态, 并存储支付信息
            sign_pay_finish_task.delay(trade_data['out_trade_no'])

            alipay_pay_logger.info("alipay_callback_start_update_status. trade_data_notify_id:" + param_notify_id)

            settlement_payment = settlement.real_payment
            purchase_tool.is_callback_payment_real(settlement_payment, trade_data['total_fee'])

            if not purchase_tool.check_from_alipay(trade_data['notify_id']):
                alipay_pay_logger.error("verify_alipay_callback_notify_id_fail. trade_data:" + json.dumps(trade_data))
                raise gen(CODES.VERIFY_FAILED)

            alipay_pay_logger.info("alipay_callback_begin_atomic. trade_data_notify_id:" + param_notify_id)

            can_cancled_settlement_2_paid(settlement)

            alipay_pay_logger.info("alipay_callback_begin_create_record. trade_data_notify_id:" + param_notify_id)

            origin_data_string = json.dumps(trade_data)
            AlipaySettlement.objects.create_record(settlement, trade_data, origin_data_string)

            gmt_create = datetime.datetime.strptime(trade_data["gmt_create"], "%Y-%m-%d %H:%M:%S")
            gmt_payment = datetime.datetime.strptime(trade_data["gmt_payment"], "%Y-%m-%d %H:%M:%S")

            pay_start_time = gmt_create
            pay_time = gmt_payment

            order_ids = set(SettlementItem.objects.filter(
                settlement_id=settlement.id
            ).values_list('order_id', flat=True))

            PaymentOrder.objects.save_from_alipay(
                order_ids=order_ids, trade_data=trade_data, notify_data_str=origin_data_string,
                pay_start_time=pay_start_time, pay_time=pay_time,
            )

            alipay_pay_logger.info(
                "alipay_callback_begin_settlement_paid_with_log. trade_data_notify_id:" + param_notify_id)
            settlement_paid_with_log(
                ctx=ctx,
                settlement=settlement,
                payment_channel=PAYMENT_CHANNEL.ALIPAY,
                pay_time=pay_time,
            )

            # 数据埋点, http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=23892080
            try:
                # 避免正常下单出现问题
                ctx.logger.app(**paid_success(order_ids=order_ids))
            except Exception:
                pass

            alipay_pay_logger.info(
                "alipay_callback_begin_after_settlement_paid. trade_data_notify_id:" + param_notify_id)
            after_settlement_paid(settlement)

            after_settlement_paid_success = True
            alipay_pay_logger.info("alipay_callback_success. trade_data_notify_id:" + param_notify_id)

    if after_settlement_paid_success is True:

        # 这个要在事务成功提交之后才能触发
        after_settlement_paid_commit(settlement)

        return gen(CODES.SUCCESS)
    else:
        alipay_pay_logger.error("alipay_callback_fail. CODES.UNKNOWN_ERROR. trade_data:" + json.dumps(trade_data))
        return gen(CODES.UNKNOWN_ERROR)


_AlipayAppPayTools = {}


def _init_AlipayAppPayTools():
    tools = AlipayAppPayTools()
    tools.use_old_conifg()
    _AlipayAppPayTools["old"] = tools
    _AlipayAppPayTools["new"] = AlipayAppPayTools()

    # app支付新接口, 后续需要全部迁移这个上面
    _AlipayAppPayTools['app_new'] = AlipayWapPayTools()


@bind_context('pay/alipay/purchase_notify_xml')
def alipay_purchase_notify_xml(ctx, trade_data):
    """
    M站支付宝付款异步通知 为 xml

        {
    "trade_data": {
        "v": "1.0",
        "sign": "4270d01655d50e0fe08db302d80f7ed0",
        "notify_data": "\n<notify>\n\t<payment_type>1</payment_type>\n\t<subject>\n\t\t&#12304;&#38271;&#27801;@&#33406;&#20381;&#32654;&#21307;&#30103;&#32654;&#23481;&#12305;&#28608;&#20809;&#33073;&#27611;\n\t</subject>\n\t<trade_no>2018072621001004910586654212\t</trade_no>\n\t<buyer_email>18646292152</buyer_email>\n\t<gmt_create>2018-07-26 20:29:44</gmt_create>\n\t<notify_type>trade_status_sync</notify_type>\n\t<quantity>1</quantity>\n\t<out_trade_no>100601991759</out_trade_no>\n\t<notify_time>2018-07-26 20:29:46</notify_time>\n\t<seller_id>2088211612339882</seller_id>\n\t<trade_status>TRADE_SUCCESS</trade_status>\n\t<is_total_fee_adjust>N</is_total_fee_adjust>\n\t<total_fee>1.00</total_fee>\n\t<gmt_payment>2018-07-26 20:29:45</gmt_payment>\n\t<seller_email>zhengxuan@wanmeizhensuo.com</seller_email>\n\t<price>1.00</price>\n\t<buyer_id>2088912569166911</buyer_id>\n\t<notify_id>8b0c6bc0d89dffefd9f7e2a1e79e81bn0x</notify_id>\n\t<use_coupon>N</use_coupon>\n</notify>",
        "service": "alipay.wap.trade.create.direct",
        "sec_id": "MD5"
    }
}

    :return:
    """

    alipay_pay_logger.info(json.dumps(trade_data))

    verified, _ = alipay_tool.verify_and_get_trade_status(trade_data)
    alipay_pay_logger.info('verified=%s|%s' % (str(verified), json.dumps(trade_data)))

    if verified is not True:
        return gen(CODES.UNKNOWN_ERROR)

    notify_data = trade_data['notify_data']
    trade_data = ElementTree.fromstring(notify_data.encode('utf-8'))
    out_trade_no = (trade_data.findtext('./out_trade_no') or "").strip()  # Not Null
    trade_status = (trade_data.findtext('./trade_status') or "").strip()
    total_fee = float((trade_data.findtext('./total_fee') or "").strip())
    notify_id = (trade_data.findtext('./notify_id') or "").strip()

    after_settlement_paid_success = False

    with transaction.atomic():
        try:
            settlement = Settlement.objects.select_for_update().get(pk=out_trade_no)
        except Settlement.DoesNotExist:
            return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

        if (trade_status == 'TRADE_SUCCESS' and
                    settlement.status in (SETTLEMENT_STATUS.PAYING, SETTLEMENT_STATUS.NOT_PAID, SETTLEMENT_STATUS.CANCEL)):

            purchase_tool.is_callback_payment_real(settlement.real_payment, total_fee)
            if not purchase_tool.check_from_alipay(notify_id):
                raise gen(CODES.VERIFY_FAILED)

            can_cancled_settlement_2_paid(settlement)

            gmt_create_str = (trade_data.findtext("./gmt_create") or "").strip()
            gmt_create = datetime.datetime.strptime(gmt_create_str, "%Y-%m-%d %H:%M:%S")
            gmt_payment_str = (trade_data.findtext("./gmt_payment") or "").strip()
            gmt_payment = datetime.datetime.strptime(gmt_payment_str, "%Y-%m-%d %H:%M:%S")

            trade_data_dict = {
                "trade_no": (trade_data.findtext("./trade_no") or "").strip(),
                "trade_status": (trade_data.findtext("./trade_status") or "").strip(),
                "buyer_id": (trade_data.findtext("./buyer_id") or "").strip(),
                "buyer_email": (trade_data.findtext("./buyer_email") or "").strip(),
                "total_fee": total_fee,
                "out_trade_no": out_trade_no,
                "seller_id": (trade_data.findtext("./seller_id") or "").strip(),
                "gmt_create": gmt_create,
                "gmt_payment": gmt_payment,
            }

            origin_data_string = ElementTree.tostring(trade_data)
            AlipaySettlement.objects.create_record(settlement, trade_data_dict, origin_data_string)

            pay_time = gmt_payment

            order_ids = set(SettlementItem.objects.filter(
                settlement_id=settlement.id
            ).values_list('order_id', flat=True))

            PaymentOrder.objects.save_from_alipay(
                order_ids=order_ids, trade_data=trade_data_dict, notify_data_str=origin_data_string,
                pay_start_time=gmt_create, pay_time=pay_time,
            )

            settlement_paid_with_log(
                ctx=ctx,
                settlement=settlement,
                payment_channel=PAYMENT_CHANNEL.ALIPAY,
                pay_time=pay_time,
            )
            after_settlement_paid(settlement)

            after_settlement_paid_success = True

        elif trade_status != 'TRADE_SUCCESS':
            # 其余逻辑(退款等)发给了支付接口不处理, 直接返回正常
            return gen(CODES.SUCCESS)

    if after_settlement_paid_success is True:
        # 这个要在事务成功提交之后才能触发
        after_settlement_paid_commit(settlement)

        return gen(CODES.SUCCESS)
    else:
        return gen(CODES.UNKNOWN_ERROR)


@bind_context('pay/settlement/apple_notify')
def apple_notify(ctx, trade_data):
    '''
    apple pay支付
    {
        "no_order": "413249339664",
        "pay_type": "O",
        "result_pay": "SUCCESS",
        "bank_code": "98000008",
        "money_order": "134.0",
        "oid_paybill": "2018062454368497",
        "sign": "e7a3c6d80327d303b062f56bb532ae59",
        "info_order": "【北京@北京爱悦丽格医疗美容】埋线提升",
        "dt_order": "20180624170057",
        "sign_type": "MD5",
        "oid_partner": "201603031000747503",
        "settle_date": "20180624"
    }
    '''
    apple_pay_logger.info(json.dumps(trade_data))
    after_settlement_paid_success = False

    with transaction.atomic():
        settlement = Settlement.objects.select_for_update().get(pk=trade_data['no_order'])
        trade_status = trade_data.get('result_pay', None)
        total_fee = trade_data.get('money_order', 0)
        if (trade_status == 'SUCCESS' and settlement.status in (
                SETTLEMENT_STATUS.PAYING, SETTLEMENT_STATUS.NOT_PAID, SETTLEMENT_STATUS.CANCEL)):

            # TODO: 安全校验
            apple_tool.check_is_from_apple(trade_data)
            purchase_tool.is_callback_payment_real(settlement.real_payment, total_fee)

            can_cancled_settlement_2_paid(settlement)
            from pay.models import ApplePaySettlement
            ApplePaySettlement.objects.create_record(settlement, trade_data)

            pay_time = datetime.datetime.now()  # 连连返回的数据没有准确的支付时间

            order_ids = set(SettlementItem.objects.filter(
                settlement_id=settlement.id
            ).values_list('order_id', flat=True))

            PaymentOrder.objects.save_from_apple(
                order_ids=order_ids, trade_data=trade_data, pay_time=pay_time,
            )

            settlement_paid_with_log(
                ctx=ctx,
                settlement=settlement,
                payment_channel=PAYMENT_CHANNEL.APPLEPAY,
                pay_time=pay_time,
            )
            after_settlement_paid(settlement)

            after_settlement_paid_success = True

    if after_settlement_paid_success is True:
        # 这个要在事务成功提交之后才能触发
        after_settlement_paid_commit(settlement)
    else:
        return gen(CODES.UNKNOWN_ERROR)


@bind_context('pay/alipay/purchase_notify')
def alipay_purchase_notify_for_web(ctx, trade_data):
    """
    M/pc站支付宝付款异步通知:
        {'gmt_create': '2019-07-29 18:19:47', 'charset': 'utf-8', 'gmt_payment': '2019-07-29 18:19:55', 'notify_time': '2019-07-29 18:19:56', 'subject': '【成都@杨隆强】牙齿矫正', 'sign': 'XWuBxHag6c57gpyChskt7gH9pgbUA6i67te17CGJ+/UbAM0F43xATMxikc7fVY7bXssIVU5t9NJPtvppXF95g36sRC87clm+sTRACUcvgfjTJY8X3cdSwuhNfg1aXWDdBiyNsWBX6vRVXXVoO4mODtTqN34WmuSDoBeVPonlpkc=', 'buyer_id': '2088102169023235', 'invoice_amount': '1000.00', 'version': '1.0', 'notify_id': '2019072900222181955023231000454448', 'fund_bill_list': '[{"amount":"1000.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': '487244732011', 'total_amount': '1000.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2019072922001423231000029957', 'auth_app_id': '2016091000475625', 'receipt_amount': '1000.00', 'point_amount': '0.00', 'app_id': '2016091000475625', 'buyer_pay_amount': '1000.00', 'sign_type': 'RSA', 'seller_id': '2088102174913091'}
    :return:
    """

    alipay_pay_logger.info(json.dumps(trade_data))

    if not _AlipayAppPayTools:
        _init_AlipayAppPayTools()

    AlipaywapPayTools = _AlipayAppPayTools['app_new']

    valid = AlipaywapPayTools.verify_alipay_notify_sign(trade_data)

    if not valid:
        alipay_pay_logger.error("verify_alipay_callback_sign_fail. trade_data:" + json.dumps(trade_data))
        raise gen(CODES.VERIFY_FAILED)

    out_trade_no = trade_data.get('out_trade_no', None)
    trade_status = trade_data.get('trade_status', None)
    total_fee = trade_data.get('total_amount', None)
    notify_id = trade_data.get('notify_id', None)

    after_settlement_paid_success = False

    with transaction.atomic():
        try:
            settlement = Settlement.objects.select_for_update().get(pk=out_trade_no)
        except Settlement.DoesNotExist:
            return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

        if (trade_status == 'TRADE_SUCCESS' and
                    settlement.status in (SETTLEMENT_STATUS.PAYING, SETTLEMENT_STATUS.NOT_PAID, SETTLEMENT_STATUS.CANCEL)):

            purchase_tool.is_callback_payment_real(settlement.real_payment, total_fee)

            alipay_pay_logger.info("alipay_callback_begin_atomic. trade_data_notify_id:" + notify_id)

            can_cancled_settlement_2_paid(settlement)

            alipay_pay_logger.info("alipay_callback_begin_create_record. trade_data_notify_id:" + notify_id)

            gmt_create_str = trade_data.get('gmt_create', '')
            gmt_payment_str = trade_data.get('gmt_payment', '')
            gmt_create = datetime.datetime.strptime(gmt_create_str, "%Y-%m-%d %H:%M:%S")
            gmt_payment = datetime.datetime.strptime(gmt_payment_str, "%Y-%m-%d %H:%M:%S")

            trade_no = trade_data.get('trade_no', "")
            trade_status = trade_data.get('trade_status', "")
            buyer_id = trade_data.get('buyer_id', "")
            buyer_email = trade_data.get('buyer_email', "")
            seller_id = trade_data.get('seller_id', "")

            trade_data_dict = {
                "trade_no": trade_no,
                "trade_status": trade_status,
                "buyer_id": buyer_id,
                "buyer_email": buyer_email,
                "total_fee": total_fee,
                "out_trade_no": out_trade_no,
                "seller_id": seller_id,
                "gmt_create": gmt_create,
                "gmt_payment": gmt_payment,
            }

            origin_data_string = json.dumps(trade_data)
            AlipaySettlement.objects.create_record(settlement, trade_data_dict, origin_data_string)

            pay_time = gmt_payment

            order_ids = set(SettlementItem.objects.filter(
                settlement_id=settlement.id
            ).values_list('order_id', flat=True))

            PaymentOrder.objects.save_from_alipay(
                order_ids=order_ids, trade_data=trade_data_dict, notify_data_str=origin_data_string,
                pay_start_time=gmt_create, pay_time=pay_time,
            )

            settlement_paid_with_log(
                ctx=ctx,
                settlement=settlement,
                payment_channel=PAYMENT_CHANNEL.ALIPAY,
                pay_time=pay_time,
            )
            after_settlement_paid(settlement)

            after_settlement_paid_success = True

        elif trade_status != 'TRADE_SUCCESS':
            return gen(CODES.SUCCESS)

    if after_settlement_paid_success is True:
        # 这个要在事务成功提交之后才能触发
        after_settlement_paid_commit(settlement)

        return gen(CODES.SUCCESS)
    else:
        return gen(CODES.UNKNOWN_ERROR)


@bind_context('api/settlement/zero_order_fake_alipay_callback')
def settlement_alipay_callback(ctx, settlement_id):
    """settlement callback after pay(alipay).
    零元单支付后续逻辑
    """
    from datetime import datetime
    trade_data = {
            "sign": "",
            "sign_type": "",
            "buyer_email": "",
            "discount": "0.00",
            "trade_status": "",
            "trade_no": "",
            "use_coupon": "N",
            "payment_type": "1",
            "quantity": "",
            "notify_id": "",
            "notify_type": "",
            "buyer_id": "",
            "is_total_fee_adjust": "N",
            "seller_id": settings.ALIPAY_PARTNER,
            "seller_email": settings.ALIPAY_SELLER_EMAIL,
            "gmt_create": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "notify_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "gmt_payment": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        }
    after_settlement_paid_success = False
    with transaction.atomic():
        try:
            settlement = Settlement.objects.select_for_update().get(pk=settlement_id)
            trade_data['out_trade_no'] = settlement_id
            trade_data['price'] = settlement.real_payment
            trade_data['total_fee'] = settlement.real_payment
            trade_data['body'] = settlement.name
            trade_data['subject'] = settlement.name
        except Settlement.DoesNotExist:
            alipay_pay_logger.error(
                "alipay_callback_fail. CODES.SETTLEMENT_DOES_NOT_EXIST. trade_data:" + json.dumps(trade_data))
            return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

        if settlement.settlement_type != SETTLEMENT_TYPE.ZERO_ORDER:
            return gen(CODES.OPERATION_NOT_SUPPORTED)

        # 支付宝付款成功, 更新订单状态, 并存储支付信息
        sign_pay_finish_task.delay(settlement_id)
        can_cancled_settlement_2_paid(settlement)
        origin_data_string = json.dumps(trade_data)
        AlipaySettlement.objects.create_record(settlement, trade_data, origin_data_string)

        pay_start_time = trade_data.get('gmt_create')
        pay_time = trade_data.get('gmt_payment')

        order_ids = set(SettlementItem.objects.filter(
            settlement_id=settlement.id
        ).values_list('order_id', flat=True))

        PaymentOrder.objects.save_from_alipay(
            order_ids=order_ids, trade_data=trade_data, notify_data_str=origin_data_string,
            pay_start_time=pay_start_time, pay_time=pay_time,
        )
        settlement_paid_with_log(
            ctx=ctx,
            settlement=settlement,
            payment_channel=PAYMENT_CHANNEL.ALIPAY,
            pay_time=pay_time,
        )

        try:
            # 避免正常下单出现问题
            ctx.logger.app(**paid_success(order_ids=order_ids))
        except Exception:
            pass

        after_settlement_paid(settlement)
        after_settlement_paid_success = True

    if after_settlement_paid_success is True:

        # 这个要在事务成功提交之后才能触发
        after_settlement_paid_commit(settlement)

        return gen(CODES.SUCCESS)
    else:
        alipay_pay_logger.error("CODES.UNKNOWN_ERROR. trade_data:" + json.dumps(trade_data))
        return gen(CODES.UNKNOWN_ERROR)
