From b9565b8e6d6534caa78112a5b1754eaad99c726a Mon Sep 17 00:00:00 2001 From: Robert Field Date: Tue, 14 Nov 2023 18:06:55 +0000 Subject: [PATCH] feat: manual payment gateway support --- .../src/commands/generate/d2c/d2c-command.tsx | 42 +++++ .../commands/generate/generate-command.tsx | 2 + .../ep-payments/ep-payments-command.tsx | 6 +- .../payments/manual/manual-command.tsx | 159 ++++++++++++++++++ .../manual/manual-integration.types.ts | 20 +++ .../manual/util/ep-payments-schema.ts | 8 + .../manual/util/setup-ep-payments-schema.ts | 14 ++ .../manual/util/setup-epcc-manual-gateway.ts | 51 ++++++ .../payments/manual/util/update-gateway.ts | 37 ++++ .../commands/payments/payments-command.tsx | 2 + .../src/commands/store/store-command.tsx | 2 +- 11 files changed, 337 insertions(+), 6 deletions(-) create mode 100644 packages/composable-cli/src/commands/payments/manual/manual-command.tsx create mode 100644 packages/composable-cli/src/commands/payments/manual/manual-integration.types.ts create mode 100644 packages/composable-cli/src/commands/payments/manual/util/ep-payments-schema.ts create mode 100644 packages/composable-cli/src/commands/payments/manual/util/setup-ep-payments-schema.ts create mode 100644 packages/composable-cli/src/commands/payments/manual/util/setup-epcc-manual-gateway.ts create mode 100644 packages/composable-cli/src/commands/payments/manual/util/update-gateway.ts diff --git a/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx b/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx index b83f71ca..f931bf45 100644 --- a/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx +++ b/packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx @@ -55,6 +55,10 @@ import { paramCase } from "change-case" import { retrieveComposableRcFile } from "../../../lib/config-middleware" import findUp from "find-up" import path from "path" +import { + createManualPaymentCommandHandler, + isManualGatewayAlreadyExistsError, +} from "../../payments/manual/manual-command" export function createD2CCommand( ctx: CommandContext, @@ -537,6 +541,44 @@ export function createD2CCommandHandler( } } + if (gatheredOptions.paymentGatewayType === "Manual") { + logger.info( + boxen( + `${colors.bold.green( + "Basic checkout needs to be configured", + )}\nTo get your checkout working you need to configure basic checkout which is powered by manual gateway.`, + { + padding: 1, + margin: 1, + }, + ), + ) + + const { configureManualGateway } = await inquirer.prompt([ + { + type: "confirm", + name: "configureManualGateway", + message: "Do you want to configure Basic checkout?", + }, + ]) + + if (configureManualGateway) { + const result = await createManualPaymentCommandHandler(updatedCtx)({ + ...args, + }) + + if ( + !result.success && + isManualGatewayAlreadyExistsError(result.error) + ) { + notes.push({ + title: "Basic checkout setup", + description: "The Manual payment gateway was already setup.", + }) + } + } + } + if (gatheredOptions.paymentGatewayType === "EP Payments") { logger.info( boxen( diff --git a/packages/composable-cli/src/commands/generate/generate-command.tsx b/packages/composable-cli/src/commands/generate/generate-command.tsx index b85f1bff..4c47b6fb 100644 --- a/packages/composable-cli/src/commands/generate/generate-command.tsx +++ b/packages/composable-cli/src/commands/generate/generate-command.tsx @@ -121,6 +121,8 @@ export function createActiveStoreMiddleware( } if (hasActiveStore(store) || !isTTY()) { + const activeStore = ctx.store.get("store") as Record + ctx.logger.info(`Using store: ${activeStore?.name} - ${activeStore?.id}`) return } diff --git a/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx b/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx index bbb420df..1bf4148a 100644 --- a/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx +++ b/packages/composable-cli/src/commands/payments/ep-payments/ep-payments-command.tsx @@ -7,10 +7,7 @@ import { } from "./ep-payments-integration.types" import { CommandContext, CommandHandlerFunction } from "../../../types/command" import { trackCommandHandler } from "../../../util/track-command-handler" -import { - createActiveStoreMiddleware, - createAuthenticationCheckerMiddleware, -} from "../../generate/generate-command" +import { createAuthenticationCheckerMiddleware } from "../../generate/generate-command" import { PaymentsCommandArguments } from "../payments.types" import * as ansiColors from "ansi-colors" import inquirer from "inquirer" @@ -38,7 +35,6 @@ export function createEPPaymentsCommand( builder: async (yargs) => { return yargs .middleware(createAuthenticationCheckerMiddleware(ctx)) - .middleware(createActiveStoreMiddleware(ctx)) .option("account-id", { type: "string", description: "EP Payments account ID", diff --git a/packages/composable-cli/src/commands/payments/manual/manual-command.tsx b/packages/composable-cli/src/commands/payments/manual/manual-command.tsx new file mode 100644 index 00000000..3bf298da --- /dev/null +++ b/packages/composable-cli/src/commands/payments/manual/manual-command.tsx @@ -0,0 +1,159 @@ +import yargs from "yargs" + +import { CommandContext, CommandHandlerFunction } from "../../../types/command" +import { trackCommandHandler } from "../../../util/track-command-handler" +import { createAuthenticationCheckerMiddleware } from "../../generate/generate-command" +import { PaymentsCommandArguments } from "../payments.types" +import inquirer from "inquirer" +import { isTTY } from "../../../util/is-tty" +import boxen from "boxen" +import ora from "ora" +import { setupManualPaymentGateway } from "./util/setup-epcc-manual-gateway" +import { EPPaymentsForce } from "./util/setup-ep-payments-schema" +import { processUnknownError } from "../../../util/process-unknown-error" +import { checkGateway } from "@elasticpath/composable-common" +import { + ManualCommandArguments, + ManualCommandData, + ManualCommandError, + ManualCommandErrorAlreadyExists, +} from "./manual-integration.types" + +export function createManualPaymentCommand( + ctx: CommandContext, +): yargs.CommandModule { + return { + command: "manual", + describe: + "setup EP Payment gateway for your Elastic Path powered storefront", + builder: async (yargs) => { + return yargs + .middleware(createAuthenticationCheckerMiddleware(ctx)) + .option("force", { + type: "boolean", + description: "Force setup of Manual even if already enabled", + }) + .fail(false) + .help() + }, + handler: ctx.handleErrors( + trackCommandHandler(ctx, createManualPaymentCommandHandler), + ), + } +} + +export function createManualPaymentCommandHandler( + ctx: CommandContext, +): CommandHandlerFunction< + ManualCommandData, + ManualCommandError, + ManualCommandArguments +> { + return async function manualPaymentCommandHandler(args) { + const spinner = ora() + + const { epClient, logger } = ctx + + try { + if (!epClient) { + spinner.fail(`Failed to setup Manual Payment.`) + return { + success: false, + error: { + code: "missing_ep_client", + message: "Failed to setup Manual Payment - missing EP client", + }, + } + } + + spinner.start(`Checking if Manual gateway already exists...`) + // check if Manual gateway is already setup + if (!args.force) { + const checkGatewayResult = await checkGateway(epClient, "manual") + spinner.stop() + + if (checkGatewayResult.success) { + const forceResult = await resolveForceOptions(args) + + if (!forceResult.force) { + spinner.fail( + `Manual gateway already exists and you didn't want to force an update.`, + ) + + logger.warn( + boxen("`Manual was already setup.", { + padding: 1, + borderColor: "yellow", + }), + ) + return { + success: false, + error: { + code: "manual_already_setup", + message: "Manual was already setup", + }, + } + } + } + } + + spinner.start(`Setting up Manual gateway...`) + const result = await setupManualPaymentGateway(epClient, logger) + + if (!result.success) { + spinner.fail(`Failed to setup Manual Gateway.`) + return { + success: false, + error: { + code: "manual_gateway_setup_failed", + message: "Failed to setup Manual", + }, + } + } + + spinner.succeed(`Manual setup successfully.`) + + return { + success: true, + data: {}, + } + } catch (e) { + spinner.fail(`Failed to setup Manual gateway.`) + logger.error(processUnknownError(e)) + return { + success: false, + error: { + code: "FAILED_TO_SETUP_MANUAL_GATEWAY", + message: "Failed to setup Manual gateway", + }, + } + } + } +} + +export function isManualGatewayAlreadyExistsError( + error: ManualCommandError, +): error is ManualCommandErrorAlreadyExists { + return error.code === "manual_already_setup" +} + +async function resolveForceOptions( + args: ManualCommandArguments, +): Promise { + if (args.interactive && isTTY()) { + const { force } = await inquirer.prompt([ + { + type: "confirm", + name: "force", + message: "Manual is already enabled would you like update anyway?", + }, + ]) + return { + force, + } + } + + throw new Error( + `Invalid arguments: Manual is already enabled and missing force argument`, + ) +} diff --git a/packages/composable-cli/src/commands/payments/manual/manual-integration.types.ts b/packages/composable-cli/src/commands/payments/manual/manual-integration.types.ts new file mode 100644 index 00000000..483666c6 --- /dev/null +++ b/packages/composable-cli/src/commands/payments/manual/manual-integration.types.ts @@ -0,0 +1,20 @@ +import { PaymentsCommandArguments } from "../payments.types" + +export type ManualCommandData = {} + +export type ManualCommandError = + | { + code: string + message: string + } + | ManualCommandErrorAlreadyExists + +export type ManualCommandErrorAlreadyExists = { + code: "manual_already_setup" + message: string + accountId: string +} + +export type ManualCommandArguments = { + force?: boolean +} & PaymentsCommandArguments diff --git a/packages/composable-cli/src/commands/payments/manual/util/ep-payments-schema.ts b/packages/composable-cli/src/commands/payments/manual/util/ep-payments-schema.ts new file mode 100644 index 00000000..4203b6f6 --- /dev/null +++ b/packages/composable-cli/src/commands/payments/manual/util/ep-payments-schema.ts @@ -0,0 +1,8 @@ +import { z } from "zod" + +export const manualGatewaySettingsSchema = z.object({ + epPaymentsStripeAccountId: z.string().min(1), + epPaymentsStripePublishableKey: z.string().min(1), +}) + +export type ManualGatewaySettings = z.TypeOf diff --git a/packages/composable-cli/src/commands/payments/manual/util/setup-ep-payments-schema.ts b/packages/composable-cli/src/commands/payments/manual/util/setup-ep-payments-schema.ts new file mode 100644 index 00000000..4975a82a --- /dev/null +++ b/packages/composable-cli/src/commands/payments/manual/util/setup-ep-payments-schema.ts @@ -0,0 +1,14 @@ +import { z } from "zod" + +export const epPaymentsSetupSchema = z.object({ + accountId: z.string(), + publishableKey: z.string(), +}) + +export type EPPaymentsSetup = z.TypeOf + +export const epPaymentsForceSchema = z.object({ + force: z.boolean(), +}) + +export type EPPaymentsForce = z.TypeOf diff --git a/packages/composable-cli/src/commands/payments/manual/util/setup-epcc-manual-gateway.ts b/packages/composable-cli/src/commands/payments/manual/util/setup-epcc-manual-gateway.ts new file mode 100644 index 00000000..bf5b60e7 --- /dev/null +++ b/packages/composable-cli/src/commands/payments/manual/util/setup-epcc-manual-gateway.ts @@ -0,0 +1,51 @@ +import { logging } from "@angular-devkit/core" +import type { Gateway, Moltin } from "@moltin/sdk" +import { OperationResult } from "@elasticpath/composable-common" +import { updateManualGateway } from "./update-gateway" +import { processUnknownError } from "../../../../util/process-unknown-error" + +export async function setupManualPaymentGateway( + epccClient: Moltin, + logger: logging.LoggerApi, +): Promise< + OperationResult< + Gateway, + { + code: "manual_gateway_update_failed" | "unknown" + message: string + } + > +> { + try { + /** + * Update manual gateway to be enabled + */ + const updateResult = await updateManualGateway(epccClient) + + if (!updateResult.success) { + logger.debug(`Failed to update ep payment gateway.`) + return { + success: false, + error: { + code: "manual_gateway_update_failed", + message: `Failed to update ep payment gateway. ${processUnknownError( + updateResult, + )}`, + }, + } + } + + return updateResult + } catch (err: unknown) { + const errorStr = processUnknownError(err) + logger.error(errorStr) + + return { + success: false, + error: { + code: "unknown", + message: errorStr, + }, + } + } +} diff --git a/packages/composable-cli/src/commands/payments/manual/util/update-gateway.ts b/packages/composable-cli/src/commands/payments/manual/util/update-gateway.ts new file mode 100644 index 00000000..6ad9a4b2 --- /dev/null +++ b/packages/composable-cli/src/commands/payments/manual/util/update-gateway.ts @@ -0,0 +1,37 @@ +import type { Gateway, Moltin as EpccClient } from "@moltin/sdk" +import { OperationResult } from "@elasticpath/composable-common" + +const errMsg = "Failed to enable manual gateway." + +export async function updateManualGateway( + client: EpccClient, +): Promise> { + try { + const updatedGateway = await client.Gateways.Update("manual", { + enabled: true, + }) + + if (updatedGateway.data) { + return { + success: true, + data: updatedGateway.data, + } + } + + return { + success: false, + error: new Error(`${errMsg} ${JSON.stringify(updatedGateway)}`), + } + } catch (err: unknown) { + return { + success: false, + error: new Error( + `${errMsg} An unknown error occurred ${ + err instanceof Error + ? `${err.name} - ${err.message}` + : "Failed to render error." + }`, + ), + } + } +} diff --git a/packages/composable-cli/src/commands/payments/payments-command.tsx b/packages/composable-cli/src/commands/payments/payments-command.tsx index 0b29ff83..adcd5346 100644 --- a/packages/composable-cli/src/commands/payments/payments-command.tsx +++ b/packages/composable-cli/src/commands/payments/payments-command.tsx @@ -14,6 +14,7 @@ import { } from "./payments.types" import { createActiveStoreMiddleware } from "../generate/generate-command" import { createComposableProjectMiddleware } from "../../lib/composable-project-middleware" +import { createManualPaymentCommand } from "./manual/manual-command" export function createPaymentsCommand( ctx: CommandContext, @@ -27,6 +28,7 @@ export function createPaymentsCommand( .middleware(createComposableProjectMiddleware(ctx)) .middleware(createActiveStoreMiddleware(ctx)) .command(createEPPaymentsCommand(ctx)) + .command(createManualPaymentCommand(ctx)) .help() .demandCommand(1) .strict() diff --git a/packages/composable-cli/src/commands/store/store-command.tsx b/packages/composable-cli/src/commands/store/store-command.tsx index 9e31fbf2..44e902f6 100644 --- a/packages/composable-cli/src/commands/store/store-command.tsx +++ b/packages/composable-cli/src/commands/store/store-command.tsx @@ -42,8 +42,8 @@ export function createStoreCommand( .middleware(createAuthenticationMiddleware(ctx)) .command(createSetStoreCommand(ctx)) .fail(false) - .help("h") .demandCommand(1) + .help() .strict() }, handler: ctx.handleErrors(