@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.
Files changed (73) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +112 -0
  3. package/dist/index.d.mts +1891 -0
  4. package/dist/index.d.ts +1891 -0
  5. package/dist/index.js +3540 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +3314 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +59 -0
  10. package/src/client/authClient.ts +135 -0
  11. package/src/client/graphqlClient.ts +53 -0
  12. package/src/client/httpClient.ts +298 -0
  13. package/src/config/index.ts +102 -0
  14. package/src/graphql/collections.graphql.ts +56 -0
  15. package/src/graphql/index.ts +7 -0
  16. package/src/graphql/products.graphql.ts +710 -0
  17. package/src/graphql/wishlist.graphql.ts +33 -0
  18. package/src/hooks/index.ts +26 -0
  19. package/src/hooks/use-auth.ts +182 -0
  20. package/src/hooks/use-bespoke-availability.ts +20 -0
  21. package/src/hooks/use-cart.ts +207 -0
  22. package/src/hooks/use-cms.ts +165 -0
  23. package/src/hooks/use-collections.ts +40 -0
  24. package/src/hooks/use-consultation-stream.ts +141 -0
  25. package/src/hooks/use-consultations.ts +76 -0
  26. package/src/hooks/use-countries.ts +24 -0
  27. package/src/hooks/use-currencies.ts +27 -0
  28. package/src/hooks/use-google-translate.ts +164 -0
  29. package/src/hooks/use-is-mounted.ts +9 -0
  30. package/src/hooks/use-languages.ts +33 -0
  31. package/src/hooks/use-order-stream.ts +195 -0
  32. package/src/hooks/use-orders.ts +64 -0
  33. package/src/hooks/use-payment.ts +68 -0
  34. package/src/hooks/use-products.ts +113 -0
  35. package/src/hooks/use-service-booking.ts +118 -0
  36. package/src/hooks/use-states.ts +28 -0
  37. package/src/hooks/use-wishlist.ts +121 -0
  38. package/src/index.ts +65 -0
  39. package/src/services/app.service.ts +17 -0
  40. package/src/services/auth.service.ts +332 -0
  41. package/src/services/bespoke.service.ts +17 -0
  42. package/src/services/cart.service.ts +245 -0
  43. package/src/services/cms.service.ts +112 -0
  44. package/src/services/collection.service.ts +77 -0
  45. package/src/services/config.service.ts +42 -0
  46. package/src/services/consultation.service.ts +51 -0
  47. package/src/services/index.ts +21 -0
  48. package/src/services/logistics.service.ts +95 -0
  49. package/src/services/notifications.service.ts +50 -0
  50. package/src/services/order.service.ts +169 -0
  51. package/src/services/payment.service.ts +127 -0
  52. package/src/services/product.service.ts +678 -0
  53. package/src/services/service-booking.service.ts +86 -0
  54. package/src/services/wishlist.service.ts +84 -0
  55. package/src/types/auth/index.ts +126 -0
  56. package/src/types/bespoke/index.ts +39 -0
  57. package/src/types/business/index.ts +6 -0
  58. package/src/types/cms/index.ts +77 -0
  59. package/src/types/consultations/index.ts +79 -0
  60. package/src/types/index.ts +27 -0
  61. package/src/types/media/index.ts +41 -0
  62. package/src/types/payments/index.ts +120 -0
  63. package/src/types/services/index.ts +80 -0
  64. package/src/types/store/index.ts +282 -0
  65. package/src/types/store/shipping.ts +38 -0
  66. package/src/types/ui/index.ts +32 -0
  67. package/src/utils/cache.ts +33 -0
  68. package/src/utils/checkout.ts +39 -0
  69. package/src/utils/customer.ts +20 -0
  70. package/src/utils/error-handler.ts +14 -0
  71. package/src/utils/index.ts +10 -0
  72. package/src/utils/logger.ts +79 -0
  73. 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