Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
3 changes: 0 additions & 3 deletions examples/commerce-essentials/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# Commerce Essentials Elastic Path storefront starter

This project was generated with [Composable CLI](https://www.npmjs.com/package/composable-cli).

This storefront accelerates the development of a direct-to-consumer ecommerce experience using Elastic Path's modular products.

## Tech Stack
Expand Down
3 changes: 2 additions & 1 deletion examples/commerce-essentials/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"react-toastify": "^9.1.3",
"schema-dts": "^1.1.2",
"server-only": "^0.0.1",
"short-unique-id": "^5.2.0",
"tailwind-clip-path": "^1.0.0",
"tailwind-merge": "^2.0.0",
"zod": "^3.22.4",
Expand Down Expand Up @@ -96,4 +97,4 @@
"vite": "^4.2.1",
"vitest": "^0.34.5"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@ import * as React from "react";
import { Separator } from "../../../components/separator/Separator";
import { CheckoutFooter } from "./CheckoutFooter";
import { Button } from "../../../components/button/Button";
import { Resource } from "@elasticpath/js-sdk";
import { OrderWithShortNumber } from "../../../lib/types/order-with-short-number";

export function OrderConfirmation() {
const { confirmationData } = useCheckout();

if (!confirmationData) {
return null;
}

const { order } = confirmationData;
const order = confirmationData.order as Resource<OrderWithShortNumber>;

const customerName = (
order.data.contact?.name ??
order.data.customer.name ??
""
).split(" ")[0];

const { shipping_address, id: orderId } = order.data;

const { shipping_address, id: orderId} = order.data;
const orderIdentifier = order.data.order_number ? order.data.order_number : orderId;

return (
<div className="lg:flex lg:min-h-full">
<div className="flex justify-center items-center lg:hidden py-5">
Expand All @@ -51,7 +53,7 @@ export function OrderConfirmation() {
</Button>
</div>
<span className="text-black/60">
Order <span className="text-black">#{orderId}</span> is confirmed.
Order <span className="text-black">#{orderIdentifier}</span> is confirmed.
</span>
{/* Shipping */}
<section className="flex flex-col gap-5 ">
Expand All @@ -74,7 +76,7 @@ export function OrderConfirmation() {
<section>
<h2 className="text-2xl font-medium">Need to make changes?</h2>
<p className="text-black/60">
Email us or call. Remember to reference order #{orderId}
Email us or call. Remember to reference order #{orderIdentifier}
</p>
</section>
<CheckoutFooter />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use server";

import ShortUniqueId from "short-unique-id";
import { getServerSideCredentialsClient } from "../../../lib/epcc-server-side-credentials-client";
import { Order, OrderFilter, OrderInclude, OrdersEndpoint, OrderSort, QueryableResource } from "@elasticpath/js-sdk";

interface OrderFilterWithShortOrder extends OrderFilter {
eq?: OrderFilter['eq'] & {
order_number: string;
};
}

interface OrdersEndpointWithShortOrder extends QueryableResource<Order, OrderFilterWithShortOrder, OrderSort, OrderInclude> {
endpoint: 'orders'
}
const shortIdGen = new ShortUniqueId();

export async function getShortOrderNumber():Promise<string> {
return generateShortOrder();
}

async function generateShortOrder() {
const shortId = shortIdGen.rnd();
if (await found(shortId))
return generateShortOrder();
return shortId;
}

async function found(shortId: string) {
const client=getServerSideCredentialsClient();
const orderEndpoint=client.Orders as OrdersEndpointWithShortOrder;
orderEndpoint.Filter({eq:{order_number: shortId}});
const previousShortOrders=await orderEndpoint.All();
if(previousShortOrders.data.length>0)
return true;
return false;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
/*
import {
useAddCustomItemToCart,
useCheckout as useCheckoutCart,
useCheckoutWithAccount,
useOrderConfirm,
usePayments,
} from "@elasticpath/react-shopper-hooks";
*/
import { getShortOrderNumber } from "./actions";
import {
useAddCustomItemToCart,
useOrderConfirm,
usePayments,
} from "@elasticpath/react-shopper-hooks";

import { useCheckout as useCheckoutCart, useCheckoutWithAccount } from "../../../hooks/use-checkout";
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
import { CheckoutForm } from "../../../components/checkout/form-schema/checkout-form-schema";
import { useShippingMethod } from "./useShippingMethod";
Expand All @@ -16,6 +26,7 @@ import {
} from "@elasticpath/js-sdk";
import { useElements, useStripe } from "@stripe/react-stripe-js";


export type UsePaymentCompleteProps = {
cartId: string | undefined;
accountToken?: string;
Expand All @@ -25,6 +36,8 @@ export type UsePaymentCompleteReq = {
data: CheckoutForm;
};



export function usePaymentComplete(
{ cartId, accountToken }: UsePaymentCompleteProps,
options?: UseMutationOptions<
Expand All @@ -49,7 +62,8 @@ export function usePaymentComplete(

const stripe = useStripe();
const elements = useElements();
const staticDeliveryMethods=useShippingMethod(cartId)?.data;
const staticDeliveryMethods = useShippingMethod(cartId)?.data;

const paymentComplete = useMutation({
mutationFn: async ({ data }) => {
const {
Expand All @@ -65,13 +79,14 @@ export function usePaymentComplete(
billingAddress:
billingAddress && !sameAsShipping ? billingAddress : shippingAddress,
shippingAddress: shippingAddress,
shortOrder: await getShortOrderNumber()
};

/**
* The handling of shipping options is not production ready.
* You must implement your own based on your business needs.
*/
const shippingAmount =staticDeliveryMethods?.find((method) => method.value === shippingMethod)?.amount ?? 0;
const shippingAmount = staticDeliveryMethods?.find((method) => method.value === shippingMethod)?.amount ?? 0;

/**
* Using a cart custom_item to represent shipping for demo purposes.
Expand All @@ -94,20 +109,20 @@ export function usePaymentComplete(
*/
const createdOrder = await ("guest" in data
? mutateConvertToOrder({
customer: {
email: data.guest.email,
name: customerName,
},
...checkoutProps,
})
customer: {
email: data.guest.email,
name: customerName,
},
...checkoutProps,
})
: mutateConvertToOrderAsAccount({
contact: {
name: data.account.name,
email: data.account.email,
},
token: accountToken ?? "",
...checkoutProps,
}));
contact: {
name: data.account.name,
email: data.account.email,
},
token: accountToken ?? "",
...checkoutProps,
}));

/**
* 2. Start payment against the order
Expand Down Expand Up @@ -171,3 +186,5 @@ export function usePaymentComplete(
...paymentComplete,
};
}


Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { formatIsoDateString } from "../../../../lib/format-iso-date-string";

export type OrderItemProps = {
children?: ReactNode;
order: Order;
order: Order & {order_number?:string};
orderItems: OrderItemType[];
imageUrl?: string;
};
Expand All @@ -26,7 +26,7 @@ export function OrderItem({ children, order, orderItems }: OrderItemProps) {
</div>
<div className="flex flex-1 flex-col gap-y-1.5">
<span className="text-sm font-normal text-black/60">
Order # {order.external_ref ?? order.id}
Order # {order.order_number?? order.external_ref ?? order.id}
</span>
<Link href={`/account/orders/${order.id}`}>
<h2 className="font-medium text-base">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
RelationshipToMany,
ResourcePage,
PcmProduct,
ResourceIncluded,
} from "@elasticpath/js-sdk";
import {
getSelectedAccount,
Expand Down Expand Up @@ -43,9 +44,9 @@ export default async function Order({
const selectedAccount = getSelectedAccount(accountMemberCookie);

const client = getServerSideImplicitClient();

let result: ResourceIncluded<OrderType & {order_number:string}, OrderIncluded> | undefined = undefined;

let result: Awaited<ReturnType<typeof client.Orders.Get>> | undefined =
undefined;
try {
result = await client.request.send(
`/orders/${params.orderId}?include=items`,
Expand Down Expand Up @@ -100,7 +101,7 @@ export default async function Order({
<div className="w-full border-t border-zinc-300"></div>
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<h1 className="text-4xl">Order # {shopperOrder.raw.id}</h1>
<h1 className="text-4xl">Order # {result?.data.order_number?? shopperOrder.raw.id}</h1>
<time dateTime={shopperOrder.raw.meta.timestamps.created_at}>
{formatIsoDateString(shopperOrder.raw.meta.timestamps.created_at)}
</time>
Expand Down
127 changes: 127 additions & 0 deletions examples/commerce-essentials/src/hooks/use-checkout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"use client"

import { useMutation, UseMutationOptions } from "@tanstack/react-query"
import { useElasticPath } from "@elasticpath/react-shopper-hooks"
import {
Address,
CartAdditionalHeaders,
CheckoutCustomer,
CheckoutCustomerObject,
ElasticPath,
Order,
Resource,
} from "@elasticpath/js-sdk"

export type UseCheckoutReq = {
customer: string | CheckoutCustomer | CheckoutCustomerObject
billingAddress: Partial<Address>
shippingAddress?: Partial<Address>
additionalHeaders?: CartAdditionalHeaders,
shortOrder?: string
}

export const useCheckout = (
cartId: string,
options?: UseMutationOptions<Resource<Order>, Error, UseCheckoutReq>,
) => {
const { client } = useElasticPath()
return useMutation({
mutationFn: async ({
customer,
billingAddress,
shippingAddress,
additionalHeaders,
shortOrder
}: UseCheckoutReq) => {
return shortOrder ? checkoutWithShortOrder(client, cartId, shortOrder, billingAddress, shippingAddress, customer, undefined, additionalHeaders as { [key: string]: string }) :
checkoutAsGuest(client, cartId, customer, billingAddress, shippingAddress, additionalHeaders)
},
...options,
})
}

export type UseCheckoutWithAccountReq = {
contact: string | CheckoutCustomer | CheckoutCustomerObject
billingAddress: Partial<Address>
token: string
shippingAddress?: Partial<Address>
additionalHeaders?: CartAdditionalHeaders
shortOrder?: string
}

export const useCheckoutWithAccount = (
cartId: string,
options?: UseMutationOptions<
Resource<Order>,
Error,
UseCheckoutWithAccountReq
>,
) => {
const { client } = useElasticPath()
return useMutation({
mutationFn: async ({
contact,
billingAddress,
shippingAddress,
token,
additionalHeaders,
shortOrder
}: UseCheckoutWithAccountReq) => {
if (token && additionalHeaders) {
additionalHeaders["EP-Account-Management-Authentication-Token"] = token;
} else if (token) {
additionalHeaders = { "EP-Account-Management-Authentication-Token": token };
}
return shortOrder ? checkoutWithShortOrder(client, cartId, shortOrder, billingAddress, shippingAddress, undefined, contact, additionalHeaders as { [key: string]: string }) :
checkoutWithContact(client, cartId, contact, billingAddress, shippingAddress, token, additionalHeaders)
},
...options,
})
}
function checkoutAsGuest(client: ElasticPath, cartId: string, customer: string | CheckoutCustomer | CheckoutCustomerObject, billingAddress: Partial<Address>, shippingAddress: Partial<Address> | undefined, additionalHeaders: CartAdditionalHeaders | undefined): Resource<Order> | PromiseLike<Resource<Order>> {
return client
.Cart(cartId)
.Checkout(customer, billingAddress, shippingAddress, additionalHeaders)
}

function checkoutWithContact(client: ElasticPath, cartId: string, contact: string | CheckoutCustomer | CheckoutCustomerObject, billingAddress: Partial<Address>, shippingAddress: Partial<Address> | undefined, token: string, additionalHeaders: CartAdditionalHeaders | undefined): Resource<Order> | PromiseLike<Resource<Order>> {
return client.Cart(cartId).CheckoutWithAccountManagementToken(
contact,
billingAddress,
shippingAddress,
token,
// @ts-ignore TODO: Fix type definition in js-sdk
additionalHeaders
)
}

function checkoutWithShortOrder(client: ElasticPath,
cartId: string,
shortOrderId: string,
billingAddress: Partial<Address>,
shippingAddress: Partial<Address> | undefined,
customer?: string | CheckoutCustomer | CheckoutCustomerObject,
contact?: string | CheckoutCustomer | CheckoutCustomerObject,
additionalHeaders?: { [key: string]: string }) {

const body: {
customer?: string | CheckoutCustomer | CheckoutCustomerObject,
contact?:string | CheckoutCustomer | CheckoutCustomerObject,
order_number: string,
billing_address: Partial<Address>,
shipping_address?: Partial<Address>

} = {
order_number: shortOrderId,
billing_address: billingAddress,
shipping_address: shippingAddress
}
body.customer = customer;
body.contact = contact;


return client.request.send(`/carts/${cartId}/checkout`, "POST", body, undefined, client, undefined, "v2",
additionalHeaders,
);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Order } from "@elasticpath/js-sdk";
export interface OrderWithShortNumber extends Order {
order_number?: string;
}
Loading
Loading