Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: manual checkout #123

Merged
merged 6 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/cyan-coins-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@elasticpath/react-shopper-hooks": minor
"composable-cli": minor
"@elasticpath/d2c-schematics": minor
"@elasticpath/composable-common": minor
"@elasticpath/composable-integration-hub-deployer": minor
"@elasticpath/shopper-common": minor
---

manual payment gateway support for d2c starter kit
8 changes: 4 additions & 4 deletions packages/composable-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This package contains the executable for running [Elastic Path Commerce Cloud](h

## Generating a storefront

### Login to Elasticpath
### Login to Elastic Path

```bash
composable-cli login
Expand All @@ -22,7 +22,7 @@ composable-cli login
composable-cli generate d2c my-storefront
```

Select your Elasticpath store from the list of stores.
Select your Elastic Path store from the list of stores.

### Getting help

Expand All @@ -38,9 +38,9 @@ composable-cli --help
composable-cli int algolia
```

## Payments Setup
## Elastic Path Payments Setup

### Configuring Ep Payments
### Configuring Elastic Path Payments

```bash
composable-cli p ep-payments
Expand Down
50 changes: 46 additions & 4 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 Expand Up @@ -629,7 +671,7 @@ type PaymentTypeOptions =
epPaymentsStripeAccountId: string
epPaymentsStripePublishableKey: string
}
| { paymentGatewayType: "None" }
| { paymentGatewayType: "Manual" }

type PlpTypeOptions =
| {
Expand Down Expand Up @@ -678,8 +720,8 @@ async function schematicOptionPrompts(): Promise<{
value: "EP Payments",
},
{
name: "None",
value: "None",
name: "Basic (quick start)",
value: "Manual",
},
],
},
Expand All @@ -688,7 +730,7 @@ async function schematicOptionPrompts(): Promise<{
const paymentGateway =
paymentGatewayType === "EP Payments"
? await epPaymentsSchematicPrompts()
: { paymentGatewayType: "None" as const }
: { paymentGatewayType: "Manual" as const }

return {
plp,
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>
Loading
Loading