@kasuvia/sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +112 -0
- package/dist/index.d.mts +1891 -0
- package/dist/index.d.ts +1891 -0
- package/dist/index.js +3540 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3314 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
- package/src/client/authClient.ts +135 -0
- package/src/client/graphqlClient.ts +53 -0
- package/src/client/httpClient.ts +298 -0
- package/src/config/index.ts +102 -0
- package/src/graphql/collections.graphql.ts +56 -0
- package/src/graphql/index.ts +7 -0
- package/src/graphql/products.graphql.ts +710 -0
- package/src/graphql/wishlist.graphql.ts +33 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/use-auth.ts +182 -0
- package/src/hooks/use-bespoke-availability.ts +20 -0
- package/src/hooks/use-cart.ts +207 -0
- package/src/hooks/use-cms.ts +165 -0
- package/src/hooks/use-collections.ts +40 -0
- package/src/hooks/use-consultation-stream.ts +141 -0
- package/src/hooks/use-consultations.ts +76 -0
- package/src/hooks/use-countries.ts +24 -0
- package/src/hooks/use-currencies.ts +27 -0
- package/src/hooks/use-google-translate.ts +164 -0
- package/src/hooks/use-is-mounted.ts +9 -0
- package/src/hooks/use-languages.ts +33 -0
- package/src/hooks/use-order-stream.ts +195 -0
- package/src/hooks/use-orders.ts +64 -0
- package/src/hooks/use-payment.ts +68 -0
- package/src/hooks/use-products.ts +113 -0
- package/src/hooks/use-service-booking.ts +118 -0
- package/src/hooks/use-states.ts +28 -0
- package/src/hooks/use-wishlist.ts +121 -0
- package/src/index.ts +65 -0
- package/src/services/app.service.ts +17 -0
- package/src/services/auth.service.ts +332 -0
- package/src/services/bespoke.service.ts +17 -0
- package/src/services/cart.service.ts +245 -0
- package/src/services/cms.service.ts +112 -0
- package/src/services/collection.service.ts +77 -0
- package/src/services/config.service.ts +42 -0
- package/src/services/consultation.service.ts +51 -0
- package/src/services/index.ts +21 -0
- package/src/services/logistics.service.ts +95 -0
- package/src/services/notifications.service.ts +50 -0
- package/src/services/order.service.ts +169 -0
- package/src/services/payment.service.ts +127 -0
- package/src/services/product.service.ts +678 -0
- package/src/services/service-booking.service.ts +86 -0
- package/src/services/wishlist.service.ts +84 -0
- package/src/types/auth/index.ts +126 -0
- package/src/types/bespoke/index.ts +39 -0
- package/src/types/business/index.ts +6 -0
- package/src/types/cms/index.ts +77 -0
- package/src/types/consultations/index.ts +79 -0
- package/src/types/index.ts +27 -0
- package/src/types/media/index.ts +41 -0
- package/src/types/payments/index.ts +120 -0
- package/src/types/services/index.ts +80 -0
- package/src/types/store/index.ts +282 -0
- package/src/types/store/shipping.ts +38 -0
- package/src/types/ui/index.ts +32 -0
- package/src/utils/cache.ts +33 -0
- package/src/utils/checkout.ts +39 -0
- package/src/utils/customer.ts +20 -0
- package/src/utils/error-handler.ts +14 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/logger.ts +79 -0
- package/src/utils/sanitize-url.ts +55 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection Service
|
|
3
|
+
*
|
|
4
|
+
* Handles collection operations using GraphQL.
|
|
5
|
+
*/
|
|
6
|
+
import { getGraphQLClient } from '../client/graphqlClient'
|
|
7
|
+
import type { Collection, PaginatedResponse, ListParams } from '../types/store'
|
|
8
|
+
|
|
9
|
+
const GET_STOREFRONT_COLLECTIONS = `
|
|
10
|
+
query GetStorefrontCollections($page: Int!, $pageSize: Int!) {
|
|
11
|
+
storefront_collections(page: $page, pageSize: $pageSize) {
|
|
12
|
+
items {
|
|
13
|
+
id
|
|
14
|
+
business_id
|
|
15
|
+
name
|
|
16
|
+
slug
|
|
17
|
+
description
|
|
18
|
+
image { url alt id }
|
|
19
|
+
collection_type
|
|
20
|
+
rules
|
|
21
|
+
status
|
|
22
|
+
sort_order
|
|
23
|
+
created_at
|
|
24
|
+
updated_at
|
|
25
|
+
}
|
|
26
|
+
total
|
|
27
|
+
page
|
|
28
|
+
page_size
|
|
29
|
+
total_pages
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
const GET_STOREFRONT_COLLECTION_BY_SLUG = `
|
|
35
|
+
query GetStorefrontCollectionBySlug($slug: String!) {
|
|
36
|
+
storefront_collection_by_slug(slug: $slug) {
|
|
37
|
+
id
|
|
38
|
+
business_id
|
|
39
|
+
name
|
|
40
|
+
slug
|
|
41
|
+
description
|
|
42
|
+
image { url alt id }
|
|
43
|
+
collection_type
|
|
44
|
+
rules
|
|
45
|
+
status
|
|
46
|
+
sort_order
|
|
47
|
+
created_at
|
|
48
|
+
updated_at
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetch paginated active collections for the storefront.
|
|
55
|
+
*/
|
|
56
|
+
export async function getStorefrontCollections(
|
|
57
|
+
params?: ListParams
|
|
58
|
+
): Promise<PaginatedResponse<Collection>> {
|
|
59
|
+
const client = getGraphQLClient()
|
|
60
|
+
const variables = {
|
|
61
|
+
page: params?.page || 1,
|
|
62
|
+
pageSize: params?.pageSize || 20,
|
|
63
|
+
}
|
|
64
|
+
const data = await client.request(GET_STOREFRONT_COLLECTIONS, variables)
|
|
65
|
+
return data.storefront_collections
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fetch a single collection by its slug.
|
|
70
|
+
*/
|
|
71
|
+
export async function getStorefrontCollectionBySlug(
|
|
72
|
+
slug: string
|
|
73
|
+
): Promise<Collection> {
|
|
74
|
+
const client = getGraphQLClient()
|
|
75
|
+
const data = await client.request(GET_STOREFRONT_COLLECTION_BY_SLUG, { slug })
|
|
76
|
+
return data.storefront_collection_by_slug
|
|
77
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Service
|
|
3
|
+
*
|
|
4
|
+
* Handles configuration endpoints (languages, currencies, locales, business config).
|
|
5
|
+
*/
|
|
6
|
+
import { paymentAxios, publicStoreAxios } from '../client/httpClient'
|
|
7
|
+
import type { BackendLanguage, SupportedLanguagesResponse, SupportedCurrenciesResponse } from '../types/payments'
|
|
8
|
+
import type { BusinessConfig } from '../types/business'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fetch supported languages from the payment system.
|
|
12
|
+
* Resolves to: {luxePaymentBackendUrl}/config/supported-languages
|
|
13
|
+
*/
|
|
14
|
+
export async function getSupportedLanguages(): Promise<SupportedLanguagesResponse> {
|
|
15
|
+
const { data } = await paymentAxios.get<SupportedLanguagesResponse>('/config/supported-languages')
|
|
16
|
+
return data
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch supported currencies from the payment system.
|
|
21
|
+
* Resolves to: {luxePaymentBackendUrl}/config/supported-currencies
|
|
22
|
+
*/
|
|
23
|
+
export async function getSupportedCurrencies(): Promise<SupportedCurrenciesResponse> {
|
|
24
|
+
const { data } = await paymentAxios.get<SupportedCurrenciesResponse>('/config/supported-currencies')
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fetch the business's public configuration, including store_currency.
|
|
30
|
+
* Uses publicStoreAxios (X-Storefront-Key + X-Business-ID — no user JWT required).
|
|
31
|
+
* Resolves to: {luxeStoreBackendUrl}/business/profile
|
|
32
|
+
*/
|
|
33
|
+
export async function getBusinessConfig(): Promise<BusinessConfig> {
|
|
34
|
+
const { data } = await publicStoreAxios.get<BusinessConfig>('business/profile')
|
|
35
|
+
return data
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const configService = {
|
|
39
|
+
getSupportedLanguages,
|
|
40
|
+
getSupportedCurrencies,
|
|
41
|
+
getBusinessConfig,
|
|
42
|
+
} as const
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consultation Service
|
|
3
|
+
*
|
|
4
|
+
* Handles consultation operations for bespoke and service products.
|
|
5
|
+
*/
|
|
6
|
+
import { storeManagementAxios } from '../client/httpClient'
|
|
7
|
+
import type {
|
|
8
|
+
Consultation,
|
|
9
|
+
ConsultationCreatePayload,
|
|
10
|
+
ConsultationMessagePayload,
|
|
11
|
+
ConsultationQuoteAcceptPayload,
|
|
12
|
+
} from '../types/consultations'
|
|
13
|
+
|
|
14
|
+
export async function createConsultation(payload: ConsultationCreatePayload): Promise<Consultation> {
|
|
15
|
+
const { data } = await storeManagementAxios.post<Consultation>('social/consultations/', payload)
|
|
16
|
+
return data
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function getConsultations(): Promise<Consultation[]> {
|
|
20
|
+
const { data } = await storeManagementAxios.get<Consultation[]>('social/consultations/')
|
|
21
|
+
return data
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getConsultation(id: string): Promise<Consultation> {
|
|
25
|
+
const { data } = await storeManagementAxios.get<Consultation>(`social/consultations/${id}`)
|
|
26
|
+
return data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function sendMessage(id: string, payload: ConsultationMessagePayload): Promise<void> {
|
|
30
|
+
await storeManagementAxios.post(`social/consultations/${id}/messages`, payload)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function acceptQuote(id: string, payload: ConsultationQuoteAcceptPayload): Promise<Consultation> {
|
|
34
|
+
const { data } = await storeManagementAxios.post<Consultation>(`social/consultations/${id}/accept-quote`, payload)
|
|
35
|
+
return data
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function rejectQuote(id: string, reason?: string): Promise<Consultation> {
|
|
39
|
+
const { data } = await storeManagementAxios.post<Consultation>(`social/consultations/${id}/reject-quote`, { reason })
|
|
40
|
+
return data
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Service object export for backward compatibility
|
|
44
|
+
export const consultationService = {
|
|
45
|
+
createConsultation,
|
|
46
|
+
getConsultations,
|
|
47
|
+
getConsultation,
|
|
48
|
+
sendMessage,
|
|
49
|
+
acceptQuote,
|
|
50
|
+
rejectQuote,
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Services Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Central export point for all service modules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './cart.service'
|
|
8
|
+
export * from './order.service'
|
|
9
|
+
export * from './auth.service'
|
|
10
|
+
export * from './product.service'
|
|
11
|
+
export * from './collection.service'
|
|
12
|
+
export * from './wishlist.service'
|
|
13
|
+
export * from './payment.service'
|
|
14
|
+
export * from './consultation.service'
|
|
15
|
+
export * from './logistics.service'
|
|
16
|
+
export * from './bespoke.service'
|
|
17
|
+
export * from './service-booking.service'
|
|
18
|
+
export * from './notifications.service'
|
|
19
|
+
export * from './app.service'
|
|
20
|
+
export * from './config.service'
|
|
21
|
+
export * from './cms.service'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logistics Service
|
|
3
|
+
*
|
|
4
|
+
* Handles shipping rates and address validation.
|
|
5
|
+
*/
|
|
6
|
+
import { storeManagementAxios } from '../client/httpClient'
|
|
7
|
+
import type { ShippingRatesResponse } from '../types/store/shipping'
|
|
8
|
+
import type { Product, ProductVariant } from '../types/store'
|
|
9
|
+
|
|
10
|
+
export interface CartItemPayload {
|
|
11
|
+
product: {
|
|
12
|
+
name: string
|
|
13
|
+
price: number
|
|
14
|
+
weight: number
|
|
15
|
+
}
|
|
16
|
+
variant: ProductVariant | null
|
|
17
|
+
quantity: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RateAddress {
|
|
21
|
+
city: string
|
|
22
|
+
state: string
|
|
23
|
+
country: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ShippingRatesRequest {
|
|
27
|
+
cart_items: CartItemPayload[]
|
|
28
|
+
origin_address: RateAddress
|
|
29
|
+
destination_address: RateAddress
|
|
30
|
+
currency?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Fetch shipping rates for a cart.
|
|
35
|
+
*/
|
|
36
|
+
export async function getShippingRates(
|
|
37
|
+
payload: ShippingRatesRequest
|
|
38
|
+
): Promise<ShippingRatesResponse> {
|
|
39
|
+
const { data } = await storeManagementAxios.post<ShippingRatesResponse>(
|
|
40
|
+
'logistics/shipping/rates',
|
|
41
|
+
payload
|
|
42
|
+
)
|
|
43
|
+
return data
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch the seller's registered shipping address.
|
|
48
|
+
*/
|
|
49
|
+
export async function getSellerShippingAddress(): Promise<{
|
|
50
|
+
terminal_address_id: string | null
|
|
51
|
+
address_details: {
|
|
52
|
+
city: string
|
|
53
|
+
state: string
|
|
54
|
+
country: string
|
|
55
|
+
line1: string
|
|
56
|
+
line2: string
|
|
57
|
+
zip: string
|
|
58
|
+
} | null
|
|
59
|
+
business_id: string
|
|
60
|
+
message: string
|
|
61
|
+
}> {
|
|
62
|
+
const { data } = await storeManagementAxios.get('logistics/shipping-address')
|
|
63
|
+
return data
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validate a shipping address.
|
|
68
|
+
*/
|
|
69
|
+
export async function validateShippingAddress(
|
|
70
|
+
payload: {
|
|
71
|
+
first_name: string
|
|
72
|
+
last_name: string
|
|
73
|
+
phone: string
|
|
74
|
+
address_line1: string
|
|
75
|
+
address_line2?: string
|
|
76
|
+
city: string
|
|
77
|
+
state: string
|
|
78
|
+
country: string
|
|
79
|
+
postal_code?: string
|
|
80
|
+
}
|
|
81
|
+
): Promise<{
|
|
82
|
+
valid: boolean
|
|
83
|
+
message?: string
|
|
84
|
+
data?: Array<{ name: string; city_id: string; stateCode: string; countryCode: string }>
|
|
85
|
+
}> {
|
|
86
|
+
const { data } = await storeManagementAxios.post('logistics/shipping-address/validate', payload)
|
|
87
|
+
return data
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Service object for backward compatibility
|
|
91
|
+
export const logisticsService = {
|
|
92
|
+
getShippingRates,
|
|
93
|
+
getSellerShippingAddress,
|
|
94
|
+
validateShippingAddress,
|
|
95
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification Service
|
|
3
|
+
*
|
|
4
|
+
* Handles contact form submissions and email notifications.
|
|
5
|
+
*/
|
|
6
|
+
import { notificationsAxios } from '../client/httpClient'
|
|
7
|
+
|
|
8
|
+
export interface ContactFormData {
|
|
9
|
+
name: string
|
|
10
|
+
email: string
|
|
11
|
+
subject: string
|
|
12
|
+
message: string
|
|
13
|
+
source?: string
|
|
14
|
+
company_name?: string
|
|
15
|
+
current_year?: number
|
|
16
|
+
turnstile_token?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Send contact email to Luxe team.
|
|
21
|
+
* POST /contact/send-contact-email
|
|
22
|
+
*/
|
|
23
|
+
export async function sendContactEmail(
|
|
24
|
+
payload: ContactFormData
|
|
25
|
+
): Promise<{ detail: string }> {
|
|
26
|
+
const { data } = await notificationsAxios.post<{ detail: string }>(
|
|
27
|
+
'contact/send-contact-email',
|
|
28
|
+
payload
|
|
29
|
+
)
|
|
30
|
+
return data
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Send confirmation email to user.
|
|
35
|
+
* POST /contact/send-contact-confirmation
|
|
36
|
+
*/
|
|
37
|
+
export async function sendContactConfirmation(
|
|
38
|
+
payload: Omit<ContactFormData, 'turnstile_token'>
|
|
39
|
+
): Promise<{ detail: string }> {
|
|
40
|
+
const { data } = await notificationsAxios.post<{ detail: string }>(
|
|
41
|
+
'contact/send-contact-confirmation',
|
|
42
|
+
payload
|
|
43
|
+
)
|
|
44
|
+
return data
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const notificationsService = {
|
|
48
|
+
sendContactEmail,
|
|
49
|
+
sendContactConfirmation,
|
|
50
|
+
} as const
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Service
|
|
3
|
+
*
|
|
4
|
+
* Handles order operations including creation, lookup, and tracking.
|
|
5
|
+
*/
|
|
6
|
+
import { storeManagementAxios, publicStoreAxios } from '../client/httpClient'
|
|
7
|
+
import type { Order, OrderStatus, FindOrderParams, CreateOrderParams, OrderItem } from '../types/store'
|
|
8
|
+
import type { DeliveryAddress } from '../types/store'
|
|
9
|
+
|
|
10
|
+
export type { FindOrderParams, CreateOrderParams, OrderStatus }
|
|
11
|
+
|
|
12
|
+
interface RawOrderItem {
|
|
13
|
+
id: string
|
|
14
|
+
product_name?: string
|
|
15
|
+
productName?: string
|
|
16
|
+
image?: string
|
|
17
|
+
quantity?: number
|
|
18
|
+
unit_price?: number
|
|
19
|
+
price?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface RawShippingAddress {
|
|
23
|
+
first_name?: string
|
|
24
|
+
last_name?: string
|
|
25
|
+
phone?: string
|
|
26
|
+
street?: string
|
|
27
|
+
city?: string
|
|
28
|
+
state?: string
|
|
29
|
+
country?: string
|
|
30
|
+
postal?: string
|
|
31
|
+
lga?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface RawOrder {
|
|
35
|
+
id: string
|
|
36
|
+
display_id?: string
|
|
37
|
+
items?: RawOrderItem[]
|
|
38
|
+
total_amount?: number
|
|
39
|
+
total?: number
|
|
40
|
+
status: OrderStatus
|
|
41
|
+
customer_email?: string
|
|
42
|
+
customer_name?: string
|
|
43
|
+
guest_email?: string
|
|
44
|
+
guest_phone?: string
|
|
45
|
+
guest_name?: string
|
|
46
|
+
guest_token?: string
|
|
47
|
+
shipping_address?: RawShippingAddress | string | null
|
|
48
|
+
delivery_method?: string
|
|
49
|
+
tracking_number?: string
|
|
50
|
+
delivery_status?: string
|
|
51
|
+
carrier_name?: string
|
|
52
|
+
terminal_shipment_id?: string
|
|
53
|
+
product_type?: string
|
|
54
|
+
bespoke_stage?: string
|
|
55
|
+
bespoke_brief?: string
|
|
56
|
+
created_at?: string
|
|
57
|
+
updated_at?: string
|
|
58
|
+
cancellation_reason?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deriveCustomerName(raw: RawOrder): string {
|
|
62
|
+
if (raw.customer_name) return raw.customer_name
|
|
63
|
+
if (raw.guest_name) return raw.guest_name
|
|
64
|
+
|
|
65
|
+
const addr = typeof raw.shipping_address === 'object' && raw.shipping_address !== null
|
|
66
|
+
? raw.shipping_address as RawShippingAddress
|
|
67
|
+
: null
|
|
68
|
+
if (addr) {
|
|
69
|
+
const full = `${addr.first_name ?? ''} ${addr.last_name ?? ''}`.trim()
|
|
70
|
+
if (full) return full
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const email = raw.guest_email ?? raw.customer_email ?? ''
|
|
74
|
+
if (email) return email.split('@')[0]
|
|
75
|
+
|
|
76
|
+
return ''
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normaliseStorefrontOrder(raw: RawOrder): Order {
|
|
80
|
+
const items: OrderItem[] = (raw.items ?? []).map((item) => ({
|
|
81
|
+
id: item.id,
|
|
82
|
+
productName: item.product_name ?? item.productName ?? '',
|
|
83
|
+
image: item.image ?? '',
|
|
84
|
+
quantity: item.quantity ?? 1,
|
|
85
|
+
price: item.unit_price ?? item.price ?? 0,
|
|
86
|
+
}))
|
|
87
|
+
|
|
88
|
+
const addr = typeof raw.shipping_address === 'object' && raw.shipping_address !== null
|
|
89
|
+
? raw.shipping_address as RawShippingAddress
|
|
90
|
+
: null
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: raw.id,
|
|
94
|
+
display_id: raw.display_id,
|
|
95
|
+
items,
|
|
96
|
+
total: raw.total_amount ?? raw.total ?? 0,
|
|
97
|
+
status: raw.status,
|
|
98
|
+
customerEmail: raw.guest_email ?? raw.customer_email ?? '',
|
|
99
|
+
customerName: deriveCustomerName(raw),
|
|
100
|
+
createdAt: raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
|
|
101
|
+
updatedAt: raw.updated_at ? new Date(raw.updated_at).getTime() : Date.now(),
|
|
102
|
+
deliveryMethod: raw.delivery_method,
|
|
103
|
+
trackingNumber: raw.tracking_number,
|
|
104
|
+
deliveryAddress: typeof raw.shipping_address === 'string'
|
|
105
|
+
? raw.shipping_address
|
|
106
|
+
: addr
|
|
107
|
+
? `${addr.street}, ${addr.city}, ${addr.state}, ${addr.country}`
|
|
108
|
+
: undefined,
|
|
109
|
+
shippingAddress: addr
|
|
110
|
+
? {
|
|
111
|
+
first_name: addr.first_name ?? '',
|
|
112
|
+
last_name: addr.last_name ?? '',
|
|
113
|
+
phone: addr.phone ?? '',
|
|
114
|
+
street: addr.street ?? '',
|
|
115
|
+
city: addr.city ?? '',
|
|
116
|
+
state: addr.state ?? '',
|
|
117
|
+
lga: addr.lga ?? '',
|
|
118
|
+
country: addr.country ?? '',
|
|
119
|
+
postal: addr.postal ?? '',
|
|
120
|
+
}
|
|
121
|
+
: undefined,
|
|
122
|
+
cancellationReason: raw.cancellation_reason,
|
|
123
|
+
hasBespoke: raw.product_type === 'bespoke',
|
|
124
|
+
bespokeStage: raw.bespoke_stage as Order['bespokeStage'],
|
|
125
|
+
bespokeBrief: raw.bespoke_brief,
|
|
126
|
+
deliveryStatus: raw.delivery_status,
|
|
127
|
+
carrierName: raw.carrier_name,
|
|
128
|
+
terminalShipmentId: raw.terminal_shipment_id,
|
|
129
|
+
guestToken: raw.guest_token,
|
|
130
|
+
guestEmail: raw.guest_email,
|
|
131
|
+
guestPhone: raw.guest_phone,
|
|
132
|
+
guestName: raw.guest_name,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function createOrder(payload: CreateOrderParams): Promise<Order> {
|
|
137
|
+
const { data } = await storeManagementAxios.post<RawOrder>('orders/storefront', payload)
|
|
138
|
+
return normaliseStorefrontOrder(data)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function findOrder(params: FindOrderParams): Promise<Order> {
|
|
142
|
+
const { data } = await publicStoreAxios.get<RawOrder>('orders/find/', { params })
|
|
143
|
+
return normaliseStorefrontOrder(data)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function findOrderByToken(token: string): Promise<Order> {
|
|
147
|
+
const { data } = await publicStoreAxios.get<RawOrder>('orders/find/', {
|
|
148
|
+
params: { guest_token: token },
|
|
149
|
+
})
|
|
150
|
+
return normaliseStorefrontOrder(data)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function getOrders(params?: { email?: string; page?: number; page_size?: number }): Promise<Order[]> {
|
|
154
|
+
const { data } = await storeManagementAxios.get<RawOrder[] | { items: RawOrder[] }>('orders/', { params })
|
|
155
|
+
const items: RawOrder[] = Array.isArray(data) ? data : (data.items ?? [])
|
|
156
|
+
return items.map(normaliseStorefrontOrder)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function cancelOrder(orderId: string, reason: string): Promise<Order> {
|
|
160
|
+
const { data } = await storeManagementAxios.post<RawOrder>(`orders/${orderId}/cancel/`, { reason })
|
|
161
|
+
return normaliseStorefrontOrder(data)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function trackOrder(orderId: string, token?: string): Promise<Order> {
|
|
165
|
+
const { data } = await publicStoreAxios.get<RawOrder>(`orders/track/${orderId}`, {
|
|
166
|
+
params: token ? { token } : undefined
|
|
167
|
+
})
|
|
168
|
+
return normaliseStorefrontOrder(data)
|
|
169
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Service
|
|
3
|
+
*
|
|
4
|
+
* Customer-facing payment API for payment initiation and verification.
|
|
5
|
+
*/
|
|
6
|
+
import { paymentAxios } from '../client/httpClient'
|
|
7
|
+
import type {
|
|
8
|
+
ConversionQuote,
|
|
9
|
+
DVACreateRequest,
|
|
10
|
+
DVADetails,
|
|
11
|
+
PaymentInitiateRequest,
|
|
12
|
+
PaymentInitiateResponse,
|
|
13
|
+
TransactionVerifyResponse,
|
|
14
|
+
} from '../types/payments/index'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a Monnify Dedicated Virtual Account so the customer can pay via
|
|
18
|
+
* bank transfer. Note: Monnify DVA only supports NGN currency - the backend
|
|
19
|
+
* will reject non-NGN requests for this payment method.
|
|
20
|
+
*/
|
|
21
|
+
export async function createDedicatedAccount(
|
|
22
|
+
request: DVACreateRequest
|
|
23
|
+
): Promise<DVADetails> {
|
|
24
|
+
const isValidUUID = (str: string) => {
|
|
25
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
26
|
+
return uuidRegex.test(str);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { data } = await paymentAxios.post<DVADetails>(
|
|
30
|
+
'payments/dedicated-account',
|
|
31
|
+
{
|
|
32
|
+
order_id: request.order_id && isValidUUID(request.order_id) ? request.order_id : undefined,
|
|
33
|
+
amount: request.amount,
|
|
34
|
+
customer_email: request.customer_email,
|
|
35
|
+
customer_name: request.customer_name,
|
|
36
|
+
currency: request.currency,
|
|
37
|
+
idempotency_key: request.idempotency_key,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
return data
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Initiate a card payment (customer → business owner).
|
|
45
|
+
* Routes to Monnify (NGN) or Flutterwave (non-NGN) on the backend.
|
|
46
|
+
* Returns a checkout_url to redirect the customer to the gateway.
|
|
47
|
+
*/
|
|
48
|
+
export async function initiatePayment(
|
|
49
|
+
request: PaymentInitiateRequest
|
|
50
|
+
): Promise<PaymentInitiateResponse> {
|
|
51
|
+
const isValidUUID = (str: string) => {
|
|
52
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
53
|
+
return uuidRegex.test(str);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const validOrderId = request.order_id && isValidUUID(request.order_id) ? request.order_id : undefined;
|
|
57
|
+
|
|
58
|
+
const { data } = await paymentAxios.post<{
|
|
59
|
+
id: string
|
|
60
|
+
gateway_ref: string
|
|
61
|
+
gateway: string
|
|
62
|
+
status: string
|
|
63
|
+
metadata_json?: { checkout_url?: string }
|
|
64
|
+
}>('payments/storefront/initiate', {
|
|
65
|
+
amount: request.amount,
|
|
66
|
+
currency: request.currency,
|
|
67
|
+
order_id: validOrderId,
|
|
68
|
+
quote_id: request.quote_id,
|
|
69
|
+
metadata_json: {
|
|
70
|
+
customer_email: request.customer_email,
|
|
71
|
+
customer_name: request.customer_name ?? '',
|
|
72
|
+
order_id: validOrderId,
|
|
73
|
+
...(request.metadata ?? {}),
|
|
74
|
+
},
|
|
75
|
+
idempotency_key: request.idempotency_key,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: data.id,
|
|
80
|
+
gateway_ref: data.gateway_ref,
|
|
81
|
+
gateway: data.gateway,
|
|
82
|
+
status: data.status,
|
|
83
|
+
checkout_url: data.metadata_json?.checkout_url ?? '',
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Request a 15-minute rate-locked conversion quote for non-NGN transactions.
|
|
89
|
+
*/
|
|
90
|
+
export async function requestQuote(
|
|
91
|
+
amount: number
|
|
92
|
+
): Promise<ConversionQuote> {
|
|
93
|
+
const { data } = await paymentAxios.post<ConversionQuote>('payments/quote', { amount })
|
|
94
|
+
return data
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Verify a payment after the gateway redirects back to /checkout/verify.
|
|
99
|
+
*/
|
|
100
|
+
export async function verifyTransaction(
|
|
101
|
+
reference: string
|
|
102
|
+
): Promise<TransactionVerifyResponse> {
|
|
103
|
+
const { data } = await paymentAxios.post<TransactionVerifyResponse>(
|
|
104
|
+
`payments/verify/${reference}`
|
|
105
|
+
)
|
|
106
|
+
return data
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Poll transaction status by internal transaction ID.
|
|
111
|
+
*/
|
|
112
|
+
export async function getTransactionStatus(
|
|
113
|
+
transactionId: string
|
|
114
|
+
): Promise<TransactionVerifyResponse> {
|
|
115
|
+
const { data } = await paymentAxios.get<TransactionVerifyResponse>(
|
|
116
|
+
`payments/transactions/${transactionId}`
|
|
117
|
+
)
|
|
118
|
+
return data
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const paymentService = {
|
|
122
|
+
createDedicatedAccount,
|
|
123
|
+
initiatePayment,
|
|
124
|
+
requestQuote,
|
|
125
|
+
verifyTransaction,
|
|
126
|
+
getTransactionStatus,
|
|
127
|
+
} as const
|