Summary
Billing, login, and receiving contact messages: I handed all of them off to outside services. For membership and billing, the only things I keep in my own database are a mapping of who is a member of which plan, and a copy of the subscription and purchase state. I keep that copy because if the site went to the payment service to check the subscription state every time a reader opens a paid article, the display would get slow. When you develop together with AI, the implementation itself can be pushed as far as you like. So the first thing to decide was not what I could build, but what I would not hold myself.
Audience and takeaways
This article is for someone trying to build a service with billing on their own while having an AI agent write much of the code. You can take away how to decide how much personal data to hold yourself and where to start handing it off to outside services, and why deciding that standard up front pays off. It also gives concrete examples: what rule I used to deal with the AI's habit of confidently writing outdated specifications, and how I split the work so that the AI handles the research into terms and prices while a person makes the decisions that put limits on cost and permissions. This article is a record of practice, based on the process of building this site itself together with AI.
The gist of what happened
Over about two weeks, working with an AI agent, I worked out what to entrust to outside services and what not to hold. I withdrew the mechanism for storing the body of contact messages in my own database after I had already verified that it worked, and I moved the site's form over to an outside form service instead. I hand off card entry for payment entirely to the payment page that the payment service displays on its own side, so my site never handles card details or passwords at all. The authoritative record of membership and billing lives on the payment service's side, and my side holds only the mapping and the copy. Along with this, I built in two rules. First, I verify any design the AI writes against the body text of the current official documentation. Second, a person sets an upper limit on costs in advance, uptime monitoring included.
Before deciding what not to hold, see the whole of what has to be done
The judgment of whether to build something yourself, entrust it to an outside service, or not hold it at all is itself a demanding one, and doing it well needs a further step of thought beforehand. To decide what to entrust and what not to hold, you have to see the whole of what has to be done: billing, authentication, legal matters, monitoring, performance, and security. If you decide without seeing the whole, you might end up holding data you should have entrusted, or conversely throw responsibilities you should guard yourself, like billing and permissions, entirely over to an outside service.
Grasping this whole on my own, without gaps, was hard. So I had the AI enumerate the list of what has to be done, and I checked that list for anything missing through a number of reviews from separate viewpoints. I asked the AI for a security audit of code and configuration and an audit of display speed as separate tasks, had it check for gaps in release preparation and the free-tier limits of outside services from yet another angle, laid out one by one the actions a reader actually takes in order to look for gaps, and for terms and legal matters I went through everything against the original text of the law and of each service's terms. Any single viewpoint's enumeration always misses something somewhere, so I layer viewpoints to fill the gaps. On top of that, I narrowed what a person checks with their own eyes down to the areas where a slip is unrecoverable: money, permissions, personal data, the weighing of legal matters, and the final judgment.
Decide up front, as a standard, what you will not hold
With AI, implementation can in practice be pushed as far as you like. The processing to receive contact messages, the database table to store them, the verification that it works, all come back in a short time once you ask. At that point whether you can build it drops out of the material for judgment, and only whether you should hold it remains. So I decided that a person would settle the standard for what not to hold up front.
The standard is a single one: keep what I hold to a minimum. When I feel the urge to build a feature that stores personal data, I first look for an outside place to entrust it to. I narrowed what I hold myself down to only the things that do not work unless the authoritative record is on my side, like billing, authentication, and permissions.
Handing something to the outside is not the same as being safe. Even so, there are two reasons handing off is better than holding. First, the damage when there is a leak is decided by what you hold. Since you cannot stand on the premise of fully preventing every vulnerability, the strongest defense is that what you do not hold cannot leak. Second, for data like card details and the identity check for login, there are specialist businesses whose very trade is guarding it. It is not realistic for a lone developer to keep up the same level of defensive setup and investment. So I choose the place to entrust to by reading its terms and its data handling down to the original text and confirming them. Even after handing off, the work of guarding the seam with the place I entrusted to remains mine. That work means two things: keeping the table that maps members to their records on the payment service accurate, and confirming that a notification arriving from the payment service is genuine.
What made me decide on this standard was withdrawing the contact feature. The AI had implemented the receiving process and the storage table, and had finished verifying that it worked, and I sent that mechanism back for two reasons. One was to make the damage from a leak as small as possible. If I do not hold the message body and the reply address myself and instead entrust them to an outside form service, then even if my own database leaks, no contact messages are in it. The other is that the database capacity I can use is also limited. I wrote this judgment down as a rule, so from now on I can stop before building.
Card details and passwords are the same. I leave login confirmation to authentication where you follow a link in an email, and I pass payment straight to the payment page that the payment service displays on its own side. So a card number never passes through my site. This is not only to keep the damage small; it is also because the free terms of the delivery service forbid handling card details on a free site at all.
The authoritative record of membership and billing I also do not hold myself. Who subscribes to which plan, and whether payment is currently valid, is known correctly only by the payment service, where the money actually moves. If I hold the same ledger in my own database too, there come to be two authoritative records, and dropping a single payment notification is enough to make them disagree, which raises the problem of which one to believe. So I settled the authoritative record on the single one on the payment service's side, and in my own database I placed only a mapping that points to it and a copy of the subscription and purchase state. The copy is a local duplicate, so that I do not keep readers waiting by querying the payment service every time they open a paid article. If it breaks, it comes back by pulling from the payment service's record again. For writes tied to an individual, like likes and favorites, I made login the minimum condition, and I narrowed what may be received anonymously down to counts not tied to an individual, like an article's view count.
Verify the outdated specs the AI confidently writes against the current official documentation
The AI writes outdated specs from its training data with confidence, and plausibly at that. So you are apt to let them through as they are. I stumbled twice around payments. The first was invoice.payment_intent, a field that points directly from an invoice to its payment, which the AI was using straight from memory. In a spring 2025 update the payment service had removed this field and changed to representing the correspondence between an invoice and its payments as an array called invoice.payments.
The second was a harder-to-spot form. A reservation to cancel a subscription at the end of the period, under the old spec, arrived as a boolean field called cancel_at_period_end, read as: if true, cancel at the end of the period. In the current spec this field has been deprecated, and it now arrives as cancel_at, the time at which the cancellation takes effect. The AI had written code that read cancel_at_period_end from its old knowledge, so it became a defect where a cancellation a reader had scheduled was not recorded on their account. The automated tests did not catch this, and I found it only when I actually went through a cancellation in the test environment. A change of spec happens not only in whether a field exists, but in the very meaning of what arrives.
There was also one that nearly became a third. In a mechanism to prevent the same person from holding two subscriptions, the AI, before checking the existing settings, started to assemble a workaround that did the cancellation and refund itself. When I read the official documentation, I found that a setting to limit a customer to a single subscription had been there from the start, so I rebuilt it to use that. From these three experiences I set a rule: before using an outside service, actually read the current official documentation, and confirm field names and setting locations from that body text rather than from memory before writing.
After I set the rule, this way of reading paid off on the verification side too. There was a time when the auditing AI recommended, in order to hide the database's real address, a custom domain that would put the delivery service's protection in front of it. But confirming against the official documentation, I found that this custom domain, for reasons of transport encryption, can only be set up in a way that cannot pass through the delivery service's protection, so it does not hold up as a front-line defense. The core of protecting the data is not front-line network protection to begin with; it is row-level security, where the database itself judges per row who may read and write, and the permission checks on the application side. For this part I created real accounts with different roles and put them through integration tests. In place of the custom domain I assembled three things: an upper limit on monthly spending, an upper limit on how many times the server-side functions can be called, and uptime monitoring. They sit outside the core of the defense and keep usage from running away. To see whether the same kind of mistake, a place reading an outdated spec from memory, was anywhere else, I had the AI cross-check every place that exchanges with an outside service, and confirmed that the shape of the arriving data did not diverge from the current spec.
Leave the research to AI, and let a person put limits on cost and permissions
In how to deal with outside services, the split that worked best was to leave the research of reading terms and prices to the AI, while a person makes the final decisions that put limits on cost and permissions. Where this split showed clearly was in choosing uptime monitoring, which checks at intervals whether the site is working as seen from outside and tells you if it is down.
The AI, reading down to the original text of current prices and terms of use, dug out fourteen services usable commercially, and lined up the ones that fit inside a free tier as its recommendation. Here I chose differently. Rather than being free, I decided to choose something where I could hold the settings for which pages to monitor and how as code, even if it cost money, and among those pick the cheap one.
This standard has a reason that matters more than cheapness. In the development ahead, I will leave even more of the work to AI than before. With a service whose settings you cannot change without a person operating the screen, I cannot leave that operation to AI. With a service you can read and write through an API, both adding monitoring targets and confirming that the monitoring works correctly can be built in as the AI's work. This site is built, after all, toward a form where I just keep doing research and development while both the making of articles and the running of the site keep turning on their own. In that form, being programmable through an API, that is, operable from code, becomes the requirement I place on every outside service I stitch together. So even at somewhat higher cost, I gave priority to what can be operated through an API. What I chose is a metered service that pays a small amount per check. It comes to under a hundred yen a month, and the free allowance given at signup alone lasts more than half a year. The monitoring settings sit in the repository as code, so running them any number of times gives the same result, and there is no accidentally erasing a setting added by hand from a screen.
Here too, a person set the limits in advance. On the use of generative AI, an upper limit on monthly spending is placed across the whole organization. On the conversation feature, I layered three further limits, how much one member can send in a day, how much one member can send in a month, and how much all members together can use in a month, and I also put a limit on the length of text handed to the AI in a single conversation. When any of these is exceeded, the request is refused rather than processed, and the usage allowance reserved earlier for that member is returned.
For the generative AI used in conversation, too, I chose the paid API rather than the free tier. Reading the provider's terms, on the free tier the text a user sends and the generated answer may be used by the provider to improve its product, and in some cases may be reviewed by human eyes. On the paid API it was stated explicitly that they are not used to improve the product. To keep the body of conversations members write from being handed to the provider's training or review, here I decided to pay.
I also split the degree of strictness by purpose. Billing, authentication, and permissions I build strictly, while rough measures of use like likes and view counts I build with approximate aggregation and sampling, without bringing in needless complexity and cost. This too is one of the limits a person set in advance.
Not what can be built, but what can be entrusted
Even just choosing one uptime monitor, there was a back-and-forth: the AI combed through the terms, I chose differently by my standard, and the AI built it, ran it, and verified it. Looking back, I built the contact feature, then withdrew it and entrusted it to the outside. For uptime monitoring I use an outside service. Likes I keep on my own side, but only for members. And the custom domain recommendation I did not adopt. Every one is the same three-way judgment: build it yourself, entrust it to the outside, or not hold it at all. Each of them redrew the line of what to entrust and what to hold.
In today's development, where the amount of implementation is no longer the constraint, what I should have decided first was not what to build, but what not to hold myself, or in other words, what to entrust to the outside. Once you decide up front not to hold something, the worry about a leak and the trouble of moving it out when you shut things down are gone from the start. If a person places the standard first and builds in verification of the current spec and limits on cost, then no matter how much volume you have the AI produce, the design does not break down. Before counting what can be built, decide what can be entrusted. What can be drawn here is only the line on the entrusting side; whether the code I wrote myself is correct needs a different kind of defense. I will write that back half of the story in the next installment.
Originally published at The Future of Humans, AI, and the Web, a site where my research and development is recorded and analyzed by a human and an AI.
Top comments (0)