Skip to content

Commit

Permalink
feat: manual payment gateway support
Browse files Browse the repository at this point in the history
  • Loading branch information
field123 committed Nov 14, 2023
1 parent caae49a commit b9565b8
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 6 deletions.
42 changes: 42 additions & 0 deletions packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export function createActiveStoreMiddleware(
}

if (hasActiveStore(store) || !isTTY()) {
const activeStore = ctx.store.get("store") as Record<string, string>
ctx.logger.info(`Using store: ${activeStore?.name} - ${activeStore?.id}`)
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PaymentsCommandArguments, ManualCommandArguments> {
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<EPPaymentsForce> {
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`,
)
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<typeof manualGatewaySettingsSchema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { z } from "zod"

export const epPaymentsSetupSchema = z.object({
accountId: z.string(),
publishableKey: z.string(),
})

export type EPPaymentsSetup = z.TypeOf<typeof epPaymentsSetupSchema>

export const epPaymentsForceSchema = z.object({
force: z.boolean(),
})

export type EPPaymentsForce = z.TypeOf<typeof epPaymentsForceSchema>
Original file line number Diff line number Diff line change
@@ -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,
},
}
}
}
Original file line number Diff line number Diff line change
@@ -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<OperationResult<Gateway>> {
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."
}`,
),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -27,6 +28,7 @@ export function createPaymentsCommand(
.middleware(createComposableProjectMiddleware(ctx))
.middleware(createActiveStoreMiddleware(ctx))
.command(createEPPaymentsCommand(ctx))
.command(createManualPaymentCommand(ctx))
.help()
.demandCommand(1)
.strict()
Expand Down
Loading

0 comments on commit b9565b8

Please sign in to comment.