| 205 |
205 |
|
|
| 206 |
206 |
|
// Connect
|
| 207 |
207 |
|
async fn create_connect_account(&self, email: &str) -> crate::error::Result<ProviderAccountId>;
|
| 208 |
|
- |
async fn create_account_link(
|
| 209 |
|
- |
&self,
|
| 210 |
|
- |
account_id: &str,
|
| 211 |
|
- |
return_url: &str,
|
| 212 |
|
- |
refresh_url: &str,
|
| 213 |
|
- |
) -> crate::error::Result<String>;
|
| 214 |
|
- |
async fn fetch_account(&self, account_id: &str) -> crate::error::Result<AccountUpdate>;
|
| 215 |
|
- |
async fn create_subscription_product_and_price(
|
| 216 |
|
- |
&self,
|
| 217 |
|
- |
connected_account_id: &str,
|
| 218 |
|
- |
tier_name: &str,
|
| 219 |
|
- |
tier_description: Option<&str>,
|
| 220 |
|
- |
price_cents: i64,
|
| 221 |
|
- |
currency: crate::currency::SettlementCurrency,
|
| 222 |
|
- |
) -> crate::error::Result<(String, String)>;
|
| 223 |
208 |
|
/// Balance in the account's own settlement currency. A connected account can
|
| 224 |
209 |
|
/// hold several currencies at once; summing across them would be adding
|
| 225 |
210 |
|
/// pounds to euros.
|
| 260 |
245 |
|
stripe_sub_id: &str,
|
| 261 |
246 |
|
cancel: bool,
|
| 262 |
247 |
|
) -> crate::error::Result<()>;
|
| 263 |
|
- |
/// Create a Stripe-hosted billing portal session. Returns the URL to redirect to.
|
| 264 |
|
- |
async fn create_billing_portal_session(
|
| 265 |
|
- |
&self,
|
| 266 |
|
- |
stripe_customer_id: &str,
|
| 267 |
|
- |
return_url: &str,
|
| 268 |
|
- |
) -> crate::error::Result<String>;
|
| 269 |
|
- |
|
| 270 |
|
- |
// Refunds, line-scoped: refunds `amount_cents` of the shared PaymentIntent
|
| 271 |
|
- |
// and tags the refund with the transaction id so the refund.created webhook
|
| 272 |
|
- |
// marks/revokes exactly that line (cart orders share one PaymentIntent).
|
| 273 |
|
- |
async fn create_refund_for_transaction(
|
| 274 |
|
- |
&self,
|
| 275 |
|
- |
payment_intent_id: &str,
|
| 276 |
|
- |
connected_account_id: &str,
|
| 277 |
|
- |
amount_cents: i64,
|
| 278 |
|
- |
transaction_id: crate::db::TransactionId,
|
| 279 |
|
- |
) -> crate::error::Result<()>;
|
| 280 |
|
- |
|
| 281 |
|
- |
// Platform-funded credit reimbursement, a platform -> connected transfer that
|
| 282 |
|
- |
// makes the creator whole for a Fan+ credit applied to their sale (MNW funds it).
|
| 283 |
|
- |
// Deterministic idempotency key keeps replays/retries from double-paying.
|
| 284 |
|
- |
// Returns the transfer id so it can be reversed if the sale is refunded.
|
| 285 |
|
- |
async fn create_platform_credit_transfer(
|
| 286 |
|
- |
&self,
|
| 287 |
|
- |
connected_account_id: &str,
|
| 288 |
|
- |
amount_cents: i64,
|
| 289 |
|
- |
transaction_id: crate::db::TransactionId,
|
| 290 |
|
- |
currency: crate::currency::SettlementCurrency,
|
| 291 |
|
- |
) -> crate::error::Result<String>;
|
| 292 |
|
- |
|
| 293 |
|
- |
// Reverse a settled platform-credit transfer when its sale is refunded,
|
| 294 |
|
- |
// clawing the reimbursement back from the connected account to MNW.
|
| 295 |
|
- |
// Deterministic idempotency key keeps replays/retries from clawing back twice.
|
| 296 |
|
- |
async fn create_platform_credit_reversal(
|
| 297 |
|
- |
&self,
|
| 298 |
|
- |
transfer_id: &str,
|
| 299 |
|
- |
amount_cents: i64,
|
| 300 |
|
- |
transaction_id: crate::db::TransactionId,
|
| 301 |
|
- |
) -> crate::error::Result<()>;
|
| 302 |
248 |
|
|
| 303 |
249 |
|
// Webhooks
|
| 304 |
250 |
|
fn verify_webhook(&self, payload: &str, signature: &str) -> crate::error::Result<UntypedEvent>;
|
| 308 |
254 |
|
signature: &str,
|
| 309 |
255 |
|
) -> crate::error::Result<serde_json::Value>;
|
| 310 |
256 |
|
|
| 311 |
|
- |
// SyncKit v2 developer billing, one customer + subscription per app,
|
| 312 |
|
- |
// separate from creator-tier and Fan+ subscriptions. See
|
| 313 |
|
- |
// `synckit_billing.rs` for the rationale on per-app customers.
|
| 314 |
|
- |
async fn create_synckit_customer(
|
| 315 |
|
- |
&self,
|
| 316 |
|
- |
developer_user_id: crate::db::UserId,
|
| 317 |
|
- |
app_id: crate::db::SyncAppId,
|
| 318 |
|
- |
email: &str,
|
| 319 |
|
- |
app_name: &str,
|
| 320 |
|
- |
) -> crate::error::Result<String>;
|
| 321 |
|
- |
async fn create_synckit_subscription(
|
| 322 |
|
- |
&self,
|
| 323 |
|
- |
customer_id: &str,
|
| 324 |
|
- |
app_id: crate::db::SyncAppId,
|
| 325 |
|
- |
app_name: &str,
|
| 326 |
|
- |
price_cents: i64,
|
| 327 |
|
- |
) -> crate::error::Result<SynckitSubResult>;
|
|
257 |
+ |
// SyncKit subscription re-pricing and cancellation. Creating the customer
|
|
258 |
+ |
// and the subscription needs [`CustodialCustomers`]; changing the price of
|
|
259 |
+ |
// one that already exists is an ordinary subscription edit, so it stays
|
|
260 |
+ |
// here.
|
| 328 |
261 |
|
async fn update_synckit_subscription_price(
|
| 329 |
262 |
|
&self,
|
| 330 |
263 |
|
subscription_id: &str,
|
| 341 |
274 |
|
product_name: &str,
|
| 342 |
275 |
|
) -> crate::error::Result<()>;
|
| 343 |
276 |
|
async fn cancel_synckit_subscription(&self, subscription_id: &str) -> crate::error::Result<()>;
|
|
277 |
+ |
}
|
|
278 |
+ |
|
|
279 |
+ |
// ── Capability extensions ─────────────────────────────────────────────────
|
|
280 |
+ |
//
|
|
281 |
+ |
// Split out of `PaymentProvider` on 2026-08-28 (`9a452f48`, option (a)). Each
|
|
282 |
+ |
// one is a capability a rail can genuinely lack, and the grouping is by the
|
|
283 |
+ |
// *reason* it lacks it rather than by the shape of the method. A provider that
|
|
284 |
+ |
// cannot host a portal does not implement [`HostedPortal`], so the compiler
|
|
285 |
+ |
// says so at the wiring rather than the route saying so at runtime with an
|
|
286 |
+ |
// `Err(Unsupported)` nobody can plan around.
|
|
287 |
+ |
|
|
288 |
+ |
/// Provider-hosted billing pages the customer is redirected to.
|
|
289 |
+ |
///
|
|
290 |
+ |
/// One trait for both entry points because they are one capability: the
|
|
291 |
+ |
/// SyncKit portal call delegates straight to the general one
|
|
292 |
+ |
/// (`StripeClient::create_synckit_billing_portal`), so a rail that can serve
|
|
293 |
+ |
/// either can serve both.
|
|
294 |
+ |
#[async_trait::async_trait]
|
|
295 |
+ |
pub trait HostedPortal: Send + Sync {
|
|
296 |
+ |
/// Create a provider-hosted billing portal session. Returns the URL to
|
|
297 |
+ |
/// redirect to.
|
|
298 |
+ |
async fn create_billing_portal_session(
|
|
299 |
+ |
&self,
|
|
300 |
+ |
customer_id: &str,
|
|
301 |
+ |
return_url: &str,
|
|
302 |
+ |
) -> crate::error::Result<String>;
|
|
303 |
+ |
|
|
304 |
+ |
/// The same portal for a SyncKit developer's per-app customer.
|
| 344 |
305 |
|
async fn create_synckit_billing_portal(
|
| 345 |
306 |
|
&self,
|
| 346 |
307 |
|
customer_id: &str,
|
| 348 |
309 |
|
) -> crate::error::Result<String>;
|
| 349 |
310 |
|
}
|
| 350 |
311 |
|
|
|
312 |
+ |
/// Provider-hosted onboarding for connected accounts, plus reading one back.
|
|
313 |
+ |
///
|
|
314 |
+ |
/// A rail that onboards sellers out of band (a form we host, a contract, a bank
|
|
315 |
+ |
/// enrolment) mints an account id without ever having a link to send anyone to,
|
|
316 |
+ |
/// and has no account object of the provider's shape to fetch.
|
|
317 |
+ |
#[async_trait::async_trait]
|
|
318 |
+ |
pub trait ConnectOnboarding: Send + Sync {
|
|
319 |
+ |
async fn create_account_link(
|
|
320 |
+ |
&self,
|
|
321 |
+ |
account_id: &str,
|
|
322 |
+ |
return_url: &str,
|
|
323 |
+ |
refresh_url: &str,
|
|
324 |
+ |
) -> crate::error::Result<String>;
|
|
325 |
+ |
|
|
326 |
+ |
async fn fetch_account(&self, account_id: &str) -> crate::error::Result<AccountUpdate>;
|
|
327 |
+ |
}
|
|
328 |
+ |
|
|
329 |
+ |
/// A product/price catalogue held on the provider's side.
|
|
330 |
+ |
///
|
|
331 |
+ |
/// A rail that prices at charge time has nothing to create here: the amount is
|
|
332 |
+ |
/// an argument, not a stored object with an id.
|
|
333 |
+ |
#[async_trait::async_trait]
|
|
334 |
+ |
pub trait Catalogue: Send + Sync {
|
|
335 |
+ |
async fn create_subscription_product_and_price(
|
|
336 |
+ |
&self,
|
|
337 |
+ |
connected_account_id: &str,
|
|
338 |
+ |
tier_name: &str,
|
|
339 |
+ |
tier_description: Option<&str>,
|
|
340 |
+ |
price_cents: i64,
|
|
341 |
+ |
currency: crate::currency::SettlementCurrency,
|
|
342 |
+ |
) -> crate::error::Result<(String, String)>;
|
|
343 |
+ |
}
|
|
344 |
+ |
|
|
345 |
+ |
/// Refunds initiated through the provider.
|
|
346 |
+ |
///
|
|
347 |
+ |
/// Line-scoped: refunds `amount_cents` of the shared PaymentIntent and tags the
|
|
348 |
+ |
/// refund with the transaction id so the `refund.created` webhook marks and
|
|
349 |
+ |
/// revokes exactly that line (cart orders share one PaymentIntent).
|
|
350 |
+ |
#[async_trait::async_trait]
|
|
351 |
+ |
pub trait Refundable: Send + Sync {
|
|
352 |
+ |
async fn create_refund_for_transaction(
|
|
353 |
+ |
&self,
|
|
354 |
+ |
payment_intent_id: &str,
|
|
355 |
+ |
connected_account_id: &str,
|
|
356 |
+ |
amount_cents: i64,
|
|
357 |
+ |
transaction_id: crate::db::TransactionId,
|
|
358 |
+ |
) -> crate::error::Result<()>;
|
|
359 |
+ |
}
|
|
360 |
+ |
|
|
361 |
+ |
/// Platform-funded transfers into a connected account, and their reversal.
|
|
362 |
+ |
///
|
|
363 |
+ |
/// This is money moving from MNW's own balance to a creator's, which only a
|
|
364 |
+ |
/// rail that holds a platform balance can do. It makes the creator whole for a
|
|
365 |
+ |
/// Fan+ credit applied to their sale. Deterministic idempotency keys keep
|
|
366 |
+ |
/// replays and retries from double-paying or double-clawing.
|
|
367 |
+ |
#[async_trait::async_trait]
|
|
368 |
+ |
pub trait PlatformTransfers: Send + Sync {
|
|
369 |
+ |
/// Reimburse a creator for a platform-funded credit. Returns the transfer
|
|
370 |
+ |
/// id so it can be reversed if the sale is refunded.
|
|
371 |
+ |
async fn create_platform_credit_transfer(
|
|
372 |
+ |
&self,
|
|
373 |
+ |
connected_account_id: &str,
|
|
374 |
+ |
amount_cents: i64,
|
|
375 |
+ |
transaction_id: crate::db::TransactionId,
|
|
376 |
+ |
currency: crate::currency::SettlementCurrency,
|
|
377 |
+ |
) -> crate::error::Result<String>;
|
|
378 |
+ |
|
|
379 |
+ |
/// Reverse a settled platform-credit transfer when its sale is refunded,
|
|
380 |
+ |
/// clawing the reimbursement back from the connected account to MNW.
|
|
381 |
+ |
async fn create_platform_credit_reversal(
|
|
382 |
+ |
&self,
|
|
383 |
+ |
transfer_id: &str,
|
|
384 |
+ |
amount_cents: i64,
|
|
385 |
+ |
transaction_id: crate::db::TransactionId,
|
|
386 |
+ |
) -> crate::error::Result<()>;
|
|
387 |
+ |
}
|
|
388 |
+ |
|
|
389 |
+ |
/// Customer records the provider holds on our behalf, and subscriptions billed
|
|
390 |
+ |
/// against them.
|
|
391 |
+ |
///
|
|
392 |
+ |
/// SyncKit v2 developer billing keeps one customer and one subscription per
|
|
393 |
+ |
/// app, separate from creator-tier and Fan+ subscriptions; see
|
|
394 |
+ |
/// `synckit_billing.rs` for the rationale on per-app customers. A rail that
|
|
395 |
+ |
/// does not custody customers (charging a token per transaction, say) has
|
|
396 |
+ |
/// nowhere to put one.
|
|
397 |
+ |
#[async_trait::async_trait]
|
|
398 |
+ |
pub trait CustodialCustomers: Send + Sync {
|
|
399 |
+ |
async fn create_synckit_customer(
|
|
400 |
+ |
&self,
|
|
401 |
+ |
developer_user_id: crate::db::UserId,
|
|
402 |
+ |
app_id: crate::db::SyncAppId,
|
|
403 |
+ |
email: &str,
|
|
404 |
+ |
app_name: &str,
|
|
405 |
+ |
) -> crate::error::Result<String>;
|
|
406 |
+ |
|
|
407 |
+ |
async fn create_synckit_subscription(
|
|
408 |
+ |
&self,
|
|
409 |
+ |
customer_id: &str,
|
|
410 |
+ |
app_id: crate::db::SyncAppId,
|
|
411 |
+ |
app_name: &str,
|
|
412 |
+ |
price_cents: i64,
|
|
413 |
+ |
) -> crate::error::Result<SynckitSubResult>;
|
|
414 |
+ |
}
|
|
415 |
+ |
|
|
416 |
+ |
/// A provider that implements the base trait and every capability, which is
|
|
417 |
+ |
/// what a full-service rail like Stripe is.
|
|
418 |
+ |
///
|
|
419 |
+ |
/// Wiring convenience only: [`PaymentCapabilities::all`] takes one `Arc` and
|
|
420 |
+ |
/// hands back a handle per capability, so a deployment names its provider once.
|
|
421 |
+ |
pub trait FullPaymentProvider:
|
|
422 |
+ |
PaymentProvider
|
|
423 |
+ |
+ HostedPortal
|
|
424 |
+ |
+ ConnectOnboarding
|
|
425 |
+ |
+ Catalogue
|
|
426 |
+ |
+ Refundable
|
|
427 |
+ |
+ PlatformTransfers
|
|
428 |
+ |
+ CustodialCustomers
|
|
429 |
+ |
+ 'static
|
|
430 |
+ |
{
|
|
431 |
+ |
}
|
|
432 |
+ |
|
|
433 |
+ |
impl<T> FullPaymentProvider for T where
|
|
434 |
+ |
T: PaymentProvider
|
|
435 |
+ |
+ HostedPortal
|
|
436 |
+ |
+ ConnectOnboarding
|
|
437 |
+ |
+ Catalogue
|
|
438 |
+ |
+ Refundable
|
|
439 |
+ |
+ PlatformTransfers
|
|
440 |
+ |
+ CustodialCustomers
|
|
441 |
+ |
+ 'static
|
|
442 |
+ |
{
|
|
443 |
+ |
}
|
|
444 |
+ |
|
|
445 |
+ |
/// Typed handles to whichever capabilities the wired provider implements.
|
|
446 |
+ |
///
|
|
447 |
+ |
/// One field per extension trait, grouped the way `AppStorage` groups the
|
|
448 |
+ |
/// buckets: the alternative was six more fields on each of `AppState`,
|
|
449 |
+ |
/// `Billing` and `AppStateParts` plus their two propagation sites, which is
|
|
450 |
+ |
/// eighteen declarations for six capabilities. Grouping keeps the reference
|
|
451 |
+ |
/// typed (`state.payment_caps.require_hosted_portal()?` is one unwrap and no
|
|
452 |
+ |
/// `Any`) without that.
|
|
453 |
+ |
///
|
|
454 |
+ |
/// `None` means the deployment's provider cannot do it, which is the same
|
|
455 |
+ |
/// answer as "no provider is configured at all" from a route's point of view.
|
|
456 |
+ |
#[derive(Clone, Default)]
|
|
457 |
+ |
pub struct PaymentCapabilities {
|
|
458 |
+ |
pub hosted_portal: Option<std::sync::Arc<dyn HostedPortal>>,
|
|
459 |
+ |
pub connect_onboarding: Option<std::sync::Arc<dyn ConnectOnboarding>>,
|
|
460 |
+ |
pub catalogue: Option<std::sync::Arc<dyn Catalogue>>,
|
|
461 |
+ |
pub refundable: Option<std::sync::Arc<dyn Refundable>>,
|
|
462 |
+ |
pub platform_transfers: Option<std::sync::Arc<dyn PlatformTransfers>>,
|
|
463 |
+ |
pub custodial_customers: Option<std::sync::Arc<dyn CustodialCustomers>>,
|
|
464 |
+ |
}
|
|
465 |
+ |
|
|
466 |
+ |
impl PaymentCapabilities {
|
|
467 |
+ |
/// Every capability, all backed by the one provider value.
|
|
468 |
+ |
pub fn all<P: FullPaymentProvider>(provider: std::sync::Arc<P>) -> Self {
|
|
469 |
+ |
Self {
|
|
470 |
+ |
hosted_portal: Some(provider.clone()),
|
|
471 |
+ |
connect_onboarding: Some(provider.clone()),
|
|
472 |
+ |
catalogue: Some(provider.clone()),
|
|
473 |
+ |
refundable: Some(provider.clone()),
|
|
474 |
+ |
platform_transfers: Some(provider.clone()),
|
|
475 |
+ |
custodial_customers: Some(provider),
|
|
476 |
+ |
}
|
|
477 |
+ |
}
|
|
478 |
+ |
}
|
|
479 |
+ |
|
|
480 |
+ |
impl PaymentCapabilities {
|
|
481 |
+ |
fn missing(what: &str) -> crate::error::AppError {
|
|
482 |
+ |
crate::error::AppError::ServiceUnavailable(format!(
|
|
483 |
+ |
"The configured payment provider does not support {what}"
|
|
484 |
+ |
))
|
|
485 |
+ |
}
|
|
486 |
+ |
|
|
487 |
+ |
/// The hosted-portal capability, or a 503 if the provider has none.
|
|
488 |
+ |
pub fn require_hosted_portal(&self) -> crate::error::Result<&std::sync::Arc<dyn HostedPortal>> {
|
|
489 |
+ |
self.hosted_portal
|
|
490 |
+ |
.as_ref()
|
|
491 |
+ |
.ok_or_else(|| Self::missing("hosted billing portals"))
|
|
492 |
+ |
}
|
|
493 |
+ |
|
|
494 |
+ |
/// The Connect-onboarding capability, or a 503 if the provider has none.
|
|
495 |
+ |
pub fn require_connect_onboarding(
|
|
496 |
+ |
&self,
|
|
497 |
+ |
) -> crate::error::Result<&std::sync::Arc<dyn ConnectOnboarding>> {
|
|
498 |
+ |
self.connect_onboarding
|
|
499 |
+ |
.as_ref()
|
|
500 |
+ |
.ok_or_else(|| Self::missing("hosted account onboarding"))
|
|
501 |
+ |
}
|
|
502 |
+ |
|
|
503 |
+ |
/// The catalogue capability, or a 503 if the provider has none.
|
|
504 |
+ |
pub fn require_catalogue(&self) -> crate::error::Result<&std::sync::Arc<dyn Catalogue>> {
|
|
505 |
+ |
self.catalogue
|
|
506 |
+ |
.as_ref()
|
|
507 |
+ |
.ok_or_else(|| Self::missing("a hosted product catalogue"))
|
|
508 |
+ |
}
|
|
509 |
+ |
|
|
510 |
+ |
/// The refund capability, or a 503 if the provider has none.
|
|
511 |
+ |
pub fn require_refundable(&self) -> crate::error::Result<&std::sync::Arc<dyn Refundable>> {
|
|
512 |
+ |
self.refundable
|
|
513 |
+ |
.as_ref()
|
|
514 |
+ |
.ok_or_else(|| Self::missing("refunds"))
|
|
515 |
+ |
}
|
|
516 |
+ |
|
|
517 |
+ |
/// The platform-transfer capability, or a 503 if the provider has none.
|
|
518 |
+ |
pub fn require_platform_transfers(
|
|
519 |
+ |
&self,
|
|
520 |
+ |
) -> crate::error::Result<&std::sync::Arc<dyn PlatformTransfers>> {
|
|
521 |
+ |
self.platform_transfers
|
|
522 |
+ |
.as_ref()
|
|
523 |
+ |
.ok_or_else(|| Self::missing("platform transfers"))
|
|
524 |
+ |
}
|
|
525 |
+ |
|
|
526 |
+ |
/// The custodial-customer capability, or a 503 if the provider has none.
|
|
527 |
+ |
pub fn require_custodial_customers(
|
|
528 |
+ |
&self,
|
|
529 |
+ |
) -> crate::error::Result<&std::sync::Arc<dyn CustodialCustomers>> {
|
|
530 |
+ |
self.custodial_customers
|
|
531 |
+ |
.as_ref()
|
|
532 |
+ |
.ok_or_else(|| Self::missing("provider-held customer records"))
|
|
533 |
+ |
}
|
|
534 |
+ |
}
|
|
535 |
+ |
|
| 351 |
536 |
|
#[cfg(test)]
|
| 352 |
537 |
|
pub(crate) mod test_provider {
|
| 353 |
538 |
|
//! A crate-visible [`PaymentProvider`] double for lib tests.
|
| 558 |
743 |
|
) -> crate::error::Result<ProviderAccountId> {
|
| 559 |
744 |
|
ScriptedProvider::create_connect_account(self)
|
| 560 |
745 |
|
}
|
| 561 |
|
- |
async fn create_account_link(
|
| 562 |
|
- |
&self,
|
| 563 |
|
- |
_account_id: &str,
|
| 564 |
|
- |
_return_url: &str,
|
| 565 |
|
- |
_refresh_url: &str,
|
| 566 |
|
- |
) -> crate::error::Result<String> {
|
| 567 |
|
- |
ScriptedProvider::create_account_link(self)
|
| 568 |
|
- |
}
|
| 569 |
|
- |
async fn fetch_account(&self, _account_id: &str) -> crate::error::Result<AccountUpdate> {
|
| 570 |
|
- |
ScriptedProvider::fetch_account(self)
|
| 571 |
|
- |
}
|
| 572 |
|
- |
async fn create_subscription_product_and_price(
|
| 573 |
|
- |
&self,
|
| 574 |
|
- |
_connected_account_id: &str,
|
| 575 |
|
- |
_tier_name: &str,
|
| 576 |
|
- |
_tier_description: Option<&str>,
|
| 577 |
|
- |
_price_cents: i64,
|
| 578 |
|
- |
_currency: crate::currency::SettlementCurrency,
|
| 579 |
|
- |
) -> crate::error::Result<(String, String)> {
|
| 580 |
|
- |
ScriptedProvider::create_subscription_product_and_price(self)
|
| 581 |
|
- |
}
|
| 582 |
746 |
|
async fn get_balance(
|
| 583 |
747 |
|
&self,
|
| 584 |
748 |
|
_account_id: &str,
|
| 596 |
760 |
|
) -> crate::error::Result<()> {
|
| 597 |
761 |
|
ScriptedProvider::set_platform_cancel_at_period_end(self)
|
| 598 |
762 |
|
}
|
| 599 |
|
- |
async fn create_billing_portal_session(
|
| 600 |
|
- |
&self,
|
| 601 |
|
- |
_customer_id: &str,
|
| 602 |
|
- |
_return_url: &str,
|
| 603 |
|
- |
) -> crate::error::Result<String> {
|
| 604 |
|
- |
ScriptedProvider::create_billing_portal_session(self)
|
| 605 |
|
- |
}
|
| 606 |
|
- |
async fn create_refund_for_transaction(
|
| 607 |
|
- |
&self,
|
| 608 |
|
- |
_payment_intent_id: &str,
|
| 609 |
|
- |
_connected_account_id: &str,
|
| 610 |
|
- |
_amount_cents: i64,
|
| 611 |
|
- |
_transaction_id: crate::db::TransactionId,
|
| 612 |
|
- |
) -> crate::error::Result<()> {
|
| 613 |
|
- |
ScriptedProvider::create_refund_for_transaction(self)
|
| 614 |
|
- |
}
|
| 615 |
|
- |
async fn create_platform_credit_transfer(
|
| 616 |
|
- |
&self,
|
| 617 |
|
- |
_connected_account_id: &str,
|
| 618 |
|
- |
_amount_cents: i64,
|
| 619 |
|
- |
_transaction_id: crate::db::TransactionId,
|
| 620 |
|
- |
_currency: crate::currency::SettlementCurrency,
|
| 621 |
|
- |
) -> crate::error::Result<String> {
|
| 622 |
|
- |
ScriptedProvider::create_platform_credit_transfer(self)
|
| 623 |
|
- |
}
|
| 624 |
|
- |
async fn create_platform_credit_reversal(
|
| 625 |
|
- |
&self,
|
| 626 |
|
- |
_transfer_id: &str,
|
| 627 |
|
- |
_amount_cents: i64,
|
| 628 |
|
- |
_transaction_id: crate::db::TransactionId,
|
| 629 |
|
- |
) -> crate::error::Result<()> {
|
| 630 |
|
- |
ScriptedProvider::create_platform_credit_reversal(self)
|
| 631 |
|
- |
}
|
| 632 |
763 |
|
fn verify_webhook(
|
| 633 |
764 |
|
&self,
|
| 634 |
765 |
|
_payload: &str,
|
| 643 |
774 |
|
) -> crate::error::Result<serde_json::Value> {
|
| 644 |
775 |
|
ScriptedProvider::verify_webhook_v2(self)
|
| 645 |
776 |
|
}
|
| 646 |
|
- |
async fn create_synckit_customer(
|
| 647 |
|
- |
&self,
|
| 648 |
|
- |
_developer_user_id: crate::db::UserId,
|
| 649 |
|
- |
_app_id: crate::db::SyncAppId,
|
| 650 |
|
- |
_email: &str,
|
| 651 |
|
- |
_app_name: &str,
|
| 652 |
|
- |
) -> crate::error::Result<String> {
|
| 653 |
|
- |
ScriptedProvider::create_synckit_customer(self)
|
| 654 |
|
- |
}
|
| 655 |
|
- |
async fn create_synckit_subscription(
|
| 656 |
|
- |
&self,
|
| 657 |
|
- |
_customer_id: &str,
|
| 658 |
|
- |
_app_id: crate::db::SyncAppId,
|
| 659 |
|
- |
_app_name: &str,
|
| 660 |
|
- |
_price_cents: i64,
|
| 661 |
|
- |
) -> crate::error::Result<SynckitSubResult> {
|
| 662 |
|
- |
ScriptedProvider::create_synckit_subscription(self)
|
| 663 |
|
- |
}
|
| 664 |
777 |
|
async fn update_synckit_subscription_price(
|
| 665 |
778 |
|
&self,
|
| 666 |
779 |
|
_subscription_id: &str,
|
| 681 |
794 |
|
async fn cancel_synckit_subscription(&self, _sub: &str) -> crate::error::Result<()> {
|
| 682 |
795 |
|
ScriptedProvider::cancel_synckit_subscription(self)
|
| 683 |
796 |
|
}
|
|
797 |
+ |
}
|
|
798 |
+ |
|
|
799 |
+ |
// ── The capability extensions, every one of them a panic: no lib test
|
|
800 |
+ |
// drives an extension yet, and `ScriptedProvider` implements them so the
|
|
801 |
+ |
// double stays wirable wherever a full provider is expected.
|
|
802 |
+ |
|
|
803 |
+ |
#[async_trait::async_trait]
|
|
804 |
+ |
impl HostedPortal for ScriptedProvider {
|
|
805 |
+ |
async fn create_billing_portal_session(
|
|
806 |
+ |
&self,
|
|
807 |
+ |
_customer_id: &str,
|
|
808 |
+ |
_return_url: &str,
|
|
809 |
+ |
) -> crate::error::Result<String> {
|
|
810 |
+ |
ScriptedProvider::create_billing_portal_session(self)
|
|
811 |
+ |
}
|
| 684 |
812 |
|
async fn create_synckit_billing_portal(
|
| 685 |
813 |
|
&self,
|
| 686 |
814 |
|
_customer_id: &str,
|
| 689 |
817 |
|
ScriptedProvider::create_synckit_billing_portal(self)
|
| 690 |
818 |
|
}
|
| 691 |
819 |
|
}
|
|
820 |
+ |
|
|
821 |
+ |
#[async_trait::async_trait]
|
|
822 |
+ |
impl ConnectOnboarding for ScriptedProvider {
|
|
823 |
+ |
async fn create_account_link(
|
|
824 |
+ |
&self,
|
|
825 |
+ |
_account_id: &str,
|
|
826 |
+ |
_return_url: &str,
|
|
827 |
+ |
_refresh_url: &str,
|
|
828 |
+ |
) -> crate::error::Result<String> {
|
|
829 |
+ |
ScriptedProvider::create_account_link(self)
|
|
830 |
+ |
}
|
|
831 |
+ |
async fn fetch_account(&self, _account_id: &str) -> crate::error::Result<AccountUpdate> {
|
|
832 |
+ |
ScriptedProvider::fetch_account(self)
|
|
833 |
+ |
}
|
|
834 |
+ |
}
|
|
835 |
+ |
|
|
836 |
+ |
#[async_trait::async_trait]
|
|
837 |
+ |
impl Catalogue for ScriptedProvider {
|
|
838 |
+ |
async fn create_subscription_product_and_price(
|
|
839 |
+ |
&self,
|
|
840 |
+ |
_connected_account_id: &str,
|
|
841 |
+ |
_tier_name: &str,
|
|
842 |
+ |
_tier_description: Option<&str>,
|
|
843 |
+ |
_price_cents: i64,
|
|
844 |
+ |
_currency: crate::currency::SettlementCurrency,
|
|
845 |
+ |
) -> crate::error::Result<(String, String)> {
|
|
846 |
+ |
ScriptedProvider::create_subscription_product_and_price(self)
|
|
847 |
+ |
}
|
|
848 |
+ |
}
|