涉及定期付款和未来事件的困难架构问题
我正在寻找关于如何构建一个优雅的解决方案来解决这个有点棘手的问题的指导。虽然我使用 Ruby(和 Rails),但我认为我的问题很大程度上是一个架构问题,尽管我选择的语言显然会对涉及库等的建议产生影响,因此该语言仍然具有相关性。
无论如何,简而言之:我的应用程序包含代表会员资格的对象,属于健身设施的会员。会员资格包含一系列定期付款。有些会员资格在任期结束时自动续订,而另一些则不会。
例如,您的会员资格初始期限可能为一年,然后按月续订。在该应用程序中,创建此类会员资格会导致创建 12 笔定期付款。当最后一个月到期时,会员资格也随之到期。每日 cron 任务负责根据已完成的付款导致会员资格到期。如果成员资格设置为自动更新,则相同的 cron 任务将更新成员资格。
您的会员资格也可能没有初始期限,只是按月或按周运行。这些工作方式类似,只是减去了初始付款安排。
到目前为止,一切都很好。使事情变得复杂的是额外的要求:
管理员可以在特定的时间内“冻结”会员资格(将其搁置),之后它们会自动重新激活(例如,代表在一段时间内休假的人) )。我可以选择立即冻结会员资格并稍后重新激活,或者我可以选择通过在未来的某个时间设置冻结日期以及重新激活日期来安排冻结(注意:总是有 重新激活日期,这使事情变得更容易一些)。
管理员可以立即取消会员资格,也可以设置将来取消会员资格。 (未来的取消功能尚未建立。)
管理员可以退还会员资格,这与取消会员资格类似,只是退还所有过去的付款
使这些问题难以处理的是对定期付款的影响。当您冻结会员资格时,定期付款必须在冻结期前后“延长”,这样代表冻结的时间段就不会被支付。这在概念上和程序上都很难处理。例如,付款可以延长不同的期限(即,每隔一周付款的人的每笔付款可以支付两周的会员资格),并且取消日期可以是付款涵盖期间内的任何日期。
对于冻结,我采取了成员资格对象包含一些日期的方法,即“freeze_on”和“thaw_on”来处理冻结期。然而,客户现在也希望将来取消,而且我注意到冻结功能存在一些错误,这让我相信我需要重新考虑我的方法。
我正在考虑进行更改,以便可以安排未来的事件,但不会影响应用程序的定期付款部分。这个想法是将特定事件排队。例如,未来的冻结将通过在特定日期排队冻结事件和随后日期的解冻事件来完成(从用户的角度来看,这两个事件将连接到单个“计划冻结”)。未来的取消也会以类似方式处理。
这种方法有一些好处,例如,如果您想取消将来的取消(这就是我所说的那种烦人、棘手的事情),您可以简单地从事件队列中删除预定的取消。
然而,我总有一种挥之不去的感觉,我可能只是从油锅里跳进了火里。我想知道是否有人可以就这个问题为我提供一些指导。对于此类问题,是否有可供我检查的设计模式或现有架构原则?
另外需要注意的是,具有预定期限的会员资格的定期付款(即不是每月自动续订)必须作为可以编辑(及时移动、价格调整)的数据库记录存在,因此使用时间表达式(如 Martin据我所知,福勒建议)不适合这个问题。我意识到我提出的事件队列解决方案不会向用户显示任何现有定期付款可能发生的更改,但我认为我可以接受这一点。
不是 scanlife 条形码,它是
多伦多的二维码,请给我们您的创意人员
编辑:回应下面的两个很好的建议(评论框不允许如此详细的信息):
< strong>Kris Robison:
是的,冻结期可以是任意长度,尽管实际上我认为少于两周的情况很少见。但任何解决方案都应该有效,无论期限长短。
是的,续订日期发生变化 - 它会根据冻结时间的长短而提前。因此,如果冻结时间长达两周,付款就会提前两周。更棘手的是,在某些企业中,付款只能在特定日期提取 - 例如,某些俱乐部仅在每月 1 日和 15 日处理付款。因此,当日期被推迟时,对于这些俱乐部来说,他们必须“调整”到特定日期。
您能否更详细地解释为什么这些规则影响事件排队而不影响订阅付款的管理?
我对你的摊销表概念感兴趣。这基本上正是我已经建立的——每月付款的为期一年的会员资格创建了 12 个,每周创建了 52 个——每一个都有与之相关的金额、税收等,以及一个管理的状态机“待处理”、“已付款”、“失败”和“已退款”状态。
我遇到的问题是这张表如何响应事件。现在,如果您设置冻结,它会通过更改付款日期立即影响表格。在表格中间设置冻结,可以推迟付款。这听起来很有效,但实际上相当复杂且难以管理。您的摊销表想法将如何改善这种情况?
Arsen7:
这听起来像是我最初提出的事件队列。对我来说,很明显,您以前曾处理过类似的事情(您对处理日期的错误检查给我留下了深刻的印象,这是一个好主意,我打算尽快实施),所以我希望您能解释一下你的建议更详细一些。
具体来说,我想知道您的概念将如何处理我在最初的问题中以及我刚刚对克里斯·罗宾逊的答案留下的评论中描述的定期付款情况。如果我为特定购买设置了定期付款时间表,并且在付款中间安排了冻结事件,那么付款时间表是否会保持不变,直到冻结日期成为当前日期(此时)何时开始冻结并提前付款?
我觉得这可能是简化应用程序的好方法,但我想知道用户会如何看待它。我如何向他们表明,他们在安排冻结时查看的付款时间表不再是准确的时间表,而是在冻结发生后会发生变化?
I'm looking for guidance on how to architect an elegant solution to what has become a bit of a thorny problem. Although I am using Ruby (and Rails) I think my problem is largely an architectural one, though my choice of language obviously has an impact in terms of suggestions involving libraries, etc., so the language remains relevant.
Anyway, in a nutshell: my application contains objects representing memberships, belonging to people who are members of fitness facilities. Memberships contain a series of recurring payments. Some memberships automatically renew at the end of their term, while others do not.
So for example, you may have a membership that is for an initial period of one year, and then renews month-to-month after that. In the application, creating a membership of this kind causes 12 recurring payments to be created. When the last month expires, so does the membership. A daily cron task is responsible for causing memberships to expire based on completed payments. If the membership is set to automatically renew, the same cron task will renew the membership.
You may also have memberships that have no initial term and simply run month-to-month or week-to-week. These work in a similar manner, minus the initial payment scheduling.
So far so good. What makes things complicated are the additional requirements:
administrators can "freeze" memberships (put them on hold), for specific durations, after which they automatically reactivate (e.g. to represent people who go away on vacation for a set period of time). I can choose to freeze a membership right now and have it reactivate later, or I can choose to schedule a freeze by setting the freeze date at some point in the future, as well as the reactivation date (note: there is always a reactivation date, which makes things a bit easier).
administrators can cancel memberships, either right now, or by setting a cancellation to occur in the future. (Future cancellations are not yet built.)
administrators can refund memberships, which is like a cancellation except any past payments are refunded.
What makes these difficult to deal with is the effect on recurring payments. When you freeze a membership, the recurring payments must "stretch out" around the freeze period, so that the period of time that represents the freeze is not paid for. This is both conceptually and programmatically difficult to handle. Payments, for example, may extend for different periods (i.e. each payment for someone who pays every other week pays for two weeks of a membership), and the date of cancellation may be anywhere within the period the payment covers.
For the freezes, I have taken the approach where the membership object contains some dates, namely "freeze_on" and "thaw_on" to handle the freeze period. However, the client now wants future cancellations as well, and I have noticed some bugs with the freezing functionality, which leads me to believe I need to reconsider my approach.
I am considering changing things so that future events can be scheduled but have no effect on the recurring payments portion of the application. The idea would be to queue up particular events. For example, a freeze in the future would be accomplished by queuing up a freeze event on a particular date, and a thaw event on a subsequent date (these two events would be connected into a single "scheduled freeze" from the user's perspective). A future cancellation would be handled similarly.
This approach has some benefits, for example, if you wanted to cancel a future cancellation (that's the kind of annoying, tricky stuff I'm talking about), you could simply remove the scheduled cancellation from the events queue.
However, I have the nagging feeling that I may simply be jumping from the frying pan into the fire. I'm wondering if anyone could provide me with some guidance on this issue. Are there design patterns or existing architectural principles for this sort of problem that I can examine?
An additional thing to note is that recurring payments for memberships with scheduled terms (i.e. not month-to-month automatically renewing) must exist as database records that can be edited (moved in time, price adjusted), so using temporal expressions (as Martin Fowler suggests) is not appropriate for this problem, so far as I know. I realize that my proposed solution of an events queue would not display to the user the changes that would happen to any existing recurring payments, but I think I can live with that.
not a scanlife bar code, it's a qr code
toronto, give us your creative people
Edit: To respond to the two great suggestions below (the comment boxes don't allow nearly this level of detail):
Kris Robison:
Yes, the freeze period can be an arbitrary length, although in practice I imagine it would be rare for it to be less than two weeks. But any solution should work regardless of the length of the period.
Yes, the renewal date changes - it is pushed forward by the length of the freeze. So if the freeze is two weeks long, it pushes the payment forward by two weeks. To make things especially tricky, in some businesses, the payments can only be withdrawn on specific dates - for example, some clubs only process payments on the 1st and 15th of each month. So when dates are pushed around, for these clubs, they have to "snap" to a particular date.
Can you explain in more detail why these rules affect event queuing but not management of subscription payments?
I'm interested in your amortization table concept. That's basically exactly what I have built already - a year-long membership with monthly payments creates 12, with weekly it created 52 - and each of these have an amount, tax, etc., associated with them, along with a state machine that governs "pending", "paid", "failed", and "refunded" states.
The part I am struggling with is how this table responds to events. Right now, if you set a freeze, it affects the table immediately by changing the dates of the payments. Set a freeze in the middle of the table, and it pushes payments forward. That sounds effective, but it's actually quite complex and hard to manage. How would your amortization table idea improve this situation?
Arsen7:
This sounds like the event queue I proposed originally. It seems obvious to me that you've worked with stuff like this before (I was impressed by your error check on the processing date, this is a great idea and one I intend to implement ASAP) so I'm hoping that you can explain your suggestion in a little more detail.
Specifically, I'm wondering how your concept would deal with the recurring payment situation I've described in my original question, and in the comment that I just left on Kris Robison's answer. If I have set up a schedule of recurring payments for a given purchase, and a freeze event is scheduled for right in the middle of the payments, would the schedule of payments remain unchanged until the date of the freeze became the current date, at which time the freeze would be instituted and the payments would move forward?
This strikes me as perhaps a great way to simplify my application, but I am wondering how users would perceive it. How would I indicate to them that the schedule of payments they were looking at when a freeze has been scheduled is no longer an accurate schedule, but will change once the freeze takes place?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
是否可以采用银行使用的方案,即每天处理一次所有帐户操作?
每个对象都可能有一组(未来的)操作,例如冻结期,并且每天该对象都必须做出一个简单的决定,例如:“我今天是否应该过期?”
好的方面是,这样的日常处理非常容易编程。另外,一个奇怪的续订规则(如果您需要它们)设计起来很简单:“是星期五吗?这是本月的最后一个吗?如果是,请将我标记为续订,在所需付款中添加一些金额,或执行任何操作”。
每次询问对象时动态计算状态的成本非常高(就计算能力而言)。如果你存储当前的“账户”,当你想要预测未来的状态时,你只需要更复杂的计算。
将其视为伪代码:
响应:
“每日处理”方案可帮助您快速回答需要复杂计算的问题。
您有三个“组”:当前状态(经常询问)、历史(几乎从未改变,询问相对较少)和未来。
您的日常处理过程不仅限于更新“当前”状态。如果在处理的当天安排了某些事件,则该过程可能需要添加“历史”记录。
如果您的用户经常询问有关未来的问题(正如您所说,他们会的),“处理”也可能会为这些问题创建一种缓存。例如:找到(计算)下一个付款日期,并将其写入辅助表(时间表)中。
您需要做的主要事情是决定用户将提出哪些问题,以及您是否能够即时计算答案还是需要准备答案。
在银行(当然,情况有所不同),如果您询问当前余额,他们可能会给出当天开始时的答案。 “更好”的银行会告诉你,早上你有X$,但现在还有Y$等待记账。
因此,如果您将冻结记录放入事件队列中,您可以调用一个方法,该方法将立即更新时间表。相同的过程(或非常相似的过程)将或可能在日常处理例程中被调用。
Is it acceptable to apply a scheme used by banking, where you process all account operations once a day?
Every object may have a set of (future) operations, like freeze periods, and every day the object has to make a simple decision, like: "should I expire today or not?"
The good part is, that such daily processing is very simple to program. Also a strange renewal rules (in case you would want them) are simple to design: "is it Friday? is it the last one in this month? If yes, mark me as renewed, add some amount to required payment, or do anything".
That would be very costly (in terms of computing power) to calculate the status dynamically, every time the object is asked. If you store the current "account", you only need more complex calculations when you want to predict future state.
Consider it a pseudo-code:
RESPONSE:
The "daily processing" scheme helps you with providing quick responses for questions which require complex calculations.
You have three "groups": current state (asked often), history (almost never changing, asked relatively rarely), and future.
Your daily processing procedure is not constrained to only update "current" state. If some event is scheduled for the processed day, the procedure probably needs to add "history" records.
If your users will often ask questions about future (and as you said, they will), the "processing" may also create a kind of cache for these questions. For example: find (calculate) the next payment date, and write it in a helper table (a schedule).
The main thing you need to do is to decide which questions will be asked by users, and whether you are able to calculate the responses on-the-fly or you need to have the answer prepared.
In a bank (it varies, of course) if you ask about your current balance, they may give you the answer which was true at the beginning of the day. "Better" banks will tell you that you had X$ at the morning, but now there are also Y$ waiting for accounting.
So, if you put a freeze record into the event queue, you may call a method, which will update the schedule at once. The same procedure (or a very similar) will or may be called in the daily processing routine.
我想到了几个问题:
这些会影响您的事件排队系统的工作方式,但最终不会改变订阅付款的管理。
解决订阅问题的一种方法是创建订阅付款摊销表。如果他们按月付款一年,则有十二笔付款会在表中排队。每周客户同一年的表中可能有 52 笔付款。每次续订日期到来时,检查冻结是否到位后,申请下一次付款。
摊销表记录了已支付的款项。如果帐户取消,未付款行将被退还。如果帐户根据您的事件队列冻结,则不会应用任何付款,并且表将保持静态,直到帐户解冻。
回复
听起来您在摊销表中内置了续订日期的概念。我更多地使用摊销表作为队列,并仅保留订阅的下一个续订日期。
如果续订日期是摊销表固有的,那么随着事情的进展进行更改会很复杂。但是,续订日期应该只影响您检查是否应用了另一笔付款的那一天。
如果您在订阅暂停时保留部分付款,并且暂停可以持续一段未指定的时间,则摊销表上的持续时间值可以让您将部分付款以“信用”状态推回到队列中持续时间等于付款中剩余的时间。这样,当帐户解冻时,将首先应用部分信用,然后您可以根据剩余期限计算下一个续订日期。
此时使用某种形式的有序列表来保存付款顺序。如果有人出于客户服务原因想要插入续订期的信用额度,它也会派上用场。
Couple of questions come to mind:
Those affect how your event queuing system may work, but don't ultimately change the management of subscription payments.
One way to address subscription issues is to create an amortization table of subscription payments. If they are paying monthly for a year, twelve payments are queued up in the table. A weekly customer may have 52 payments in the table for the same year. Each time the renewal date comes up, after checking whether a freeze is in place, apply the next payment.
The amortization table keeps track of which payments have been made. If an account cancels, unpaid rows are refunded. If an account freezes according to your event queue, no payment is applied and the table remains static until the account is thawed.
Reply
It sounds like you have the concept of renewal date built into the amortization table. I use the amortization table more as a queue, and keep just the next renewal date with the subscription.
If the renewal date is inherent with the amortization table, then yes, it would be complicated to make changes as things go along. However, the renewal date should only affect the day on which you check to see if another payment gets applied.
If you are preserving partial payments while a subscription is on hold, and if a hold can be for an unspecified period of time, having the duration value on the amortization table lets you push a partial payment back in the queue in a "credit" state with a duration equal to the remaining time left in the payment. That way when the account is thawed, that partial credit is applied first and you calculate the next renewal date from the remaining duration.
Use some form of ordered list to preserve payment order at this point. It also comes in handy should someone ever want to insert a renewal period's worth of credit for customer service reasons.