Skip to content

Feature/bgf 100 #378

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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