-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pricing table and use global configuration Customer on Stripe (#12)
* ensure configuration customer on Stripe and use its theme * add typed backend client to frontend * implement pricing page into frontend * lint & format
- Loading branch information
1 parent
2196e6f
commit 3d66bd9
Showing
17 changed files
with
680 additions
and
146 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,19 +11,25 @@ import { | |
import { ChainId, PaymentOption, ProductDetails, WidgetProps } from '@superfluid-finance/widget'; | ||
import { currencyDecimalMapping } from 'src/stripe-currencies'; | ||
import { formatUnits } from 'viem'; | ||
import { InjectStripeClient } from '@golevelup/nestjs-stripe'; | ||
import { DEFAULT_PAGING } from 'src/stripe-module-config'; | ||
|
||
const configurationCustomerEmail = '[email protected]' as const; | ||
|
||
type Input = { | ||
product: Stripe.Product; | ||
prices: Stripe.Price[]; // NOTE: These need to be fetched separately from the Product based on Product ID. | ||
configurationCustomer?: Stripe.Customer; | ||
}; | ||
|
||
type Output = { | ||
productDetails: ProductDetails; | ||
paymentDetails: WidgetProps['paymentDetails']; | ||
theme: any; // TODO: get rid of any | ||
}; | ||
|
||
interface StripeProductToWidgetConfigMapper { | ||
mapStripeProductToWidgetConfig(stripe: Input): Output; | ||
mapStripeProductToWidgetConfig(stripe: Input): Promise<Output>; | ||
} | ||
|
||
type PriceId = string; | ||
|
@@ -34,14 +40,55 @@ interface SuperTokenToStripeCurrencyMapper { | |
}): PriceId | undefined; | ||
} | ||
|
||
// Rename to "global config"? | ||
interface ConfigurationCustomerManager { | ||
ensureConfigurationCustomer(): Promise<Stripe.Customer>; | ||
} | ||
|
||
@Injectable() | ||
export class SuperfluidStripeConverterService | ||
implements StripeProductToWidgetConfigMapper, SuperTokenToStripeCurrencyMapper | ||
implements | ||
StripeProductToWidgetConfigMapper, | ||
SuperTokenToStripeCurrencyMapper, | ||
ConfigurationCustomerManager | ||
{ | ||
// TODO(KK): Inject | ||
private readonly chainToSuperTokenReceiverMap = defaultChainToSuperTokenReceiverMap; | ||
private readonly stripeCurrencyToSuperTokenMap = defaultStripeCurrencyToSuperTokenMap; | ||
|
||
constructor(@InjectStripeClient() private readonly stripeClient: Stripe) {} | ||
|
||
async ensureConfigurationCustomer(): Promise<Stripe.Customer> { | ||
// TODO: caching | ||
// TODO: use better constants | ||
|
||
let configurationCustomer: Stripe.Customer; | ||
|
||
const customers = await this.stripeClient.customers | ||
.list({ | ||
email: configurationCustomerEmail, | ||
}) | ||
.autoPagingToArray(DEFAULT_PAGING); | ||
|
||
if (customers.length === 1) { | ||
configurationCustomer = customers[0]; | ||
} else if (customers.length === 0) { | ||
configurationCustomer = await this.stripeClient.customers.create({ | ||
email: configurationCustomerEmail, | ||
metadata: { | ||
note: 'Auto-generated. Be careful when editing!', | ||
theme: `{"palette":{"mode":"light","primary":{"main":"#3f51b5"},"secondary":{"main":"#f50057"}}}`, | ||
}, | ||
}); | ||
} else { | ||
throw new Error( | ||
`There should not be more than one Superfluid-Stripe configuration customer. Please remove one of the customers with e-mail: [${configurationCustomerEmail}]`, | ||
); | ||
} | ||
|
||
return configurationCustomer; | ||
} | ||
|
||
mapSuperTokenToStripeCurrency(superToken: { | ||
chainId: number; | ||
address: string; | ||
|
@@ -60,9 +107,12 @@ export class SuperfluidStripeConverterService | |
return undefined; | ||
} | ||
|
||
mapStripeProductToWidgetConfig(stripe: Input): Output { | ||
async mapStripeProductToWidgetConfig(stripe: Input): Promise<Output> { | ||
// TODO(KK): Enforce it's a subscription-based product? | ||
|
||
const configurationCustomer = | ||
stripe.configurationCustomer ?? (await this.ensureConfigurationCustomer()); | ||
|
||
const productDetails: Output['productDetails'] = { | ||
name: stripe.product.name, | ||
description: stripe.product.description ?? '', // TODO(KK): Stripe product might not have a description. The Product Card of the widget should still look good. | ||
|
@@ -112,9 +162,22 @@ export class SuperfluidStripeConverterService | |
paymentOptions, | ||
}; | ||
|
||
// TODO: use Zod for validation? | ||
// TODO: get rid of any | ||
let theme: any; | ||
try { | ||
theme = JSON.parse(configurationCustomer.metadata['theme']); | ||
} catch (e) { | ||
logger.error(e); | ||
} | ||
|
||
logger.debug('theme'); | ||
logger.debug(theme); | ||
|
||
return { | ||
productDetails, | ||
paymentDetails, | ||
theme, | ||
}; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.