Add shipping cost management and checkout shipping fees.

Business store shipping groups/static rates UI, and customer checkout that quotes and applies shipping on delivery and payment.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-02 21:52:47 +03:30
co-authored by Cursor
parent 95390933a7
commit c59ca4444f
38 changed files with 5353 additions and 98 deletions
+12
View File
@@ -17,6 +17,7 @@ export interface CartItem {
productTitle: string
productNameFa: string
productImage: string | null
categoryIds?: string[]
sku: string
selections: CartItemSelection[]
label: string
@@ -68,9 +69,20 @@ export interface CheckoutPaymentResult {
redirect: PaymentRedirect
}
export interface CheckoutShippingGroupInput {
groupId: string
title?: string
methodId?: string
methodLabel?: string
amount?: number
itemNames?: string[]
}
export interface CheckoutInput {
addressId?: string
shippingAddress?: ShippingAddressInput
deliveryMode?: 'delivery' | 'pickup'
shippingGroups?: CheckoutShippingGroupInput[]
customerNotes?: string
/** Absolute URL for bank callback return (required for e_payment_gate). */
returnUrl?: string
@@ -0,0 +1,69 @@
import { apiRequest } from '../lib/api'
import { getTenantDomain } from '../lib/config'
export const SHIPPING_METHOD_IDS = [
'post',
'tipax',
'mahex',
'freight',
'air',
] as const
export type ShippingMethodId = (typeof SHIPPING_METHOD_IDS)[number]
export const SHIPPING_METHOD_LABELS_FA: Record<ShippingMethodId, string> = {
post: 'پست',
tipax: 'تیپاکس',
mahex: 'ماهکس',
freight: 'باربری',
air: 'ارسال هوایی',
}
export interface PublicShippingCostGroup {
id: string
nameFa: string
nameEn: string
description: string | null
methods: ShippingMethodId[]
defaultAmount: number
isDefault: boolean
categoryIds: string[]
provinceExceptions?: Array<{ provinceId: string; amount: number }>
}
export interface PublicShippingConfig {
enabled: boolean
staticShippingEnabled: boolean
staticShippingAmount: number
staticShippingProvinceExceptions: Array<{ provinceId: string; amount: number }>
items: PublicShippingCostGroup[]
}
export async function getPublicShippingConfig(signal?: AbortSignal) {
const host = encodeURIComponent(getTenantDomain())
return apiRequest<PublicShippingConfig>(`/tenants/${host}/shipping-cost-groups`, {
signal,
})
}
export function resolveProvinceAmount(
defaultAmount: number,
exceptions: Array<{ provinceId: string; amount: number }> | undefined,
provinceId: string | null,
) {
if (!provinceId || !exceptions?.length) return defaultAmount
const match = exceptions.find((item) => item.provinceId === provinceId)
return match ? match.amount : defaultAmount
}
export function findGroupForCategoryIds(
groups: PublicShippingCostGroup[],
categoryIds: string[],
) {
const categorySet = new Set(categoryIds)
const byCategory = groups.filter((group) =>
group.categoryIds.some((id) => categorySet.has(id)),
)
if (byCategory.length === 0) return null
return byCategory.find((group) => !group.isDefault) ?? byCategory[0] ?? null
}