class UserCashBalanceLog(db.Model): __tablename__ = 'user_cash_balance_log' id = db.Column(db.BigInteger, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('account_user.id'), nullable=False) user = db.relationship('AccountUser', backref=db.backref('cash_balance_logs', lazy='dynamic')) event_id = db.Column(db.BigInteger, db.ForeignKey('event.id'), nullable=False) event = db.relationship('Event', backref=db.backref('cash_balance_logs', lazy='dynamic')) total = db.Column(db.Numeric(16, 2), nullable=False, default=0) available = db.Column(db.Numeric(16, 2), nullable=False, default=0) frozen = db.Column(db.Numeric(16, 2), nullable=False, default=0) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) def __repr__(self): return '<UserCashBalanceLog %r: [%r, %r]>' % (self.user_id, self.total, self.available)
class UserWithdrawLog(db.Model): __tablename__ = 'user_withdraw_log' id = db.Column(db.Integer, primary_key=True) tx_sn = db.Column(db.CHAR(32), nullable=False) user_id = db.Column(db.Integer, nullable=False) bankcard_id = db.Column(db.Integer, db.ForeignKey('bankcard.id'), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) actual_amount = db.Column(db.Numeric(16, 2), nullable=False) fee = db.Column(db.Numeric(16, 2), nullable=False, default=0) state = db.Column(db.Enum(WithdrawState.PROCESSING, WithdrawState.FAILED, WithdrawState.SUCCESS), nullable=False) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
class PrepaidRecord(db.Model): __tablename__ = 'prepaid_record' id = db.Column(db.BigInteger, primary_key=True) tx_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=False, unique=True) tx = db.relationship('Transaction', backref=db.backref('prepaid_record', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), nullable=False) to_id = db.Column(db.Integer, nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) client_callback_url = db.Column(db.VARCHAR(128)) client_notify_url = db.Column(db.VARCHAR(128)) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) @property def payer_id(self): return self.to_id def __repr__(self): return '<Prepaid %r->%r>' % (self.amount, self.to_id)
class RefundRecord(db.Model): __tablename__ = 'refund_record' id = db.Column(db.BigInteger, primary_key=True) tx_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=False, unique=True) tx = db.relationship('Transaction', backref=db.backref('refund_record', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), nullable=False) payment_sn = db.Column(db.CHAR(32), nullable=False) payment_state = db.Column(db.VARCHAR(32), nullable=False) payer_id = db.Column(db.Integer, nullable=False) payee_id = db.Column(db.Integer, nullable=False) order_id = db.Column(db.VARCHAR(64), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) client_notify_url = db.Column(db.VARCHAR(128)) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) def __repr__(self): return '<Refund %r, %r<-%r$%r>' % (self.id, self.payer_id, self.payee_id, self.amount)
class UserLiabilityAccountItem(db.Model): __tablename__ = 'user_liability_account_item' id = db.Column(db.BigInteger, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('account_user.id'), nullable=False) user = db.relationship('AccountUser', backref=db.backref('liability_account_items', lazy='dynamic')) type = db.Column(db.Enum('TOTAL', 'AVAILABLE', 'FROZEN'), nullable=False) event_id = db.Column(db.BigInteger, db.ForeignKey('event.id'), nullable=False) event = db.relationship('Event', backref=db.backref('liability_account_items', lazy='dynamic')) side = db.Column(db.Enum('DEBIT', 'CREDIT'), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) def __repr__(self): return '<UserLiabilityAccountItem %r, %r, %r>' % ( self.user_id, self.side, self.amount)
class SystemAssetAccountItem(db.Model): __tablename__ = 'system_asset_account_item' id = db.Column(db.BigInteger, primary_key=True) vas_name = db.Column(db.VARCHAR(32), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('account_user.id'), nullable=False) user = db.relationship('AccountUser', backref=db.backref('asset_account_items', lazy='dynamic')) event_id = db.Column(db.BigInteger, db.ForeignKey('event.id'), nullable=False) event = db.relationship('Event', backref=db.backref('asset_account_items', lazy='dynamic')) side = db.Column(db.Enum('DEBIT', 'CREDIT'), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) def __repr__(self): return '<SystemAssetAccountItem %r, %r>' % (self.side, self.amount)
class Event(db.Model): __tablename__ = 'event' id = db.Column(db.BigInteger, primary_key=True) tx_sn = db.Column(db.CHAR(32), nullable=False) vas_name = db.Column(db.VARCHAR(32), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('account_user.id'), nullable=False) user = db.relationship('AccountUser', backref=db.backref('events', lazy='dynamic')) type = db.Column(db.Enum(EventType.TRANSFER_IN, EventType.TRANSFER_OUT, EventType.FREEZE, EventType.UNFREEZE, EventType.TRANSFER_IN_FROZEN, EventType.TRANSFER_OUT_FROZEN), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) def __repr__(self): return '<Event %r, %r, %r>' % (self.id, self.user_id, self.amount)
class WithdrawRecord(db.Model): __tablename__ = 'withdraw_record' id = db.Column(db.BigInteger, primary_key=True) tx_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=False) tx = db.relationship('Transaction', backref=db.backref('withdraw_record', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), nullable=False) from_user_id = db.Column(db.Integer, nullable=False) flag_card = db.Column(db.CHAR(1), nullable=False) card_type = db.Column(db.Enum('DEBIT', 'CREDIT'), nullable=False) card_no = db.Column(db.VARCHAR(21), nullable=False) acct_name = db.Column(db.VARCHAR(12), nullable=False) bank_code = db.Column(db.CHAR(9)) province_code = db.Column(db.VARCHAR(12)) city_code = db.Column(db.VARCHAR(12)) bank_name = db.Column(db.VARCHAR(32), nullable=False) brabank_name = db.Column(db.VARCHAR(50)) prcptcd = db.Column(db.CHAR(12)) amount = db.Column(db.Numeric(16, 2), nullable=False) actual_amount = db.Column(db.Numeric(16, 2), nullable=False) fee = db.Column(db.Numeric(16, 2), nullable=False, default=0) client_notify_url = db.Column(db.VARCHAR(128)) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) def __repr__(self): return '<Withdraw %r>' % (self.id, )
class DebitNoteDetail(db.Model): __tablename__ = 'debit_note_detail' id = db.Column(db.BigInteger, primary_key=True) sn = db.Column(db.CHAR(32), nullable=False) vas_name = db.Column(db.VARCHAR(32), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) order_id = db.Column(db.VARCHAR(64), nullable=False, default='') state = db.Column(db.Boolean, nullable=False, default=False) # False:Lvyeok True:LLok valid = db.Column(db.Boolean, nullable=False, default=False) # False:checking True:ok type = db.Column(db.Enum(TransactionType.PAYMENT, TransactionType.REFUND), nullable=False) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) def __repr__(self): return '<DebitNoteDetail %r, %r, %r, %r, %r, %r, %r, %r>' % ( self.id, self.sn, self.vas_name, self.amount, self.order_id, self.state, self.valid, self.created_on)
class TransferRecord(db.Model): __tablename__ = 'transfer_record' id = db.Column(db.BigInteger, primary_key=True) tx_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=False) tx = db.relationship('Transaction', backref=db.backref('transfer_record', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), nullable=False) from_id = db.Column(db.Integer, nullable=False) to_id = db.Column(db.Integer, nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) def __repr__(self): return '<Transfer %r>' % (self.id, )
class Transaction(db.Model): __tablename__ = 'transaction' id = db.Column(db.BigInteger, primary_key=True) # 父tx super_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=True, default=None) super = db.relationship('Transaction', remote_side=id, backref=db.backref('subs', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), unique=True) type = db.Column(db.Enum(TransactionType.PAYMENT, TransactionType.REFUND, TransactionType.WITHDRAW, TransactionType.TRANSFER, TransactionType.PREPAID, TransactionType.CHEQUE), nullable=False) channel_name = db.Column(db.VARCHAR(32), nullable=False) order_id = db.Column(db.VARCHAR(64), nullable=False, default='') # 涉及到的总金额 # 支付金额,退款金额,提现金额(+手续费), 充值金额 amount = db.Column(db.Numeric(16, 2), nullable=False) comments = db.Column(db.VARCHAR(128), default='') state = db.Column(db.VARCHAR(32), nullable=False) # need update on notify. vas_name = db.Column(db.VARCHAR(32), nullable=False, default='') vas_sn = db.Column(db.VARCHAR(128), nullable=False, default='') tried_times = db.Column(db.Integer, nullable=False, default=1) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) def __init__(self, *args, **kwargs): super(Transaction, self).__init__(*args, **kwargs) self.__record = None self.__source_sn = None self.__stack_sn_item = None @property def channel(self): from api_x.zyt.user_mapping import get_channel_by_name return get_channel_by_name(self.channel_name) @staticmethod def get_tx_from_hashed_sn(hashed_sn): try: tx_id, _hash = utils.aes_decrypt(hashed_sn).split('$', 1) return Transaction.query.get(tx_id), _hash except Exception as e: logger.warn('bad hashed sn: [{0}], [{1}]'.format( hashed_sn, e.message)) return None, None @property def sn_with_expire_hash(self): now = times.timestamp() expired = now + etc.Biz.PAYMENT_CHECKOUT_VALID_SECONDS key = str(self.id) + str(self.tried_times) + etc.KEY data = str(int(expired)) logger.info('hash sn: [%s], now: [%s], expired: [%s]' % (self.sn, now, expired)) _hash = utils.aes_encrypt(data, key) return utils.aes_encrypt('%s$%s' % (self.id, _hash)) def check_expire_hash(self, expire_hash): now = times.timestamp() key = str(self.id) + str(self.tried_times) + etc.KEY try: logger.info('try check sn: [%s], now: [%s], _hash: [%s]' % (self.sn, now, expire_hash)) expired = long(utils.aes_decrypt(expire_hash, key)) except Exception as _: return False timestamp = times.timestamp() logger.info('done check sn: [%s], timestamp: [%s], expired: [%s]' % (self.sn, timestamp, expired)) return timestamp < expired @property def record(self): if hasattr(self, '_Transaction__record') and self.__record: return self.__record if self.type == TransactionType.PAYMENT: self.__record = self.payment_record.one() elif self.type == TransactionType.REFUND: self.__record = self.refund_record.one() elif self.type == TransactionType.WITHDRAW: self.__record = self.withdraw_record.one() elif self.type == TransactionType.TRANSFER: self.__record = self.transfer_record.one() elif self.type == TransactionType.PREPAID: self.__record = self.prepaid_record.one() elif self.type == TransactionType.CHEQUE: self.__record = self.cheque_record.one() return self.__record def get_role(self, role): return self.users.filter_by(role=role).first() @property def source_sn(self): """ 表示当前使用的sn,参考 TransactionSnStack """ # NOTE: Model类的属性名前面都添加了_<ClassName>前缀 if hasattr(self, '_Transaction__source_sn') and self.__source_sn: return self.__source_sn return self.sn @source_sn.setter def source_sn(self, sn): self.__source_sn = sn @property def stack_sn_item(self): """ 表示当前使用的sn,参考 TransactionSnStack """ # NOTE: Model类的属性名前面都添加了_<ClassName>前缀 if hasattr(self, '_Transaction__stack_sn_item') and self.__stack_sn_item: return self.__stack_sn_item return self.sn @stack_sn_item.setter def stack_sn_item(self, sn): self.__stack_sn_item = sn def __repr__(self): return '<Transaction %s, %s, %s, %s>' % (self.type, str( self.amount), self.state, str(self.created_on))
class ChequeRecord(db.Model): __tablename__ = 'cheque_record' id = db.Column(db.BigInteger, primary_key=True) tx_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=False) tx = db.relationship('Transaction', backref=db.backref('cheque_record', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), nullable=False) type = db.Column(db.Enum(ChequeType.INSTANT, ChequeType.LAZY), nullable=False) signature = db.Column(db.CHAR(32)) info = db.Column(db.CHAR(128), default='') from_id = db.Column(db.Integer, nullable=False) # 兑现时才知道 to_id = db.Column(db.Integer, nullable=True) amount = db.Column(db.Numeric(16, 2), nullable=False) client_notify_url = db.Column(db.VARCHAR(128)) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) expired_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) @property def cash_token(self): tx_id = self.tx_id signature = self.signature signer = Signer('key', 'sign', etc.KEY, etc.LVYE_PRI_KEY, None) data = {'tx_id': tx_id, 'signature': signature} sign = signer.sign(data, SignType.RSA) return '%s_%s' % (ints.int_to_base36(tx_id), hashlib.md5(sign).hexdigest()[::2]) @staticmethod def get_cheque_record_from_cash_token(cash_token): try: from api_x.zyt.biz.transaction.dba import get_tx_by_id tx_id, hash_sign = cash_token.split('_') tx_id = ints.base36_to_int(tx_id) tx = get_tx_by_id(tx_id) if tx is None or tx.type != TransactionType.CHEQUE: return None cheque_record = tx.record if cheque_record.cash_token == cash_token: return cheque_record except Exception as _: pass return None def __repr__(self): return '<Check %r, %r>' % (self.id, self.amount)
class PaymentRecord(db.Model): __tablename__ = 'payment_record' id = db.Column(db.BigInteger, primary_key=True) tx_id = db.Column(db.BigInteger, db.ForeignKey('transaction.id'), nullable=False, unique=True) tx = db.relationship('Transaction', backref=db.backref('payment_record', lazy='dynamic'), lazy='joined') sn = db.Column(db.CHAR(32), nullable=False) type = db.Column(db.Enum(PaymentType.DIRECT, PaymentType.GUARANTEE), nullable=False) payer_id = db.Column(db.Integer, nullable=False) payee_id = db.Column(db.Integer, nullable=False) channel_id = db.Column(db.Integer, nullable=False) order_id = db.Column(db.VARCHAR(64), nullable=False) origin = db.Column(db.VARCHAR(32), default=None) product_name = db.Column(db.VARCHAR(150), nullable=False) product_category = db.Column(db.VARCHAR(50), nullable=False) product_desc = db.Column(db.VARCHAR(350), nullable=False) amount = db.Column(db.Numeric(16, 2), nullable=False) real_amount = db.Column(db.Numeric(16, 2), nullable=False, default=0) paid_amount = db.Column(db.Numeric(16, 2), nullable=False, default=0) refunded_amount = db.Column(db.Numeric(16, 2), nullable=False, default=0) client_callback_url = db.Column(db.VARCHAR(128)) client_notify_url = db.Column(db.VARCHAR(128)) created_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) tried_times = db.Column(db.Integer, nullable=False, default=1) updated_on = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) # index __table_args__ = (db.UniqueConstraint( 'channel_id', 'order_id', 'origin', name='channel_order_id_origin_uniq_idx'), ) def is_finished(self): return self.real_amount == self.paid_amount - self.refunded_amount @transactional def add_refund(self, refund_record, event_id): from api_x.constant import PaymentTxState from api_x.zyt.biz.transaction import transit_transaction_state self.refunded_amount += refund_record.amount # 全部金额都退款,则状态为已退款 is_refunded = self.paid_amount == self.refunded_amount if self.tx.state == PaymentTxState.REFUNDING: if is_refunded: transit_transaction_state(self.tx_id, PaymentTxState.REFUNDING, PaymentTxState.REFUNDED, event_id) else: transit_transaction_state(self.tx_id, PaymentTxState.REFUNDING, refund_record.payment_state, event_id) db.session.add(self) super_tx = self.tx.super if super_tx and super_tx.type == TransactionType.PAYMENT: super_payment = super_tx.record super_payment.add_refund(refund_record, event_id) @transactional def add_paid(self, amount): self.paid_amount += amount db.session.add(self) super_tx = self.tx.super if super_tx and super_tx.type == TransactionType.PAYMENT: super_payment = super_tx.record super_payment.add_paid(amount) def __repr__(self): return '<Payment %r, %r->%r$%r:%r:%r/%r>' % ( self.id, self.payer_id, self.payee_id, self.amount, self.real_amount, self.paid_amount, self.refunded_amount)