Skip to main content

max / makenotwork

Give the provider trait an account id it can legally return create_connect_account returned crate::db::StripeAccountId, whose validator requires Stripe's acct_ prefix, so a non-Stripe implementor had no legal value to hand back. The trait deals in an opaque ProviderAccountId now; the acct_ check runs at the single caller, where the value meets the Stripe-shaped column.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-24 19:23 UTC
Signed with PGP, not checked
Commit: 4adef9c9419965ea6255848703d88285f8c6e845
Parent: 337d8b6
3 files changed, +44 insertions, -13 deletions
@@ -43,6 +43,39 @@
43 43 /// the request strategy still retries a timed-out attempt where permitted.
44 44 const STRIPE_HTTP_TIMEOUT: Duration = Duration::from_secs(30);
45 45
46 + /// A connected-account id as a payment provider minted it.
47 + ///
48 + /// The `PaymentProvider` trait deals in this rather than in
49 + /// [`crate::db::StripeAccountId`], which carries Stripe's `acct_` shape in its
50 + /// validator: a non-Stripe provider has no legal value to return there. The
51 + /// wrapper is deliberately opaque, holding whatever the provider handed back
52 + /// with no format claim of its own. Vendor validation happens where the value
53 + /// meets a vendor-shaped column, at the call site.
54 + #[derive(Clone, Debug, PartialEq, Eq)]
55 + pub struct ProviderAccountId(String);
56 +
57 + impl ProviderAccountId {
58 + /// Wrap what a provider returned. No validation: the provider minted it.
59 + pub fn from_provider(id: String) -> Self {
60 + Self(id)
61 + }
62 +
63 + pub fn as_str(&self) -> &str {
64 + &self.0
65 + }
66 +
67 + /// Consume the wrapper, returning the inner `String`.
68 + pub fn into_inner(self) -> String {
69 + self.0
70 + }
71 + }
72 +
73 + impl std::fmt::Display for ProviderAccountId {
74 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 + self.0.fmt(f)
76 + }
77 + }
78 +
46 79 /// Stripe client wrapper for payment operations
47 80 #[derive(Clone)]
48 81 pub struct StripeClient {
@@ -167,10 +200,7 @@
167 200 ) -> crate::error::Result<CheckoutResult>;
168 201
169 202 // Connect
170 - async fn create_connect_account(
171 - &self,
172 - email: &str,
173 - ) -> crate::error::Result<crate::db::StripeAccountId>;
203 + async fn create_connect_account(&self, email: &str) -> crate::error::Result<ProviderAccountId>;
174 204 async fn create_account_link(
175 205 &self,
176 206 account_id: &str,
@@ -521,7 +551,7 @@
521 551 async fn create_connect_account(
522 552 &self,
523 553 _email: &str,
524 - ) -> crate::error::Result<crate::db::StripeAccountId> {
554 + ) -> crate::error::Result<ProviderAccountId> {
525 555 ScriptedProvider::create_connect_account(self)
526 556 }
527 557 async fn create_account_link(
@@ -771,11 +801,9 @@
771 801 })
772 802 }
773 803
774 - async fn create_connect_account(
775 - &self,
776 - email: &str,
777 - ) -> crate::error::Result<crate::db::StripeAccountId> {
778 - StripeClient::create_connect_account(self, email).await
804 + async fn create_connect_account(&self, email: &str) -> crate::error::Result<ProviderAccountId> {
805 + let account = StripeClient::create_connect_account(self, email).await?;
806 + Ok(ProviderAccountId::from_provider(account.into_inner()))
779 807 }
780 808
781 809 async fn create_account_link(
@@ -249,9 +249,9 @@
249 249 async fn create_connect_account(
250 250 &self,
251 251 _email: &str,
252 - ) -> Result<makenotwork::db::StripeAccountId> {
252 + ) -> Result<makenotwork::payments::ProviderAccountId> {
253 253 self.faults.check("create_connect_account")?;
254 - Ok(makenotwork::db::StripeAccountId::from_trusted(
254 + Ok(makenotwork::payments::ProviderAccountId::from_provider(
255 255 "acct_test_mock".to_string(),
256 256 ))
257 257 }
@@ -56,7 +56,10 @@
56 56 {
57 57 acct_id
58 58 } else {
59 - let acct_id = stripe.create_connect_account(&user.email).await?;
59 + // The provider hands back an opaque id; the column is Stripe-shaped, so
60 + // the `acct_` check runs here, where the two meet.
61 + let acct_id =
62 + db::StripeAccountId::new(stripe.create_connect_account(&user.email).await?.as_str())?;
60 63 tracing::info!(user_id = %user.id, stripe_account_id = %acct_id, "created stripe connected account");
61 64
62 65 // Atomically claim the stripe_account_id slot. The WHERE clause