public with sharing class salesforceAuthenticationController { list listofConfiguration = new list(); String clientIdProduction; String clientSecretproduction; String salesforceUrl = system.url.getsalesforcebaseurl().toexternalform(); String myURL = Url.getOrgDomainUrl().toExternalForm(); String Viewmode; public boolean isConnectionSuccessful{get;set;} public Pagereference handleAuthentication(){ PageReference pr; if(ShopifyController.isSiteSupportedOrg()){ System.debug('>>> THIS IS SITE SUPPORTED ORG'); pr = authenticate(); }else{ System.debug('>>> THIS IS NON SITE SUPPORTED ORG'); pr = authenticateSalesforce(); } return pr; // return null; } public pagereference authenticate(){ system.debug('>>> SITE SUPPORTED ORG METHOD'); String isLwcApp = 'false'; String query = 'Select '+CheckAccessibility.isAccessible('id,name,HIC_ShopifySync__ClientId__c,HIC_ShopifySync__ClientSecret__c','HIC_ShopifySync__Configuration__c')+' from HIC_ShopifySync__Configuration__c where '+CheckAccessibility.isAccessible('name','HIC_ShopifySync__Configuration__c')+' = \'Default\' LIMIT 1'; String finalQuery = String.escapeSingleQuotes(query).remove('\\'); listofConfiguration = database.query(finalQuery); //listofConfiguration = [select id,name,HIC_Shopi fySync__ClientId__c,HIC_ShopifySync__ClientSecret__c from HIC_ShopifySync__Configuration__c where name = 'Default' limit 1]; if(listofConfiguration.isEmpty()){ return null; } clientIdProduction = listofConfiguration[0].HIC_ShopifySync__ClientId__c; clientSecretproduction = listofConfiguration[0].HIC_ShopifySync__ClientSecret__c; if(apexpages.currentpage().getparameters().containsKey('viewmode') && apexpages.currentpage().getparameters().get('viewmode') != null && apexpages.currentpage().getparameters().get('viewmode') != '' ){ ViewMode = apexpages.currentpage().getparameters().get('viewmode'); } if(apexpages.currentpage().getparameters().containsKey('islwcapp') && apexpages.currentpage().getparameters().get('islwcapp') != null && apexpages.currentpage().getparameters().get('islwcapp') != '' ){ isLwcApp = 'true'; } String mainOrgUrl = 'https://hicshopifymadeeasy-dev-ed.my.salesforce-sites.com/thankYouPageSuccessfulAuthentication'; if(!apexpages.currentpage().getparameters().containsKey('code') ){ Organization orgRecord; if(Schema.sObjectType.organization.isAccessible() && Schema.sObjectType.organization.fields.issandbox.isAccessible() && Schema.sObjectType.organization.fields.id.isAccessible() ){ orgRecord = [select id,issandbox from organization LIMIT 1]; } // PKCE: generate verifier, derive challenge, and pack verifier into state so it // can be recovered at token-exchange time (cross-org via publisher Site). String verifier = PkceUtil.generateCodeVerifier(); String challenge = PkceUtil.generateCodeChallenge(verifier); String pkceParams = '&code_challenge=' + challenge + '&code_challenge_method=S256'; if(orgRecord.issandbox){ if(isLwcApp == 'true'){ String state = PkceUtil.embedVerifierInState(salesforceUrl+'test'+ViewMode+'islwcapp', verifier); pagereference p = new pagereference(salesforceUrl+'/services/oauth2/authorize?response_type=code&client_id='+clientIdProduction+'&redirect_uri='+mainOrgUrl+'&islwcapp=true&state='+EncodingUtil.urlEncode(state, 'UTF-8')+pkceParams); return p; }else{ // NEW SALESFORCE URL String state = PkceUtil.embedVerifierInState(salesforceUrl+'test'+ViewMode, verifier); pagereference p = new pagereference(salesforceUrl+'/services/oauth2/authorize?response_type=code&client_id='+clientIdProduction+'&redirect_uri='+mainOrgUrl+'&islwcapp=true&state='+EncodingUtil.urlEncode(state, 'UTF-8')+pkceParams); return p; } }else{ if(isLwcApp == 'true'){ //NEW SALESFORCE URL String state = PkceUtil.embedVerifierInState(salesforceUrl+'login'+ViewMode+'islwcapp', verifier); pagereference p = new pagereference(salesforceUrl+'/services/oauth2/authorize?response_type=code&client_id='+clientIdProduction+'&redirect_uri='+mainOrgUrl+'&state='+EncodingUtil.urlEncode(state, 'UTF-8')+pkceParams); return p; }else{ String state = PkceUtil.embedVerifierInState(salesforceUrl+'login'+ViewMode, verifier); pagereference p = new pagereference(salesforceUrl+'/services/oauth2/authorize?response_type=code&client_id='+clientIdProduction+'&redirect_uri='+mainOrgUrl+'&state='+EncodingUtil.urlEncode(state, 'UTF-8')+pkceParams); return p; } } } return null; } // Method that will run for Non Site Supporting Org public Pagereference authenticateSalesforce(){ system.debug('>>> NON SITE SUPPORTED ORG METHOD'); //System.debug('SalesforceAuthorizationController called'); String code = ApexPages.currentPage().getParameters().get('code'); // String state = ApexPages.currentPage().getParameters().get('state'); String storeName; // if (state != null) { // List redirectParams = state.split('_'); // // Ensure that 'parts' has at least two elements before accessing 'parts[1]' // if (redirectParams.size() > 1 && redirectParams[1] != null) { // List subParts = redirectParams[1].split('='); // if (subParts.size() > 1 && subParts[1] != null) { // storeName = subParts[1]; // } // } // } Organization orgRecord; if(Schema.sObjectType.organization.isAccessible() && Schema.sObjectType.organization.fields.issandbox.isAccessible() && Schema.sObjectType.organization.fields.id.isAccessible() ){ orgRecord = [select id,issandbox from organization LIMIT 1]; } String endPoint; Map headerMap = new Map(); String query = 'Select '+CheckAccessibility.isAccessible('id,name,HIC_ShopifySync__ClientId__c,HIC_ShopifySync__Salesforce_Site_URL__c,HIC_ShopifySync__ClientSecret__c','HIC_ShopifySync__Configuration__c')+' from HIC_ShopifySync__Configuration__c where '+CheckAccessibility.isAccessible('name','HIC_ShopifySync__Configuration__c')+' = \'Default\' LIMIT 1'; String finalQuery = String.escapeSingleQuotes(query).remove('\\'); listofConfiguration = database.query(finalQuery); //listofConfiguration = [select id,name,HIC_Shopi fySync__ClientId__c,HIC_ShopifySync__ClientSecret__c from HIC_ShopifySync__Configuration__c where name = 'Default' limit 1]; if(listofConfiguration.isEmpty()){ System.debug('@@@ Checking List of configuration :: '+listofConfiguration); // return null; } if(orgRecord.isSandbox){ clientIdProduction = '3MVG9G9pzCUSkzZv4NsQV0kOphuwhNCDbV2xJ4wazQQkD_nyOa73sUwa0Z0DtHz1t5woA9d9AVNwcHvqbQy45'; clientSecretproduction = 'FE5D62CA3C108BF836DDD3DB01C6B063E66F28FFAC5EC3BF43BC6EB248466F4A'; }else{ clientIdProduction = '3MVG9G9pzCUSkzZv4NsQV0kOphm0Il0eDJK5e0YicZLcCZKQcDHS0FORFMYHW8LzqJWPeMxzGUxeyshwEBOin'; clientSecretproduction = '2CF544F99E959C43977763BFBFA5999D4488E0C69757A99149D101EEB62E92B3'; } String middlewareUrl = listofConfiguration[0].HIC_ShopifySync__Salesforce_Site_URL__c; // clientIdProduction = listofConfiguration[0].HIC_ShopifySync__ClientId__c; // clientSecretproduction = listofConfiguration[0].HIC_ShopifySync__ClientSecret__c; // clientSecretproduction = listofConfiguration[0].HIC_ShopifySync__ClientSecret__c; if(apexpages.currentpage().getparameters().containsKey('viewmode') && apexpages.currentpage().getparameters().get('viewmode') != null && apexpages.currentpage().getparameters().get('viewmode') != '' ){ ViewMode = apexpages.currentpage().getparameters().get('viewmode'); } if(apexpages.currentpage().getparameters().containsKey('storeName') && apexpages.currentpage().getparameters().get('storeName') != null && apexpages.currentpage().getparameters().get('storeName') != '' ){ storeName = apexpages.currentpage().getparameters().get('storeName'); } if(apexpages.currentpage().getparameters().containsKey('storeName') && apexpages.currentpage().getparameters().get('storeName') != null && apexpages.currentpage().getparameters().get('storeName') != '' ){ storeName = apexpages.currentpage().getparameters().get('storeName'); } try { System.debug('@@@ Inside Try Method :: '); if(code != null && code != ''){ System.debug('@@@ Found Code :: '+code); // PKCE: recover the code_verifier that was packed into state at /authorize time. String stateParam = ApexPages.currentPage().getParameters().get('state'); String codeVerifier = PkceUtil.extractVerifier(stateParam); headerMap.put('Content-Type' , 'application/x-www-form-urlencoded'); headerMap.put('Accept' , 'application/json'); headerMap.put('Content-length' , '307'); //String body = 'grant_type=authorization_code&code='+code + '&redirect_uri=https://hic192-dev-ed.develop.my.salesforce-sites.com/thankYouPageSuccessfulAuthentication'; String body = 'grant_type=authorization_code&code='+code + '&redirect_uri='; // String mainOrgUrl = 'https://hicshopifymadeeasy-dev-ed.my.salesforce-sites.com/thankYouPageSuccessfulAuthentication'; if(orgRecord.isSandbox){ body += 'https://test.salesforce.com/apex/HIC_ShopifySync__SalesforceAuthentication'; //body += 'https://hic192-dev-ed.develop.my.salesforce-sites.com/thankYouPageSuccessfulAuthentication'; //endPoint for sandbox endPoint = 'https://test.salesforce.com/services/oauth2/token'; body += '&client_id=' +clientIdProduction+ '&client_secret=' +clientSecretproduction+'&islwcapp=true&state=test'+ViewMode+'islwcapp'; }else{ body+='https://login.salesforce.com/apex/HIC_ShopifySync__SalesforceAuthentication'; // body+=mainOrgUrl; //endPoint for Production endPoint = 'https://login.salesforce.com/services/oauth2/token'; body += '&client_id=' +clientIdProduction+ '&client_secret=' +clientSecretproduction+'&islwcapp=true&state=prod'+ViewMode+'islwcapp'; } if(String.isNotBlank(codeVerifier)){ body += '&code_verifier=' + codeVerifier; } HttpResponse tokenResp = makeCallout(body, endpoint, headerMap, 'POST'); if(tokenResp != null){ System.debug('@@@ Token exchange status :: '+tokenResp.getStatusCode()); } // Null-check: makeCallout returns null on any callout failure (timeout/DNS/cert). if(tokenResp != null && tokenResp.getStatusCode() == 200){ Map jsonMap = (Map) JSON.deserializeUntyped(tokenResp.getBody()); String newAccess = (String) jsonMap.get('access_token'); String newRefresh = (String) jsonMap.get('refresh_token'); // A 200 that lacks a FULL token pair is NOT a successful auth. Never report // 'connected' with nothing persisted — that strands the org (this is the recovery // path for a dead org, and the OAuth code is single-use). Fail instead of redirecting. if(String.isBlank(newAccess) || String.isBlank(newRefresh)){ isConnectionSuccessful = false; System.debug('@@@ token exchange returned 200 but missing access/refresh — treating as failure'); return null; } // Best-effort push to the middleware. This is a CALLOUT, so it runs BEFORE the token // DML (never a callout-after-DML) and it must NEVER gate the authoritative local save // — if the middleware is down the org must still keep its freshly issued token. try { Map qbDataPassToDataBase = new Map(); qbDataPassToDataBase.put('storeId','salesforceUrl'); qbDataPassToDataBase.put('accessToken' , newAccess); qbDataPassToDataBase.put('refreshToken' , newRefresh); qbDataPassToDataBase.put('isSandbox' , orgRecord.isSandbox); qbDataPassToDataBase.put('orgId' , UserInfo.getOrganizationId()); qbDataPassToDataBase.put('instanceUrl' , URL.getOrgDomainURL().toExternalForm()); Map mwHeaders = new Map(); mwHeaders.put('Content-Type' , 'application/json'); mwHeaders.put('Accept' , '*/*'); makeCallout(JSON.serialize(qbDataPassToDataBase), middlewareUrl+'/api/auth/callback', mwHeaders, 'POST'); } catch(Exception mwEx) { System.debug('@@@ Middleware push failed (non-fatal) :: '+mwEx.getMessage()); } // AUTHORITATIVE local save — the LAST durable action, independent of the middleware. // updateCredentials throws if it cannot persist, so we never falsely report success. updateCredentials(newAccess, newRefresh); isConnectionSuccessful = true; PageReference ref = new PageReference('/lightning/n/HIC_ShopifySync__SME_Settings'); ref.setRedirect(true); return ref; } } } catch (Exception ex) { System.debug('@@@@ ERROR OCCURED WHILE AUTHORIZING ! at line :: '+ex.getLineNumber()+' with message :: '+ex.getMessage()); } return null; } // public static HttpResponse saveTokenToMiddleWare(Map qbDataPassToDataBase, String endpoint){ // Map headerMap = new Map(); // headerMap.put('Content-Type' , 'application/json');//;charset=UTF-8'); // // headerMap.put('Accept' , 'application/json'); // headerMap.put('Accept' , '*/*'); // HttpResponse httpResponse = makeCallout(JSON.serialize(qbDataPassToDataBase), endpoint, headerMap, 'POST'); // return httpResponse; // } public PageReference redirectToApp(){ PageReference ref = new PageReference('/lightning/n/HIC_ShopifySync__SME_Settings'); ref.setRedirect(true); return ref; } public Static void updateCredentials(String accessToken, String refreshToken){ if(String.isBlank(accessToken) || String.isBlank(refreshToken)) return; // Serialize on the Default row (FOR UPDATE) and FAIL LOUD: a freshly issued token that cannot // be saved must surface as an error, never a silent success. The OAuth code is single-use, so // a swallowed save here would leave the org permanently without a token. Exception last; for(Integer attempt = 0; attempt < 3; attempt++){ try { List rows = [ SELECT Id FROM HIC_ShopifySync__Configuration__c WHERE Name = 'Default' LIMIT 1 FOR UPDATE ]; if(rows.isEmpty()) return; update new HIC_ShopifySync__Configuration__c( Id = rows[0].Id, HIC_ShopifySync__Access_Token__c = accessToken, HIC_ShopifySync__Refresh_Token__c = refreshToken ); return; } catch(Exception e){ last = e; } } if(last != null) throw last; // never report success after failing to persist the token } // Updating Tokens on server public static void updateAppServer(){ List cyptokeyquery = ShopifyController.getCryptoKey(); if(cyptokeyquery == null || cyptokeyquery.isEmpty()){ return; } Organization orgRecord; if(Schema.sObjectType.organization.isAccessible() && Schema.sObjectType.organization.fields.issandbox.isAccessible() && Schema.sObjectType.organization.fields.id.isAccessible() ){ orgRecord = [select id,issandbox from organization LIMIT 1]; } String middlewareUrl = cyptokeyquery[0].HIC_ShopifySync__Salesforce_Site_URL__c; // Setting Body to sent to Server Map qbDataPassToDataBase = new Map(); qbDataPassToDataBase.put('storeId','salesforceUrl'); qbDataPassToDataBase.put('accessToken' , cyptokeyquery[0].HIC_ShopifySync__Access_Token__c); qbDataPassToDataBase.put('refreshToken' , cyptokeyquery[0].HIC_ShopifySync__Refresh_Token__c); qbDataPassToDataBase.put('isSandbox' ,orgRecord.isSandbox); qbDataPassToDataBase.put('orgId' , (UserInfo.getOrganizationId())); String sfdcBaseURL = URL.getOrgDomainURL().toExternalForm(); qbDataPassToDataBase.put('instanceUrl' , sfdcBaseURL); Map headerMap = new Map(); String endPoint = middlewareUrl+'/api/auth/callback'; // Setting Headers headerMap = new Map(); headerMap.put('Content-Type' , 'application/json'); headerMap.put('Accept' , '*/*'); HttpResponse httpResponse = makeCallout(JSON.serialize(qbDataPassToDataBase), endpoint, headerMap, 'POST'); } public static HttpResponse makeCallout(String body , String endpoint , Map headers , String method){ System.debug('@@@ Inside Cllout Method :: '+endpoint); try{ System.debug('@@@ body : '+body); System.debug('@@@ endpoint : '+endpoint); System.debug('@@@ headers : '+headers); System.debug('@@@ method : '+method); HttpRequest httpRequestObject = new HttpRequest(); httpRequestObject.setEndpoint(endpoint); httpRequestObject.setMethod(method); httpRequestObject.setTimeout(120000); if(String.isNotEmpty(method)){ if(method.equalsIgnoreCase('POST')){ httpRequestObject.setBody(body); } if(method.equalsIgnoreCase('PUT')){ httpRequestObject.setBody(body); } } if(headers != null && !headers.isEmpty()){ for(String headerKey : headers.keySet()){ httpRequestObject.setHeader(headerKey, headers.get(headerKey)); } } Http httpObject = new Http(); HttpResponse httpResponseObject = httpObject.send(httpRequestObject); return httpResponseObject; } catch (Exception ex) { System.debug('@@@ Exception OCcured at line : '+ex.getLineNumber()+' with message :: '+ex.getMessage()); return null; } } }