Order Confirmation

This commit is contained in:
tedraykov
2024-06-28 18:21:44 +03:00
parent 68039b1a6e
commit f4f6edcd9a
39 changed files with 2386 additions and 363 deletions

View File

@@ -390,6 +390,7 @@ export async function isLoggedIn(request: NextRequest, origin: string) {
}
newHeaders.set('x-shop-customer-token', `${customerTokenValue}`);
console.log('Customer Token', customerTokenValue);
return NextResponse.next({
request: {
// New request headers

View File

@@ -0,0 +1,17 @@
const addressFragment = /* GraphQL */ `
fragment Address on CustomerAddress {
id
address1
address2
firstName
lastName
provinceCode: zoneCode
city
zip
countryCodeV2: territoryCode
company
phone: phoneNumber
}
`;
export default addressFragment;

View File

@@ -0,0 +1,56 @@
const orderMetafieldsFragment = /* GraphQL */ `
fragment OrderMetafields on Order {
warrantyStatus: metafield(namespace: "custom", key: "warranty_status") {
value
id
key
}
warrantyActivationDeadline: metafield(
namespace: "custom"
key: "warranty_activation_deadline"
) {
value
id
key
}
warrantyActivationOdometer: metafield(
namespace: "custom"
key: "warranty_activation_odometer"
) {
value
id
key
}
warrantyActivationInstallation: metafield(
namespace: "custom"
key: "warranty_activation_installation"
) {
value
id
key
}
warrantyActivationSelfInstall: metafield(
namespace: "custom"
key: "warranty_activation_self_install"
) {
value
id
key
}
warrantyActivationVIN: metafield(namespace: "custom", key: "warranty_activation_vin") {
value
id
key
}
warrantyActivationMileage: metafield(namespace: "custom", key: "warranty_activation_mileage") {
value
id
key
}
orderConfirmation: metafield(namespace: "custom", key: "customer_confirmation") {
value
}
}
`;
export default orderMetafieldsFragment;

View File

@@ -0,0 +1,38 @@
const orderTransactionFragment = /* GraphQL */ `
fragment OrderTransaction on OrderTransaction {
id
processedAt
paymentIcon {
id
url
altText
}
paymentDetails {
... on CardPaymentDetails {
last4
cardBrand
}
}
transactionAmount {
presentmentMoney {
...Price
}
}
giftCardDetails {
last4
balance {
...Price
}
}
status
kind
transactionParentId
type
typeDetails {
name
message
}
}
`;
export default orderTransactionFragment;

View File

@@ -1,4 +1,8 @@
import addressFragment from './address';
import lineItemFragment from './line-item';
import orderMetafieldsFragment from './order-metafields';
import orderTrasactionFragment from './order-transaction';
import priceFragment from './price';
const orderCard = /* GraphQL */ `
fragment OrderCard on Order {
@@ -16,69 +20,44 @@ const orderCard = /* GraphQL */ `
}
}
totalPrice {
amount
currencyCode
...Price
}
subtotal {
...Price
}
totalShipping {
...Price
}
totalTax {
...Price
}
shippingLine {
title
originalPrice {
...Price
}
}
lineItems(first: 20) {
nodes {
...LineItem
}
}
shippingAddress {
...Address
}
billingAddress {
...Address
}
transactions {
...OrderTransaction
}
...OrderMetafields
}
${lineItemFragment}
`;
export const orderMetafields = /* GraphQL */ `
fragment OrderMetafield on Order {
id
warrantyStatus: metafield(namespace: "custom", key: "warranty_status") {
value
id
key
}
warrantyActivationDeadline: metafield(
namespace: "custom"
key: "warranty_activation_deadline"
) {
value
id
key
}
warrantyActivationOdometer: metafield(
namespace: "custom"
key: "warranty_activation_odometer"
) {
value
id
key
}
warrantyActivationInstallation: metafield(
namespace: "custom"
key: "warranty_activation_installation"
) {
value
id
key
}
warrantyActivationSelfInstall: metafield(
namespace: "custom"
key: "warranty_activation_self_install"
) {
value
id
key
}
warrantyActivationVIN: metafield(namespace: "custom", key: "warranty_activation_vin") {
value
id
key
}
warrantyActivationMileage: metafield(namespace: "custom", key: "warranty_activation_mileage") {
value
id
key
}
}
${addressFragment}
${priceFragment}
${orderTrasactionFragment}
${orderMetafieldsFragment}
`;
export default orderCard;

View File

@@ -0,0 +1,8 @@
const priceFragment = /* GraphQL */ `
fragment Price on MoneyV2 {
amount
currencyCode
}
`;
export default priceFragment;

View File

@@ -37,9 +37,8 @@ import {
import { getCustomerQuery } from './queries/customer';
import { getMenuQuery } from './queries/menu';
import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject';
import { getFileQuery, getImageQuery, getMetaobjectsByIdsQuery } from './queries/node';
import { getCustomerOrderQuery, getOrderMetafieldsQuery } from './queries/order';
import { getCustomerOrderMetafieldsQuery, getCustomerOrdersQuery } from './queries/orders';
import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node';
import { getCustomerOrdersQuery } from './queries/orders';
import { getPageQuery, getPagesQuery } from './queries/page';
import {
getProductQuery,
@@ -53,7 +52,6 @@ import {
Collection,
Connection,
Customer,
File,
FileCreateInput,
Filter,
Fulfillment,
@@ -64,6 +62,7 @@ import {
Metaobject,
Money,
Order,
OrderConfirmationContent,
Page,
PageInfo,
Product,
@@ -86,10 +85,10 @@ import {
ShopifyImageOperation,
ShopifyMenuOperation,
ShopifyMetaobject,
ShopifyMetaobjectOperation,
ShopifyMetaobjectsOperation,
ShopifyMoneyV2,
ShopifyOrder,
ShopifyOrderMetafield,
ShopifyPage,
ShopifyPageOperation,
ShopifyPagesOperation,
@@ -109,6 +108,7 @@ import {
UploadInput,
WarrantyStatus
} from './types';
import getCustomerOrderQuery from './queries/order';
const domain = process.env.SHOPIFY_STORE_DOMAIN
? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://')
@@ -185,7 +185,7 @@ export async function shopifyFetch<T>({
}
}
async function adminFetch<T>({
async function shopifyAdminFetch<T>({
headers,
query,
variables,
@@ -531,8 +531,7 @@ function reshapeOrders(orders: ShopifyOrder[]): any[] | Promise<Order[]> {
}
function reshapeOrder(shopifyOrder: ShopifyOrder): Order {
const reshapeAddress = (address?: ShopifyAddress): Address | undefined => {
if (!address) return undefined;
const reshapeAddress = (address: ShopifyAddress): Address => {
return {
address1: address.address1,
address2: address.address2,
@@ -547,8 +546,7 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order {
};
};
const reshapeMoney = (money?: ShopifyMoneyV2): Money | undefined => {
if (!money) return undefined;
const reshapeMoney = (money: ShopifyMoneyV2): Money => {
return {
amount: money.amount || '0.00',
currencyCode: money.currencyCode || 'USD'
@@ -619,23 +617,42 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order {
totalShipping: reshapeMoney(shopifyOrder.totalShipping),
totalTax: reshapeMoney(shopifyOrder.totalTax),
totalPrice: reshapeMoney(shopifyOrder.totalPrice),
createdAt: shopifyOrder.createdAt
createdAt: shopifyOrder.createdAt,
shippingMethod: {
name: shopifyOrder.shippingLine?.title,
price: reshapeMoney(shopifyOrder.shippingLine.originalPrice)!
}
};
if (shopifyOrder.customer) {
order.customer = reshapeCustomer(shopifyOrder.customer);
}
if (shopifyOrder.shippingLine) {
order.shippingMethod = {
name: shopifyOrder.shippingLine?.title,
price: reshapeMoney(shopifyOrder.shippingLine.originalPrice)!
};
if (shopifyOrder.warrantyStatus) {
order.warrantyStatus = shopifyOrder.warrantyStatus.value as WarrantyStatus;
}
if (shopifyOrder.warrantyActivationDeadline) {
order.warrantyActivationDeadline = new Date(shopifyOrder.warrantyActivationDeadline.value);
}
if (shopifyOrder.orderConfirmation) {
order.orderConfirmation = shopifyOrder.orderConfirmation.value;
}
return order;
}
export function reshapeOrderConfirmationPdf(
metaobject: ShopifyMetaobject
): OrderConfirmationContent {
return {
body: metaobject.fields.find((field) => field.key === 'body')?.value || '',
logo: metaobject.fields.find((field) => field.key === 'logo')?.reference.image!,
color: metaobject.fields.find((field) => field.key === 'color')?.value || '#000000'
};
}
export async function createCart(): Promise<Cart> {
const res = await shopifyFetch<ShopifyCreateCartOperation>({
query: createCartMutation,
@@ -895,10 +912,7 @@ export async function getMetaobject({
id?: string;
handle?: { handle: string; type: string };
}) {
const res = await shopifyFetch<{
data: { metaobject: ShopifyMetaobject };
variables: { id?: string; handle?: { handle: string; type: string } };
}>({
const res = await shopifyFetch<ShopifyMetaobjectOperation>({
query: getMetaobjectQuery,
variables: { id, handle }
});
@@ -906,6 +920,15 @@ export async function getMetaobject({
return res.body.data.metaobject ? reshapeMetaobjects([res.body.data.metaobject])[0] : null;
}
export async function getOrderConfirmationContent(): Promise<OrderConfirmationContent> {
const res = await shopifyFetch<ShopifyMetaobjectOperation>({
query: getMetaobjectQuery,
variables: { handle: { handle: 'order-confirmation-pdf', type: 'order_confirmation_pdf' } }
});
return reshapeOrderConfirmationPdf(res.body.data.metaobject);
}
export async function getPage(handle: string): Promise<Page> {
const res = await shopifyFetch<ShopifyPageOperation>({
query: getPageQuery,
@@ -1064,7 +1087,7 @@ export const getImage = async (id: string): Promise<Image> => {
};
export const stageUploadFile = async (params: UploadInput) => {
const res = await adminFetch<ShopifyStagedUploadOperation>({
const res = await shopifyAdminFetch<ShopifyStagedUploadOperation>({
query: createStageUploads,
variables: { input: [params] }
});
@@ -1080,7 +1103,7 @@ export const uploadFile = async ({ url, formData }: { url: string; formData: For
};
export const createFile = async (params: FileCreateInput) => {
const res = await adminFetch<ShopifyCreateFileOperation>({
const res = await shopifyAdminFetch<ShopifyCreateFileOperation>({
query: createFileMutation,
variables: { files: [params] }
});
@@ -1103,7 +1126,7 @@ export const updateOrderMetafields = async ({
validMetafields.find(({ key }) => (Array.isArray(field) ? field.includes(key) : key === field))
);
const response = await adminFetch<ShopifyUpdateOrderMetafieldsOperation>({
const response = await shopifyAdminFetch<ShopifyUpdateOrderMetafieldsOperation>({
query: updateOrderMetafieldsMutation,
variables: {
input: {
@@ -1124,72 +1147,3 @@ export const updateOrderMetafields = async ({
return response.body.data.orderUpdate.order.id;
};
export const getOrdersMetafields = async (): Promise<{ [key: string]: ShopifyOrderMetafield }> => {
const customer = await getCustomer();
const res = await adminFetch<{
data: {
customer: {
orders: {
nodes: Array<
{
id: string;
} & ShopifyOrderMetafield
>;
};
};
};
variables: {
id: string;
};
}>({
query: getCustomerOrderMetafieldsQuery,
variables: { id: customer.id },
tags: [TAGS.orderMetafields]
});
return res.body.data.customer.orders.nodes.reduce(
(acc, order) => ({
...acc,
[order.id]: order
}),
{} as { [key: string]: ShopifyOrderMetafield }
);
};
export const getOrderMetafields = async (orderId: string): Promise<ShopifyOrderMetafield> => {
const res = await adminFetch<{
data: {
order: {
id: string;
} & ShopifyOrderMetafield;
};
variables: {
id: string;
};
}>({
query: getOrderMetafieldsQuery,
variables: { id: `gid://shopify/Order/${orderId}` },
tags: [TAGS.orderMetafields]
});
const order = res.body.data.order;
return order;
};
export const getFile = async (id: string) => {
const res = await shopifyFetch<{
data: {
node: File;
};
variables: {
id: string;
};
}>({
query: getFileQuery,
variables: { id }
});
return res.body.data.node;
};

View File

@@ -30,6 +30,14 @@ export const getMetaobjectQuery = /* GraphQL */ `
... on Metaobject {
id
}
... on MediaImage {
image {
url
altText
height
width
}
}
}
key
value

View File

@@ -1,8 +1,11 @@
import addressFragment from '../fragments/address';
import lineItemFragment from '../fragments/line-item';
import { orderMetafields } from '../fragments/order';
import orderMetafieldsFragment from '../fragments/order-metafields';
import orderTrasactionFragment from '../fragments/order-transaction';
import priceFragment from '../fragments/price';
// NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer
export const getCustomerOrderQuery = /* GraphQL */ `
const getCustomerOrderQuery = /* GraphQL */ `
query getCustomerOrderQuery($orderId: ID!) {
customer {
emailAddress {
@@ -95,60 +98,7 @@ export const getCustomerOrderQuery = /* GraphQL */ `
...Price
}
}
}
fragment OrderTransaction on OrderTransaction {
id
processedAt
paymentIcon {
id
url
altText
}
paymentDetails {
... on CardPaymentDetails {
last4
cardBrand
}
}
transactionAmount {
presentmentMoney {
...Price
}
}
giftCardDetails {
last4
balance {
...Price
}
}
status
kind
transactionParentId
type
typeDetails {
name
message
}
}
fragment Price on MoneyV2 {
amount
currencyCode
}
fragment Address on CustomerAddress {
id
address1
address2
firstName
lastName
provinceCode: zoneCode
city
zip
countryCodeV2: territoryCode
company
phone: phoneNumber
...OrderMetafields
}
fragment Fulfillment on Fulfillment {
@@ -220,13 +170,10 @@ export const getCustomerOrderQuery = /* GraphQL */ `
}
}
${lineItemFragment}
${addressFragment}
${priceFragment}
${orderTrasactionFragment}
${orderMetafieldsFragment}
`;
export const getOrderMetafieldsQuery = /* GraphQL */ `
query getOrderMetafields($id: ID!) {
order(id: $id) {
...OrderMetafield
}
}
${orderMetafields}
`;
export default getCustomerOrderQuery;

View File

@@ -1,5 +1,4 @@
import customerDetailsFragment from '../fragments/customer-details';
import { orderMetafields } from '../fragments/order';
const customerFragment = `#graphql
`;
@@ -14,16 +13,3 @@ export const getCustomerOrdersQuery = `#graphql
${customerFragment}
${customerDetailsFragment}
`;
export const getCustomerOrderMetafieldsQuery = /* GraphQL */ `
query getCustomerOrderMetafields($id: ID!) {
customer(id: $id) {
orders(first: 20, sortKey: PROCESSED_AT, reverse: true) {
nodes {
...OrderMetafield
}
}
}
}
${orderMetafields}
`;

View File

@@ -141,17 +141,20 @@ export type Order = {
fulfillments: Fulfillment[];
transactions: Transaction[];
lineItems: LineItem[];
shippingAddress?: Address;
billingAddress?: Address;
shippingAddress: Address;
billingAddress: Address;
/** the price of all line items, excluding taxes and surcharges */
subtotal?: Money;
totalShipping?: Money;
totalTax?: Money;
totalPrice?: Money;
shippingMethod?: {
subtotal: Money;
totalShipping: Money;
totalTax: Money;
totalPrice: Money;
shippingMethod: {
name: string;
price: Money;
};
warrantyStatus?: WarrantyStatus | null;
warrantyActivationDeadline?: Date | null;
orderConfirmation?: string | null;
};
export type ShopifyOrder = {
@@ -181,6 +184,9 @@ export type ShopifyOrder = {
requiresShipping: boolean;
shippingLine: ShopifyShippingLine;
note: string | null;
warrantyStatus?: ShopifyMetafield;
warrantyActivationDeadline?: ShopifyMetafield;
orderConfirmation?: ShopifyMetafield;
};
type ShopifyShippingLine = {
@@ -372,16 +378,30 @@ export type ShopifyMetaobject = {
value: string;
reference: {
id: string;
image?: Image;
};
}>;
};
export type ShopifyMetafield = {
id: string;
namespace: string;
key: string;
value: string;
};
export type Metaobject = {
id: string;
type: string;
[key: string]: string;
};
export type OrderConfirmationContent = {
logo: Image;
body: string;
color: string;
};
export type TransmissionType = 'Automatic' | 'Manual';
export type Product = Omit<
@@ -675,8 +695,8 @@ export type ShopifyPagesOperation = {
};
export type ShopifyMetaobjectOperation = {
data: { nodes: ShopifyMetaobject[] };
variables: { ids: string[] };
data: { metaobject: ShopifyMetaobject };
variables: { id?: string; handle?: { handle: string; type: string } };
};
export type ShopifyProductOperation = {