Skip to content

Commit

Permalink
feat(top-up): init top-up with stripe checkout command PE-6635
Browse files Browse the repository at this point in the history
  • Loading branch information
fedellen committed Sep 3, 2024
1 parent feab3c6 commit c43e11b
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 37 deletions.
16 changes: 7 additions & 9 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { Command, program } from 'commander';

import { version } from '../version.js';
import { cryptoFund, getBalance } from './commands.js';
import { cryptoFund, getBalance, topUp } from './commands.js';
import {
applyOptions,
configFromOptions,
Expand Down Expand Up @@ -53,14 +53,12 @@ applyOptions(

applyOptions(
program.command('top-up').description('Top up a Turbo address with Fiat'),
[optionMap.address, optionMap.value, optionMap.token],
).action((options) => {
console.log(
'TODO: fiat top-up',
options.address,
options.token,
options.value,
);
[...walletOptions, optionMap.address, optionMap.value, optionMap.currency],
).action(async (_commandOptions, command: Command) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const options: any = command.optsWithGlobals();

return topUp(options);
});

applyOptions(
Expand Down
131 changes: 122 additions & 9 deletions src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,57 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { exec } from 'node:child_process';

import {
TokenType,
TurboFactory,
TurboUnauthenticatedConfiguration,
TurboWallet,
currencyMap,
fiatCurrencyTypes,
isCurrency,
tokenToBaseMap,
} from '../node/index.js';
import { AddressOptions } from './types.js';
import { sleep } from '../utils/common.js';
import { AddressOptions, TopUpOptions } from './types.js';
import { configFromOptions, optionalPrivateKeyFromOptions } from './utils.js';

export async function addressOrPrivateKeyFromOptions(
options: AddressOptions,
): Promise<{
address: string | undefined;
privateKey: string | undefined;
}> {
if (options.address !== undefined) {
return { address: options.address, privateKey: undefined };
}

return {
address: undefined,
privateKey: await optionalPrivateKeyFromOptions(options),
};
}

export async function getBalance(options: AddressOptions) {
const config = configFromOptions(options);

if (options.address !== undefined) {
const { address, privateKey } = await addressOrPrivateKeyFromOptions(options);

if (address !== undefined) {
const turbo = TurboFactory.unauthenticated(config);
const { winc } = await turbo.getBalance(options.address);
const { winc } = await turbo.getBalance(address);

console.log(
`Turbo Balance for Native Address "${options.address}"\nCredits: ${
`Turbo Balance for Native Address "${address}"\nCredits: ${
+winc / 1_000_000_000_000
}`,
);
return;
}

const privateKey = await optionalPrivateKeyFromOptions(options);

if (privateKey === undefined) {
throw new Error(
'Must provide an address (--address) or use a valid wallet',
);
throw new Error('Must provide an (--address) or use a valid wallet');
}

const turbo = TurboFactory.authenticated({
Expand Down Expand Up @@ -88,3 +108,96 @@ export async function cryptoFund({
JSON.stringify(result, null, 2),
);
}

export async function topUp(options: TopUpOptions) {
const config = configFromOptions(options);

const { address, privateKey } = await addressOrPrivateKeyFromOptions(options);

const value = options.value;
if (value === undefined) {
throw new Error('Must provide a --value to top up');
}

const currency = options.currency ?? 'usd';

if (!isCurrency(currency)) {
throw new Error(
`Invalid fiat currency type ${currency}!\nPlease use one of these:\n${JSON.stringify(
fiatCurrencyTypes,
null,
2,
)}`,
);
}

// TODO: Pay in CLI prompts via --cli options

// if (address !== undefined) {
// const turbo = TurboFactory.unauthenticated(config);
// const { url, paymentAmount, winc } = await turbo.createCheckoutSession({
// amount: currencyMap[currency](+options.value),
// owner: address,
// });
// }

// if (privateKey === undefined) {
// throw new Error('Must provide a wallet to top up');
// }

// const turbo = TurboFactory.authenticated({
// ...config,
// privateKey,
// });

// const { url, paymentAmount, winc } = await turbo.createCheckoutSession({
// amount: currencyMap[currency](+options.value),
// owner: await turbo.signer.getNativeAddress(),
// });

const { url, paymentAmount, winc } = await (async () => {
const amount = currencyMap[currency](+value);

if (address !== undefined) {
const turbo = TurboFactory.unauthenticated(config);
return turbo.createCheckoutSession({
amount,
owner: address,
});
}

if (privateKey === undefined) {
throw new Error('Must provide a wallet to top up');
}

const turbo = TurboFactory.authenticated({
...config,
privateKey,
});
return turbo.createCheckoutSession({
amount,
owner: await turbo.signer.getNativeAddress(),
});
})();

console.log(
'Got Checkout Session\n' + JSON.stringify({ url, paymentAmount, winc }),
);
console.log('Opening checkout session in browser...');
await sleep(2000);

openUrl(url);
}

export function openUrl(url: string) {
if (process.platform === 'darwin') {
// macOS
exec(`open ${url}`);
} else if (process.platform === 'win32') {
// Windows
exec(`start "" "${url}"`, { windowsHide: true });
} else {
// Linux/Unix
open(url);
}
}
5 changes: 5 additions & 0 deletions src/cli/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ export type WalletOptions = GlobalOptions & {
export type AddressOptions = WalletOptions & {
address: string | undefined;
};

export type TopUpOptions = AddressOptions & {
value: string | undefined;
currency: string | undefined;
};
13 changes: 13 additions & 0 deletions src/common/currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,16 @@ export const BRL = (brl: number) => new TwoDecimalCurrency(brl, 'brl');

// Zero decimal currencies that are supported by the Turbo API
export const JPY = (jpy: number) => new ZeroDecimalCurrency(jpy, 'jpy');

export const currencyMap: Record<Currency, (amount: number) => CurrencyMap> = {
usd: USD,
eur: EUR,
gbp: GBP,
cad: CAD,
aud: AUD,
inr: INR,
sgd: SGD,
hkd: HKD,
brl: BRL,
jpy: JPY,
};
7 changes: 3 additions & 4 deletions src/common/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import {
Currency,
TokenTools,
TokenType,
TopUpRawResponse,
TurboAuthenticatedPaymentServiceConfiguration,
TurboAuthenticatedPaymentServiceInterface,
TurboBalanceResponse,
TurboCheckoutRawResponse,
TurboCheckoutSessionParams,
TurboCheckoutSessionResponse,
TurboCountriesResponse,
Expand Down Expand Up @@ -157,17 +157,16 @@ export class TurboUnauthenticatedPaymentService
}&token=${this.token}`;

const { adjustments, paymentSession, topUpQuote } =
await this.httpService.get<TopUpRawResponse>({
await this.httpService.get<TurboCheckoutRawResponse>({
endpoint,
headers,
});

return {
winc: topUpQuote.winstonCreditAmount,
adjustments,
url: paymentSession.url ?? undefined,
url: paymentSession.url,
id: paymentSession.id,
client_secret: paymentSession.client_secret ?? undefined,
paymentAmount: topUpQuote.paymentAmount,
quotedPaymentAmount: topUpQuote.quotedPaymentAmount,
};
Expand Down
46 changes: 31 additions & 15 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,24 @@ export type NativeAddress = string;
export type PublicArweaveAddress = Base64String;
export type TransactionId = Base64String;
export type UserAddress = string | PublicArweaveAddress;
export type Currency =
| 'usd'
| 'eur'
| 'gbp'
| 'cad'
| 'aud'
| 'jpy'
| 'inr'
| 'sgd'
| 'hkd'
| 'brl';

export const fiatCurrencyTypes = [
'usd',
'eur',
'gbp',
'cad',
'aud',
'jpy',
'inr',
'sgd',
'hkd',
'brl',
] as const;
export type Currency = (typeof fiatCurrencyTypes)[number];
export function isCurrency(currency: string): currency is Currency {
return fiatCurrencyTypes.includes(currency as Currency);
}

export type Country = 'United States' | 'United Kingdom' | 'Canada'; // TODO: add full list

export const tokenTypes = ['arweave', 'solana', 'ethereum', 'kyve'] as const;
Expand Down Expand Up @@ -94,17 +101,26 @@ export type TopUpRawResponse = {
winstonCreditAmount: string;
};
paymentSession: {
url: string | null;
id: string;
client_secret: string | null;
};
adjustments: Adjustment[];
};

export type TurboPaymentIntentRawResponse = TopUpRawResponse & {
paymentSession: {
client_secret: string;
};
};

export type TurboCheckoutRawResponse = TopUpRawResponse & {
paymentSession: {
url: string;
};
};

export type TurboCheckoutSessionResponse = TurboWincForFiatResponse & {
id: string;
client_secret?: string;
url?: string;
url: string;
};

export type TurboBalanceResponse = Omit<TurboPriceResponse, 'adjustments'>;
Expand Down

0 comments on commit c43e11b

Please sign in to comment.