从0-100:物业缴费支付小程序开发笔记

设计背景

小区物业费用缴纳,公寓租金收取,出租屋租金收取等场景,大部分都是靠人工收取,而广大业主和住户可能时间或者工作繁忙,不能到现场付款,那么设计一个这样的小程序,方便物业公司进行账务收款,账务统计,节省人力物力成本,不论在家中或者外地,都可以通过小程序直接缴费,不受时间、地点约束,操作简单,方便快捷

概要设计

在这里插入图片描述

数据字典


SheetModel.DB_STRUCTURE = {
	_pid: 'string|true',
	SHEET_ID: 'string|true',

	SHEET_TITLE: 'string|true|comment=标题',
	SHEET_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中',

	SHEET_CATE_ID: 'string|true|default=0|comment=分类',
	SHEET_CATE_NAME: 'string|false|comment=分类冗余',

	SHEET_ORDER: 'int|true|default=9999',
	SHEET_VOUCH: 'int|true|default=0',

	SHEET_FORMS: 'array|true|default=[]',
	SHEET_OBJ: 'object|true|default={}',

	SHEET_QR: 'string|false',
	SHEET_VIEW_CNT: 'int|true|default=0',


	SHEET_CNT: 'int|true|default=0',
	SHEET_PAY_CNT: 'int|true|default=0',
	SHEET_WAIT_CNT: 'int|true|default=0',
	SHEET_NO_CNT: 'int|true|default=0',

	SHEET_FEE: 'int|true|default=0',
	SHEET_PAY_FEE: 'int|true|default=0',
	SHEET_WAIT_FEE: 'int|true|default=0',

	SHEET_ADD_TIME: 'int|true',
	SHEET_EDIT_TIME: 'int|true',
	SHEET_ADD_IP: 'string|false',
	SHEET_EDIT_IP: 'string|false',
};
SheetDataModel.DB_STRUCTURE = {
	_pid: 'string|true',
	SHEET_DATA_ID: 'string|true', 
	SHEET_DATA_SHEET_ID: 'string|true|comment=FK',
	SHEET_DATA_SHEET_TITLE: 'string|false',


	SHEET_DATA_PAY_TRADE_NO: 'string|false|comment=商家订单号 32位',
	SHEET_DATA_PAY_STATUS: 'int|true|default=0|comment=支付状态 0=未支付 1=已支付 99=无需支付',
	SHEET_DATA_PAY_FEE: 'int|true|default=0|comment=已支付费用 分',
	SHEET_DATA_PAY_TIME: 'int|true|default=0|comment=支付时间',

	SHEET_DATA_NAME: 'string|false|姓名',
	SHEET_DATA_MOBILE: 'string|false|手机号',
	SHEET_DATA_FEE: 'int|true|default=0|comment=需支付费用 分',

	SHEET_DATA_FORMS: 'array|true|default=[]',
	SHEET_DATA_OBJ: 'object|true|default={}',

	SHEET_DATA_ADD_TIME: 'int|true',
	SHEET_DATA_EDIT_TIME: 'int|true',
	SHEET_DATA_ADD_IP: 'string|false',
	SHEET_DATA_EDIT_IP: 'string|false',
};

关键实现


	async getMySheetChartList(userId, time = 180) {
		let where = {};
		if (!config.IS_DEMO) {
			let user = await UserModel.getOne({ USER_MINI_OPENID: userId });
			if (!user) return null;

			where = {
				SHEET_DATA_PAY_STATUS: 1,
				SHEET_DATA_NAME: user.USER_NAME,
				SHEET_DATA_MOBILE: user.USER_MOBILE,
				'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
			};
		}
		else {
			where = {
				SHEET_DATA_PAY_STATUS: 1,
				SHEET_DATA_NAME: 'Tom',
				SHEET_DATA_MOBILE: '14600000000',
				'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
			};
		}


		time = Number(time);
		time = this._timestamp - time * 86400 * 1000;

		where.SHEET_DATA_PAY_TIME = ['>=', time];

		let fields = 'SHEET_DATA_PAY_TIME,SHEET_DATA_FORMS,SHEET_DATA_FEE,sheet.SHEET_TITLE ';

		let orderBy = {
			'SHEET_DATA_PAY_TIME': 'desc',
			'SHEET_DATA_ADD_TIME': 'desc'
		};

		let joinParams = {
			from: SheetModel.CL,
			localField: 'SHEET_DATA_SHEET_ID',
			foreignField: '_id',
			as: 'sheet',
		};

		let list = await SheetDataModel.getListJoin(joinParams, where, fields, orderBy, 1, 200, false);
		list = list.list;

		if (list.length == 0) return null;

		let categories = [];
		let data = [];

		for (let k = 0; k < list.length; k++) {
			data.push(Number(list[k].SHEET_DATA_FEE / 100));
			categories.push(list[k].sheet.SHEET_TITLE.substr(0, 15));

			list[k].SHEET_DATA_PAY_TIME = timeUtil.timestamp2Time(list[k].SHEET_DATA_PAY_TIME);
		}
		return { categories, data, list: list.reverse() };

	}

	// 取得账单详情
	async getMySheetDataDetail(userId, sheetDataId) {
		let where = {};

		if (!config.IS_DEMO) {
			let user = await UserModel.getOne({ USER_MINI_OPENID: userId, USER_STATUS: UserModel.STATUS.COMM });
			if (!user) this.AppError('用户不存在或者状态异常');

			where = {
				_id: sheetDataId,
				SHEET_DATA_NAME: user.USER_NAME,
				SHEET_DATA_MOBILE: user.USER_MOBILE,
			}
		}
		else {
			where = {
				_id: sheetDataId,
				SHEET_DATA_NAME: 'Tom',
				SHEET_DATA_MOBILE: '14600000000',
			}
		}

		let sheetData = await SheetDataModel.getOne(where);
		if (!sheetData) return null;

		let sheet = await SheetModel.getOne({ _id: sheetData.SHEET_DATA_SHEET_ID, SHEET_STATUS: SheetModel.STATUS.COMM });
		if (!sheet) this.AppError('支付项目不存在');

		sheetData.sheet = sheet;
		return sheetData;
	}

	/** 取得我的分页列表 */
	async getMySheetDataList(userId, {
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		page,
		size = 30,
		isTotal = true,
		oldTotal
	}) {


		orderBy = orderBy || {
			'SHEET_DATA_ADD_TIME': 'desc'
		};
		let fields = 'SHEET_DATA_PAY_TIME,SHEET_DATA_ADD_TIME,SHEET_DATA_FEE,SHEET_DATA_FORMS,SHEET_DATA_PAY_FEE,SHEET_DATA_PAY_STATUS,sheet.SHEET_TITLE,sheet.SHEET_OBJ';

		let where = {};
		if (!config.IS_DEMO) {
			let user = await UserModel.getOne({ USER_MINI_OPENID: userId });
			if (!user) return null;
			where = {
				SHEET_DATA_NAME: user.USER_NAME,
				SHEET_DATA_MOBILE: user.USER_MOBILE,
				'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
			};
		}
		else {
			where = {
				SHEET_DATA_NAME: 'Tom',
				SHEET_DATA_MOBILE: '14600000000',
				'sheet.SHEET_STATUS': SheetModel.STATUS.COMM
			};
		}

		if (util.isDefined(search) && search) {
			where['SHEET_DATA_SHEET_TITLE'] = {
				$regex: '.*' + search,
				$options: 'i'
			};
		} else if (sortType) {
			// 搜索菜单
			switch (sortType) {
				case 'status': {
					where['SHEET_DATA_PAY_STATUS'] = Number(sortVal);
					break;
				}
				case 'sort': { //按时间倒序
					orderBy = this.fmtOrderBySort(sortVal, 'SHEET_DATA_ADD_TIME');
					break;
				}

			}
		}

		let joinParams = {
			from: SheetModel.CL,
			localField: 'SHEET_DATA_SHEET_ID',
			foreignField: '_id',
			as: 'sheet',
		};

		let result = await SheetDataModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);

		return result;
	}

	async statSheetData(sheetId) {
		let where = {
			SHEET_DATA_SHEET_ID: sheetId
		}

		// 总数
		let cnt = await SheetDataModel.count(where);


		// 总费用
		let fee = await SheetDataModel.sum(where, 'SHEET_DATA_FEE');


		// 已支付记录
		let wherePayCnt = {
			SHEET_DATA_SHEET_ID: sheetId,
			SHEET_DATA_PAY_STATUS: 1,
		}
		let payCnt = await SheetDataModel.count(wherePayCnt);

		// 无须支付
		let whereNoCnt = {
			SHEET_DATA_SHEET_ID: sheetId,
			SHEET_DATA_PAY_STATUS: 99,
		}
		let noCnt = await SheetDataModel.count(whereNoCnt);

		// 已支付金额
		let wherePayFee = {
			SHEET_DATA_SHEET_ID: sheetId,
			SHEET_DATA_PAY_STATUS: 1,
		}
		let payFee = await SheetDataModel.sum(wherePayFee, 'SHEET_DATA_PAY_FEE');

		let waitCnt = cnt - payCnt - noCnt;
		let waitFee = fee - payFee;
		let data = {
			SHEET_CNT: cnt,
			SHEET_PAY_CNT: payCnt,
			SHEET_WAIT_CNT: waitCnt,
			SHEET_NO_CNT: noCnt,

			SHEET_FEE: fee,
			SHEET_PAY_FEE: payFee,
			SHEET_WAIT_FEE: waitFee
		}
		await SheetModel.edit(sheetId, data);
		return { cnt, payCnt, waitCnt, noCnt, fee, payFee, waitFee };
	}

	// 修正本地订单状态
	async fixSheetDataPay(tradeNo, sheetId) {
		if (!tradeNo) {
			// 无支付号空单
			let data = {
				SHEET_DATA_PAY_STATUS: 0,
				SHEET_DATA_PAY_TRADE_NO: '',
				SHEET_DATA_PAY_FEE: 0,
				SHEET_DATA_PAY_TIME: 0,
			}

			await SheetDataModel.edit({ SHEET_DATA_PAY_TRADE_NO: tradeNo }, data);

			// 重新统计
			this.statSheetData(sheetId);

			return false;
		}

		let payService = new PayService();
		if (!await payService.fixPayResult(tradeNo)) {
			// 关闭未支付单
			payService.closePay(tradeNo);

			// 未支付
			let data = {
				SHEET_DATA_PAY_STATUS: 0,
				SHEET_DATA_PAY_TRADE_NO: '',
				SHEET_DATA_PAY_FEE: 0,
				SHEET_DATA_PAY_TIME: 0,
			}

			await SheetDataModel.edit({ SHEET_DATA_PAY_TRADE_NO: tradeNo }, data);

			// 重新统计
			this.statSheetData(sheetId);

			return false;
		}

		// 已支付
		let pay = await PayModel.getOne({ PAY_TRADE_NO: tradeNo });
		if (!pay) this.AppError('支付流水异常,请核查');

		// 更新支付信息
		let data = {
			SHEET_DATA_PAY_STATUS: 1,
			SHEET_DATA_PAY_TRADE_NO: tradeNo,
			SHEET_DATA_PAY_FEE: pay.PAY_TOTAL_FEE,
			SHEET_DATA_PAY_TIME: pay.PAY_END_TIME,
		}
		await SheetDataModel.edit({ SHEET_DATA_PAY_TRADE_NO: tradeNo }, data);


		// 重新统计
		this.statSheetData(sheetId);
		return true;
	}


	// 预支付
	async prepay(userId, sheetDataId) {

		this.AppError('[物业缴费]该功能暂不开放,如有需要请加作者微信:cclinux0730');
	}

	// 查询支付结果
	async queryPayResult(sheetDataId) {
		let sheetData = await SheetDataModel.getOne(sheetDataId);
		if (!sheetData) return { status: 0 };

		return {
			status: sheetData.SHEET_DATA_PAY_STATUS
		}
	}

住户端 UI设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

管理端UI设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码

git源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值