def post(self, match_id): """" TODO: 如果回调时赛事已满员则自动退款处理 Parteam Api request example: { "message": "成功", "code": 200, "attribute": { "userId": 169, "orderNo": "11354642167546", "paymentMethod":1, // 支付类型 1微信,2支付宝 "orderState":2 // // 订单状态 1 待付款 2 已付款 3 超时未付款 4 已发货 } } """ data = self.request.body.decode() data = json.loads(data) if not data.get("attribute"): raise ApiException(400, "未包含 attribute") order = data['attribute'] member = MatchMember.get_or_404(pt_order_no=order['orderNo']) if order['orderState'] in (2, 4): # 将状态为未支付报名状态修改为正常 if member.state == MatchMember.MatchMemberState.wait_pay: payment_method = (TeamOrder.OrderPaymentMethod.WXPAY, TeamOrder.OrderPaymentMethod.ALIPAY )[order['paymentMethod'] - 1] with self.db.transaction(): MatchMember.update( state=MatchMember.MatchMemberState.normal).where( MatchMember.id == member.id).execute() TeamOrder.update( paid=datetime.now(), state=TeamOrder.OrderState.TRADE_BUYER_PAID, gateway_trade_no=order['orderNo'], payment_method=payment_method).where( TeamOrder.id == member.order_id).execute() # 统计赛事人数 Match.update_members_count(member.match_id) if member.group_id > 0: MatchGroup.update_members_count(member.group_id) match_notify.join_match_done\ .apply_async(args=[match_id, member.id], retry=True) self.write_success()
def initial_data(self): self.team_owner = User.create(name='test_activity') self.team = Team.create(name='club_test_activity', owner_id=self.team_owner.id) self.user = self.creator = User.create(name='activity_creator') self.activity = Activity.create(team=self.team, creator=self.creator, price='10', vip_price='8', leader=self.creator, title='just a test', description='description', need_nickname=True, join_end="3000-12-30 22:59:59", max_members=10, start_time='3000-01-01 00:00:01', end_time='3000-12-31 23:59:59') self.activity_2 = Activity.create(team=self.team, creator=self.creator, price='10', vip_price='8', leader=self.creator, title='just a test', description='description', need_nickname=True, join_end="3000-12-30 22:59:59", max_members=10, start_time='3000-01-01 00:00:01', end_time='3000-12-31 23:59:59') self.activity_member = ActivityMember.create( activity=self.activity, user=self.user, total_fee='0.01', nickname="leave activities", payment_method=TeamOrder.OrderPaymentMethod.WXPAY.value, payment_state=TeamOrder.OrderState.TRADE_BUYER_PAID, ) TeamMemberService.new_order(team=self.team, activity_id=self.activity.id, user=self.user, order_type=TeamOrder.OrderType.ACTIVITY, payment_method='wxpay', total_fee=self.activity.price, payment_fee=self.activity.price, title='TestLeave') TeamOrder.update(state=TeamOrder.OrderState.TRADE_BUYER_PAID)\ .where(TeamOrder.activity_id == self.activity.id, TeamOrder.user == self.user)\ .execute()
def _finish_order(self, order: TeamOrder, arguments: dict, payment: PaymentMethod): """ 完成支付流程 Args: order: TeamOrder arguments: dict, 微信服务器回调的 body payment: PaymentMethod, 支付方式 Returns: """ logging.debug('开始终结支付订单[{0}]'.format(order.order_no)) if payment == PaymentMethod.ALIPAY: # 支付宝 gateway_account = arguments["buyer_email"] gateway_trade_no = arguments["trade_no"] else: # 默认违心支付 gateway_trade_no = arguments['transaction_id'] gateway_account = arguments['openid'] TeamOrder.update(state=TeamOrder.OrderState.TRADE_BUYER_PAID, paid=datetime.datetime.now(), gateway_trade_no=gateway_trade_no, gateway_account=gateway_account) \ .where(TeamOrder.order_no == order.order_no) \ .execute() ActivityMember \ .update(payment_method=payment.value, payment_state=TeamOrder.OrderState.TRADE_BUYER_PAID, state=ActivityMember.ActivityMemberState.confirmed, paid=datetime.datetime.now(), confirmed=datetime.datetime.now()) \ .where(ActivityMember.activity == order.activity_id) \ .execute() # 更新活动成员数 Activity.update_members_count(order.activity_id)
def leave(cls, user_id, match: Match, notify_url: str = None, insists=False, role: int = 1): """ 退出赛事 :param user_id: :param match: :param notify_url: 派队回调地址 :param insists: 强制退出 :param role: 退赛发起人, 1 用户, 2 赛事方 :return: """ logging.debug("notify_url: {0}, insists: {1}".format( notify_url, insists)) if not insists and notify_url is None: raise AssertionError("非强制退出 `insists=False` 操作需要提供退款回调" "地址 `notify_url`") if insists is False and not match.can_leave(): raise MatchException("赛事无法退出") # 退出赛事 member = MatchMember.get(user_id=user_id, match_id=match.id) # type: MatchMember with Match._meta.database.transaction(): if insists: match.leave(member) # Warning: 数据库事物中尝试远程 HTTP 调用, 需要修复 else: # match.leave_request(member) # 调用退款接口 pt = Parteam(app.settings["parteam_api_url"]) if member.order_id: # 有支付信息, 调用退款接口 order = TeamOrder.get( id=member.order_id) # type: TeamOrder refund_fee = int(order.payment_fee * 100) try: pt.order_refund(user_id=user_id, order_no=member.pt_order_no, refund_fee=refund_fee, notify_url=notify_url, role=role) except NotPaidError as e: logging.warning("调用派队退款接口发现订单未支付: {0}".format(str(e))) TeamOrder.update(state=TeamOrder.OrderState .TRADE_CLOSED_BY_USER.value) \ .where(TeamOrder.id == member.order_id) \ .execute() match.leave(member) except RefundError as e: raise MatchException(e) else: # 更新订单状态为 `退款`, 订单退款状态为 `全部退款` TeamOrder.update( state=TeamOrder.OrderState.TRADE_CLOSED.value, refund_state=TeamOrder.OrderRefundState.FULL_REFUNDED.value, refunded_time=datetime.now())\ .where(TeamOrder.id == member.order_id)\ .execute() match.leave(member) else: # 无支付信息直接退赛 match.leave(member)
def refund(order_no, refund_fee, is_retry=False): """ Args: order_no: int, 订单号 refund_fee: 退款金额 """ wxpay = WxPay(appid=current_app.settings["wxpay_appid"], mch_id=current_app.settings["wxpay_mchid"], secret_key=current_app.settings["wxpay_secret_key"], ca_certs=current_app.settings["wxpay_ca_certs"], client_cert=current_app.settings["wxpay_api_client_cert"], client_key=current_app.settings["wxpay_api_client_key"]) order = TeamOrder.get(order_no=order_no) if order.payment_method == order.OrderPaymentMethod.WXPAY.value and \ order.refund_state in ( TeamOrder.OrderRefundState.PARTIAL_REFUNDING.value, TeamOrder.OrderRefundState.PARTIAL_REFUNDED.value, TeamOrder.OrderRefundState.FULL_REFUNDING.value, TeamOrder.OrderRefundState.FULL_REFUNDED.value ): if is_retry: out_trade_no = 'J%s' % order.order_no else: out_trade_no = 'N%s' % order.order_no try: response = wxpay.refund(out_trade_no=out_trade_no, out_refund_no="R%s" % order.order_no, total_fee=order.total_fee, refund_fee=refund_fee, # transaction_id=order.gateway_trade_no, op_user_id=order.gateway_account) except Exception as e: logging.error("refund fail, order_no:{0} exception:{1}".format(out_trade_no, e)) response = None if response and response["result_code"] == "SUCCESS": logging.debug(response) TeamOrder.update( refund_state=TeamOrder.OrderRefundState.FULL_REFUNDED.value, refunded_fee=refund_fee, refunded_time=datetime.datetime.now(), state=TeamOrder.OrderState.TRADE_CLOSED, )\ .where(TeamOrder.order_no == order.order_no)\ .execute() # 换个订单号重试 elif not is_retry: refund(order_no, refund_fee, is_retry=True) else: # 标记退款失败 TeamOrder.update( refund_state=TeamOrder.OrderRefundState.FULL_REFUND_FAILED.value, ).where(TeamOrder.order_no == order.order_no)\ .execute() logging.error("refund fail,order_no:{0} response:{1}".format(order_no, response))
def finish_activity(activity_id): """结算活动场次 1. 将用户在线支付费用转到俱乐部账户 2. 标记场次和订单状态为完成 3. 记录俱乐部账户变化 """ activity = Activity.get_or_404(id=activity_id) if activity.end_time > datetime.now(): raise Exception("活动场次未结束") if activity.state == Activity.ActivityState.cancelled: raise Exception("活动场次已取消") if activity.state == Activity.ActivityState.finished: raise Exception("活动场次已结算") # 计算在线支付完成交易的总额 online_paid_amount = TeamOrder.select(fn.SUM(TeamOrder.payment_fee)).where( TeamOrder.activity_id == activity_id, TeamOrder.state >= TeamOrder.OrderState.TRADE_BUYER_PAID.value, TeamOrder.refund_state == TeamOrder.OrderRefundState.NO_REFUND, TeamOrder.payment_method << TeamOrder.ONLINE_PAYMENT_METHODS).scalar() or 0 # 计算余额支付完成交易的总额 credit_paid_amount = TeamOrder.select(fn.SUM(TeamOrder.credit_fee)).where( TeamOrder.activity_id == activity_id, TeamOrder.state >= TeamOrder.OrderState.TRADE_BUYER_PAID.value).scalar() or 0 # 使用次数 free_times_amount = ActivityMember.select(fn.SUM( ActivityMember.free_times)).where( ActivityMember.state == ActivityMember.ActivityMemberState.confirmed).scalar() or 0 # online_paid_amount= DecimalField(default=Decimal(0), verbose_name="在线支付收入") # credit_paid_amount= DecimalField(default=Decimal(0), verbose_name="余额支付收入") # cash_paid_amount= DecimalField(default=Decimal(0), verbose_name="现金支付收入") # free_times_amount = IntegerField(default=0, verbose_name="次卡支付数量") with app.db.transaction() as txn: team = Team.select().where( Team.id == activity.team.id).for_update().get() # 将收入打到俱乐部账上 Team.update( credit=Team.credit + online_paid_amount, total_receipts=Team.total_receipts + online_paid_amount, updated=datetime.now()).where(Team.id == team.id).execute() # 将订单修改状态为已完成 TeamOrder.update( state=TeamOrder.OrderState.TRADE_FINISHED.value, finished=datetime.now()).where( TeamOrder.activity_id == activity_id, TeamOrder.state == TeamOrder.OrderState.TRADE_BUYER_PAID.value, TeamOrder.refund_state == TeamOrder.OrderRefundState.NO_REFUND.value).execute() # 修改场次状态为已结算 Activity.update(state=Activity.ActivityState.finished, finished=datetime.now(), online_paid_amount=online_paid_amount, credit_paid_amount=credit_paid_amount, free_times_amount=free_times_amount).where( Activity.id == activity_id).execute() # 记录俱乐部账户变更 TeamAccountLog.create(team_id=team.id, credit_change=online_paid_amount, change_type=0, credit_before=team.credit, credit_after=team.credit + online_paid_amount, note="活动结算:%s(%s)" % (activity.title, activity.start_time), activity_id=activity_id, operator_id=0) # 生成下期活动 gen_next_activity(activity_id)