-
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.
Continued initial development, 2... (#14)
* add adr * remove demo project * init stripe customer configs on backend boot-up * fetch theme from stripe for all pages
- Loading branch information
1 parent
d62edcb
commit 20e2c6a
Showing
41 changed files
with
823 additions
and
3,574 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
87 changes: 87 additions & 0 deletions
87
apps/backend/src/superfluid-stripe-converter/superfluid-stripe-config/stripe-entities.ts
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 |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { tierASuperTokenList } from '@superfluid-finance/tokenlist'; | ||
|
||
import Stripe from 'stripe'; | ||
|
||
export const CUSTOMER_EMAIL = '[email protected]'; // This is always the key for finding the customers. | ||
|
||
// The customer names are used for finding the configuration with right type. | ||
export const LOOK_AND_FEEL_CUSTOMER_NAME = 'Superfluid ♥ Stripe: Look and Feel'; | ||
export const BLOCKCHAIN_CUSTOMER_NAME = 'Superfluid ♥ Stripe: Blockchain'; | ||
|
||
// This is the default customer for look and feel to bootstrap the integration. | ||
export const DEFAULT_LOOK_AND_FEEL_CUSTOMER = { | ||
email: CUSTOMER_EMAIL, | ||
name: LOOK_AND_FEEL_CUSTOMER_NAME, | ||
description: 'Auto-generated fake customer for Superfluid integration.', | ||
metadata: { | ||
theme: `{"palette":{"mode":"light","primary":{"main":"#3f51b5"},"secondary":{"main":"#f50057"}}}`, | ||
}, | ||
} as const satisfies Stripe.CustomerCreateParams; | ||
|
||
const liveCurrencyTokenSymbols = { | ||
usd: ['USDCx', 'USDTx', 'DAIx', 'cUSDx', 'G$', 'mUSDx'], | ||
eur: ['cEURx', 'EUROex', 'EURSx', 'agEURx', 'jEURx', 'EURex'], | ||
cad: ['jCADx'], | ||
bgn: ['jBGNx'], | ||
chf: ['jXOFx'], | ||
php: ['jPHPx'], | ||
xaf: ['jXAFx'], | ||
sgd: ['jSGDx'], | ||
jpy: ['JPYCx'], | ||
}; | ||
|
||
// This is the default customer for on-chain settings. | ||
export const createDefaultBlockChainCustomer = (testMode: boolean): Stripe.CustomerCreateParams => { | ||
return { | ||
email: CUSTOMER_EMAIL, | ||
name: BLOCKCHAIN_CUSTOMER_NAME, | ||
description: 'Auto-generated fake customer for Superfluid integration.', | ||
metadata: { | ||
chain_5_usd_token: '0x8ae68021f6170e5a766be613cea0d75236ecca9a', | ||
chain_5_receiver: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', | ||
default_receiver: '', | ||
}, | ||
}; | ||
}; | ||
|
||
// This is the default customer for on-chain settings. | ||
export const DEFAULT_LIVE_BLOCKCHAIN_CUSTOMER = { | ||
email: CUSTOMER_EMAIL, | ||
name: BLOCKCHAIN_CUSTOMER_NAME, | ||
description: 'Auto-generated fake customer for Superfluid integration.', // TODO(KK): Add documentation here. | ||
metadata: { | ||
chain_43114_usd_token: '0x288398f314d472b82c44855f3f6ff20b633c2a97', | ||
chain_43114_receiver: '0x...', | ||
chain_42161_usd_token: '0x1dbc1809486460dcd189b8a15990bca3272ee04e', | ||
chain_42161_receiver: '0x...', | ||
chain_100_usd_token: '0x1234756ccf0660e866305289267211823ae86eec', | ||
chain_100_receiver: '0x...', | ||
chain_1_usd_token: '0x1ba8603da702602a75a25fca122bb6898b8b1282', | ||
chain_1_receiver: '0x...', | ||
chain_10_usd_token: '0x8430f084b939208e2eded1584889c9a66b90562f', | ||
chain_10_receiver: '0x...', | ||
chain_137_usd_token: '0xcaa7349cea390f89641fe306d93591f87595dc1f', | ||
chain_137_receiver: '0x...', | ||
default_receiver: '', | ||
}, | ||
} as const satisfies Stripe.CustomerCreateParams; | ||
|
||
export const FIRST_TIME_EXAMPLE_PRODUCT: Stripe.ProductCreateParams = { | ||
name: 'Example Superfluid Integration Product', | ||
features: [ | ||
{ name: 'decentralized' }, | ||
{ name: 'pseudoanonymous' }, | ||
{ name: 'pay and get paid every second' }, | ||
{ name: 'complete control of your money streams' }, | ||
], | ||
default_price_data: { | ||
currency: 'USD', | ||
recurring: { | ||
interval: 'month', | ||
}, | ||
unit_amount: 500, | ||
}, | ||
// metadata: { | ||
// superfluid: `The value here does not matter. When "superfluid" metadata key is specified then it is valid for the Superfluid integration.` | ||
// } | ||
}; |
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 |
---|---|---|
|
@@ -3,128 +3,111 @@ import { Injectable, Logger } from '@nestjs/common'; | |
import { DEFAULT_PAGING } from 'src/stripe-module-config'; | ||
import Stripe from 'stripe'; | ||
import { Address, ChainId, StripeCurrencyKey } from './basic-types'; | ||
import { isAddress } from 'viem'; | ||
|
||
const CUSTOMER_EMAIL = '[email protected]'; // This is the key for finding the customer. | ||
const LOOK_AND_FEEL_CUSTOMER_NAME = 'Superfluid ♥ Stripe: Look and Feel'; | ||
const BLOCKCHAIN_CUSTOMER_NAME = 'Superfluid ♥ Stripe: Blockchain'; | ||
|
||
const DEFAULT_LOOK_AND_FEEL_CUSTOMER = { | ||
email: CUSTOMER_EMAIL, | ||
name: LOOK_AND_FEEL_CUSTOMER_NAME, | ||
description: 'Auto-generated fake customer for Superfluid integration.', | ||
metadata: { | ||
theme: `{"palette":{"mode":"light","primary":{"main":"#3f51b5"},"secondary":{"main":"#f50057"}}}`, | ||
}, | ||
} as const satisfies Stripe.CustomerCreateParams; | ||
|
||
const DEFAULT_BLOCKCHAIN_CUSTOMER = { | ||
email: CUSTOMER_EMAIL, | ||
name: BLOCKCHAIN_CUSTOMER_NAME, | ||
description: 'Auto-generated fake customer for Superfluid integration.', // TODO(KK): Add documentation here. | ||
metadata: { | ||
chain_43114_usd_token: '0x288398f314d472b82c44855f3f6ff20b633c2a97', | ||
chain_43114_receiver: '0x...', | ||
chain_42161_usd_token: '0x1dbc1809486460dcd189b8a15990bca3272ee04e', | ||
chain_42161_receiver: '0x...', | ||
chain_100_usd_token: '0x1234756ccf0660e866305289267211823ae86eec', | ||
chain_100_receiver: '0x...', | ||
chain_1_usd_token: '0x1ba8603da702602a75a25fca122bb6898b8b1282', | ||
chain_1_receiver: '0x...', | ||
chain_10_usd_token: '0x8430f084b939208e2eded1584889c9a66b90562f', | ||
chain_10_receiver: '0x...', | ||
chain_137_usd_token: '0xcaa7349cea390f89641fe306d93591f87595dc1f', | ||
chain_137_receiver: '0x...', | ||
chain_5_usd_token: '0x8ae68021f6170e5a766be613cea0d75236ecca9a', | ||
chain_5_receiver: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', // vitalik.eth | ||
default_receiver: '', | ||
}, | ||
} as const satisfies Stripe.CustomerCreateParams; | ||
|
||
const FIRST_TIME_EXAMPLE_PRODUCT: Stripe.ProductCreateParams = { | ||
name: 'Example Superfluid Integration Product', | ||
features: [ | ||
{ name: 'decentralized' }, | ||
{ name: 'pseudoanonymous' }, | ||
{ name: 'pay and get paid every second' }, | ||
{ name: 'complete control of your money streams' }, | ||
], | ||
default_price_data: { | ||
currency: 'USD', | ||
recurring: { | ||
interval: 'month', | ||
}, | ||
unit_amount: 500, | ||
}, | ||
|
||
// metadata: { | ||
// superfluid: `The value here does not matter. When "superfluid" metadata key is specified then it is valid for the Superfluid integration.` | ||
// } | ||
import { Block, isAddress } from 'viem'; | ||
import { | ||
BLOCKCHAIN_CUSTOMER_NAME, | ||
CUSTOMER_EMAIL, | ||
DEFAULT_LOOK_AND_FEEL_CUSTOMER, | ||
FIRST_TIME_EXAMPLE_PRODUCT, | ||
LOOK_AND_FEEL_CUSTOMER_NAME, | ||
createDefaultBlockChainCustomer, | ||
} from './stripe-entities'; | ||
import { ConfigService } from '@nestjs/config'; | ||
|
||
export type LookAndFeelConfig = { | ||
theme: any; // TODO(KK): any | ||
}; | ||
|
||
export type IntegrationConfig = { | ||
export type BlockchainConfig = { | ||
version: string; | ||
chains: ReadonlyArray<ChainConfig>; | ||
theme: any; // TODO(KK): any | ||
// lookAndFeel: Record<string, any>; | ||
}; | ||
|
||
export type CompleteConfig = LookAndFeelConfig & BlockchainConfig; | ||
|
||
interface GlobalConfigCustomerManager { | ||
loadConfig(): Promise<IntegrationConfig>; | ||
loadOrInitializeCompleteConfig(): Promise<CompleteConfig>; | ||
loadOrInitializeLookAndFeelConfig(): Promise<LookAndFeelConfig>; | ||
loadOrInitializeBlockchainConfig(): Promise<BlockchainConfig>; | ||
} | ||
|
||
@Injectable() | ||
export class SuperfluidStripeConfigService implements GlobalConfigCustomerManager { | ||
constructor(@InjectStripeClient() private readonly stripeClient: Stripe) {} | ||
private readonly stripeTestMode; | ||
|
||
async loadConfig(): Promise<IntegrationConfig> { | ||
// TODO: caching | ||
// TODO: use better constants | ||
constructor( | ||
@InjectStripeClient() private readonly stripeClient: Stripe, | ||
private readonly configService: ConfigService, | ||
) { | ||
this.stripeTestMode = this.configService.get('STRIPE_TEST_MODE') === 'true'; | ||
} | ||
|
||
let configurationCustomer: Stripe.Customer; | ||
async loadOrInitializeLookAndFeelConfig(): Promise<LookAndFeelConfig> { | ||
const { lookAndFeelCustomer: lookAndFeelCustomer_ } = await this.loadCustomers(); | ||
const lookAndFeelCustomer = | ||
lookAndFeelCustomer_ ?? | ||
(await this.stripeClient.customers.create(DEFAULT_LOOK_AND_FEEL_CUSTOMER)); | ||
|
||
const customers = await this.stripeClient.customers | ||
.list({ | ||
email: CUSTOMER_EMAIL, | ||
}) | ||
.autoPagingToArray(DEFAULT_PAGING); | ||
// TODO: use Zod for validation? | ||
// TODO: get rid of any | ||
const theme = JSON.parse(lookAndFeelCustomer.metadata.theme); | ||
|
||
let blockchainCustomer = customers.find((x) => x.name === BLOCKCHAIN_CUSTOMER_NAME); | ||
let lookAndFeelCustomer = customers.find((x) => x.name === LOOK_AND_FEEL_CUSTOMER_NAME); | ||
return { | ||
theme, | ||
}; | ||
} | ||
|
||
const isFirstTimeUsage = !blockchainCustomer && !lookAndFeelCustomer; | ||
if (isFirstTimeUsage) { | ||
await this.stripeClient.products.create(FIRST_TIME_EXAMPLE_PRODUCT); | ||
} | ||
async loadOrInitializeBlockchainConfig(): Promise<BlockchainConfig> { | ||
const { blockchainCustomer: blockchainCustomer_ } = await this.loadCustomers(); | ||
const blockchainCustomer = | ||
blockchainCustomer_ ?? | ||
(await this.stripeClient.customers.create( | ||
createDefaultBlockChainCustomer(this.stripeTestMode), | ||
)); | ||
|
||
if (!blockchainCustomer) { | ||
blockchainCustomer = await this.stripeClient.customers.create(DEFAULT_BLOCKCHAIN_CUSTOMER); | ||
} | ||
const chainConfigs = mapBlockchainCustomerMetadataIntoChainConfigs(blockchainCustomer.metadata); | ||
|
||
if (!lookAndFeelCustomer) { | ||
lookAndFeelCustomer = await this.stripeClient.customers.create( | ||
DEFAULT_LOOK_AND_FEEL_CUSTOMER, | ||
); | ||
} | ||
return { | ||
version: '1.0.0', | ||
chains: chainConfigs, | ||
}; | ||
} | ||
|
||
const chainConfigs = mapBlockchainCustomerMetadataIntoChainConfigs(blockchainCustomer.metadata); | ||
async loadOrInitializeCompleteConfig(): Promise<CompleteConfig> { | ||
const { lookAndFeelCustomer, blockchainCustomer } = await this.loadCustomers(); | ||
|
||
// TODO: use Zod for validation? | ||
// TODO: get rid of any | ||
let theme: any; | ||
try { | ||
theme = JSON.parse(lookAndFeelCustomer.metadata['theme']); | ||
} catch (e) { | ||
logger.error(e); | ||
const isFirstTimeUsage = !lookAndFeelCustomer && !blockchainCustomer; | ||
if (this.stripeTestMode) { | ||
if (isFirstTimeUsage) { | ||
await this.stripeClient.products.create(FIRST_TIME_EXAMPLE_PRODUCT); | ||
} | ||
} | ||
|
||
const mappedResult: IntegrationConfig = { | ||
version: '1.0.0', | ||
chains: chainConfigs, | ||
theme, | ||
const lookAndFeelConfig = await this.loadOrInitializeLookAndFeelConfig(); | ||
const blockchainConfig = await this.loadOrInitializeBlockchainConfig(); | ||
|
||
return { | ||
...lookAndFeelConfig, | ||
...blockchainConfig, | ||
}; | ||
} | ||
|
||
private async loadCustomers(): Promise<{ | ||
blockchainCustomer: Stripe.Customer | undefined; | ||
lookAndFeelCustomer: Stripe.Customer | undefined; | ||
}> { | ||
// TODO: cache | ||
|
||
return mappedResult; | ||
const customers = await this.stripeClient.customers | ||
.list({ | ||
email: CUSTOMER_EMAIL, | ||
}) | ||
.autoPagingToArray(DEFAULT_PAGING); | ||
|
||
return { | ||
blockchainCustomer: customers.find((x) => x.name === BLOCKCHAIN_CUSTOMER_NAME), | ||
lookAndFeelCustomer: customers.find((x) => x.name === LOOK_AND_FEEL_CUSTOMER_NAME), | ||
}; | ||
} | ||
} | ||
|
||
|
Oops, something went wrong.