@@ -64,7 +64,7 @@ export const saveRole = async (
6464) : Promise < SessionUser > => {
6565 const token = storage . getToken ( ) ;
6666 // Backend expects RoleEnum, which is typically uppercase. Send uppercase to avoid 400.
67- const body = { role : role . toUpperCase ( ) } as unknown as { role : string } ;
67+ const body = { role : role . toUpperCase ( ) } as unknown as { role : string } ;
6868 const { data} = await api . patch (
6969 "/session/user" ,
7070 body ,
@@ -73,28 +73,87 @@ export const saveRole = async (
7373 return data ;
7474} ;
7575
76- export const startAuthorization = ( userId : string ) : void => {
76+ export const startAuthorization = ( provider : "square" | "clover" | "shopify" , userId : string , shop : string | null ) : void => {
7777 if ( ! userId ) {
78- console . warn ( " Cannot start Square OAuth: missing userId" ) ;
78+ console . warn ( ` Cannot start ${ provider } OAuth: missing userId` ) ;
7979 return ;
8080 }
81- sessionStorage . setItem ( "square_oauth_pending" , "true" ) ;
82- window . location . assign ( `${ BASE_URL } /square/authorize?id=${ userId } ` ) ;
81+ const key = `${ provider } _oauth_pending` ;
82+ sessionStorage . setItem ( key , "true" ) ;
83+
84+ // For Shopify, pass the shop value as provided by the user. Backend will normalize it.
85+ if ( provider === "shopify" ) {
86+ const provided = shop ?? "" ;
87+ if ( ! provided ) {
88+ console . warn ( "Shopify OAuth requires a shop value" ) ;
89+ sessionStorage . removeItem ( key ) ;
90+ return ;
91+ }
92+ // Swagger: /{provider}/authorize?userId=...&shop=...
93+ window . location . assign ( `${ BASE_URL } /${ provider } /authorize?user_id=${ encodeURIComponent ( userId ) } &shop=${ encodeURIComponent ( provided ) } ` ) ;
94+ return ;
95+ }
96+
97+ // Square / Clover don't require extra params
98+ // Swagger: /{provider}/authorize?userId=...
99+ window . location . assign ( `${ BASE_URL } /${ provider } /authorize?user_id=${ encodeURIComponent ( userId ) } ` ) ;
83100} ;
84101
85102// === POS: Only status & refresh ===
86- export const getPosToken = async ( provider : "square" | "clover" , merchantId ?: string | null ) => {
103+ export const getPosToken = async ( provider : "square" | "clover" | "shopify" , merchantId ?: string | null ) => {
87104 if ( ! merchantId ) {
88- console . error ( "NO MERCHANT ID for:" , provider ) ;
105+ console . warn ( `[pos] Skipping ${ provider } token load: missing merchantId` ) ;
106+ return null ;
89107 }
90- const { data} = await api . get ( `/${ provider } /status` , { params : { merchant_id : merchantId } } ) ;
91- return data ;
108+ // Swagger: GET /{provider}/token?merchantId=...
109+ const { data} = await api . get ( `/${ provider } /token` , { params : { merchant_id : merchantId } } ) ;
110+
111+ if ( ! data ) return null ;
112+ const expiry = pickExpiryMs ( data ) ;
113+
114+ return {
115+ merchantId : data . merchantId ?? data . merchant_id ?? "" ,
116+ expiry,
117+ token : data . token ?? undefined ,
118+ } as { merchantId : string ; expiry : number | null ; token ?: string } ;
92119} ;
93120
94- export const refreshPosToken = async ( provider : "square" | "clover" , merchantId ?: string | null ) => {
121+ export const refreshPosToken = async ( provider : "square" | "clover" | "shopify" , merchantId ?: string | null ) => {
95122 if ( ! merchantId ) {
96- console . error ( "NO MERCHANT ID for:" , provider ) ;
123+ console . warn ( `[pos] Skipping ${ provider } token refresh: missing merchantId` ) ;
124+ return null ;
97125 }
126+ // Swagger: POST /{provider}/refresh?merchantId=...
98127 const { data} = await api . post ( `/${ provider } /refresh` , null , { params : { merchant_id : merchantId } } ) ;
99- return data ;
100- } ;
128+ if ( ! data ) return null ;
129+ const expiry = pickExpiryMs ( data ) ;
130+ return {
131+ merchantId : data . merchantId ?? data . merchant_id ?? "" ,
132+ expiry,
133+ token : data . token ?? undefined ,
134+ } as { merchantId : string ; expiry : number | null ; token ?: string } ;
135+ } ;
136+
137+ // === Expiry selection: backend exposes multiple fields; prefer ms when present ===
138+ function pickExpiryMs ( data : any ) : number | null {
139+ // 1) Prefer numeric epoch ms directly
140+ const ms = data ?. expiresAtMs ;
141+ if ( typeof ms === "number" && Number . isFinite ( ms ) && ms > 0 ) return Math . round ( ms ) ;
142+
143+ // 2) If ISO string Instant exists, parse it
144+ const iso = data ?. expiresAt ?? "" ;
145+ if ( typeof iso === "string" && iso . trim ( ) ) {
146+ // Trim fractional seconds beyond ms precision to avoid parse inconsistencies
147+ const trimmed = iso . replace ( / \. ( \d { 3 } ) \d + ( Z | [ + \- ] \d { 2 } : \d { 2 } ) $ / , ".$1$2" ) ;
148+ const parsed = Date . parse ( trimmed ) ;
149+ if ( Number . isFinite ( parsed ) && parsed > 0 ) return parsed ;
150+ }
151+
152+ // 3) As a fallback, if seconds TTL provided, convert relative seconds to absolute ms
153+ const secs = data ?. expiresInSeconds ;
154+ if ( typeof secs === "number" && Number . isFinite ( secs ) && secs > 0 ) {
155+ return Date . now ( ) + Math . round ( secs * 1000 ) ;
156+ }
157+
158+ return null ;
159+ }
0 commit comments