This repository contains a Salesforce project that integrates with the SpreadsheetWeb API to perform Excel calculations. It features an Excel spreadsheet for calculating loan payments and generating an amortization schedule, though it can be applied to any Excel file containing worksheet formulas. The project includes Apex classes, a Visualforce page, and necessary configuration to perform API calls and display the results in a user-friendly manner.
- CalculationController2: Handles the loan calculation logic and prepares data for the Visualforce page.
- CalculationService2: Manages API requests to SpreadsheetWeb.
- TokenService: Handles token retrieval for API authentication.
- LoanCalculatorPage: Collects user input and displays the loan calculation results.
- Salesforce Developer Org
- SpreadsheetWeb Account with access to the API
Apex Classes:
- Navigate to Setup > Apex Classes.
- Create new Apex classes with the content provided in the
CalculationController2,CalculationService2, andTokenServicesections below.
Visualforce Page:
- Navigate to Setup > Visualforce Pages.
- Create a new Visualforce page with the content provided in the
LoanCalculatorPagesection below.
Configuration:
- Update the
workspaceId,applicationId,clientId, andclientSecretin the Apex classes with your SpreadsheetWeb credentials.
- Update the
// In CalculationController2StringworkspaceId='YourWorkspaceID';
StringapplicationId='YourApplicationID';
// In TokenServiceStringclientId='YourClientID';
StringclientSecret='YourClientSecret';Navigate to the Visualforce Page:
- Open the newly created Visualforce page (
LoanCalculatorPage) in your Salesforce org.
- Open the newly created Visualforce page (
Enter Loan Details:
- Input the loan amount, interest rate, loan period, and start date.
Calculate:
- Click the "Calculate" button to perform the calculation.
View Results:
- The scheduled payment amount and amortization chart will be displayed below the input form.
The CalculationController2 Apex class handles user inputs, prepares API requests, processes API responses, and stores the results for display on the Visualforce page.
Key Methods:
calculate(): Prepares input data, makes an API call, and processes the response to extract the scheduled payment amount and amortization chart.
publicclassCalculationController2 {
publicStringloanAmount { get; set; }
publicStringinterestRate { get; set; }
publicStringloanPeriod { get; set; }
publicStringloanStartDate { get; set; }
publicStringscheduledPaymentAmt { get; set; }
publicList<AmortizationRow> amortizationChart { get; set; }
publicStringrequestBody { get; set; }
publicStringresponseBody { get; set; }
publicStringrawResponse { get; set; }
publicCalculationController2() {
amortizationChart=newList<AmortizationRow>();
}
publicvoidcalculate() {
List<CalculationService.CalculationInput> inputs=newList<CalculationService.CalculationInput>();
CalculationService.CalculationInputloanAmountInput=newCalculationService.CalculationInput();
loanAmountInput.reference='loan_amount';
loanAmountInput.value=newList<List<Map<String, String>>>{ newList<Map<String, String>>{ newMap<String, String>{ 'value'=>loanAmount }}};
inputs.add(loanAmountInput);
CalculationService.CalculationInputinterestRateInput=newCalculationService.CalculationInput();
interestRateInput.reference='interest_rate';
interestRateInput.value=newList<List<Map<String, String>>>{ newList<Map<String, String>>{ newMap<String, String>{ 'value'=>interestRate }}};
inputs.add(interestRateInput);
CalculationService.CalculationInputloanPeriodInput=newCalculationService.CalculationInput();
loanPeriodInput.reference='loan_period';
loanPeriodInput.value=newList<List<Map<String, String>>>{ newList<Map<String, String>>{ newMap<String, String>{ 'value'=>loanPeriod }}};
inputs.add(loanPeriodInput);
CalculationService.CalculationInputloanStartDateInput=newCalculationService.CalculationInput();
loanStartDateInput.reference='loan_start_date';
loanStartDateInput.value=newList<List<Map<String, String>>>{ newList<Map<String, String>>{ newMap<String, String>{ 'value'=>loanStartDate }}};
inputs.add(loanStartDateInput);
List<String> outputs=newList<String>{ 'scheduled_payment_amt', 'amortization_chart' };
try {
StringworkspaceId='YourWorkspaceID';
StringapplicationId='YourApplicationID';
CalculationService.CalculationRequestcalculationRequest=newCalculationService.CalculationRequest(workspaceId, applicationId, inputs, outputs);
requestBody=JSON.serialize(calculationRequest);
CalculationService.CalculationResponseresponse=CalculationService.calculate(workspaceId, applicationId, inputs, outputs);
responseBody=JSON.serialize(response);
rawResponse=response.rawResponse;
Map<String, Object> rawResponseMap= (Map<String, Object>) JSON.deserializeUntyped(rawResponse);
Map<String, Object> responseMap= (Map<String, Object>) rawResponseMap.get('response');
Map<String, Object> responseInnerMap= (Map<String, Object>) responseMap.get('response');
Map<String, Object> outputMap= (Map<String, Object>) responseInnerMap.get('output');
Map<String, Object> calculationMap= (Map<String, Object>) outputMap.get('calculation');
if (calculationMap!=null&&calculationMap.containsKey('outputs')) {
List<Object> outputsList= (List<Object>) calculationMap.get('outputs');
for (ObjectoutputObj:outputsList) {
Map<String, Object> outputItem= (Map<String, Object>) outputObj;
if (outputItem.get('reference') =='scheduled_payment_amt') {
List<Object> valueList= (List<Object>) outputItem.get('value');
if (valueList!=null&&!valueList.isEmpty()) {
List<Object> innerValueList= (List<Object>) valueList.get(0);
if (innerValueList!=null&&!innerValueList.isEmpty()) {
Map<String, Object> valueItem= (Map<String, Object>) innerValueList.get(0);
scheduledPaymentAmt= (String) valueItem.get('text');
}
}
} elseif (outputItem.get('reference') =='amortization_chart') {
List<Object> valueList= (List<Object>) outputItem.get('value');
if (valueList!=null&&!valueList.isEmpty()) {
for (Integeri=1; i<valueList.size(); i++) { // Skip header rowList<Object> rowList= (List<Object>) valueList.get(i);
AmortizationRowrow=newAmortizationRow();
row.Year= (String) ((Map<String, Object>) rowList.get(0)).get('text');
row.Remaining= (String) ((Map<String, Object>) rowList.get(1)).get('text');
row.InterestPaid= (String) ((Map<String, Object>) rowList.get(2)).get('text');
row.PrincipalPaid= (String) ((Map<String, Object>) rowList.get(3)).get('text');
amortizationChart.add(row);
}
}
}
}
} else {
scheduledPaymentAmt='Error calculating scheduled payment amount. Full response: '+responseBody;
}
} catch (Exceptione) {
responseBody=e.getMessage();
scheduledPaymentAmt='Request: '+requestBody+' | Response: '+rawResponse+' | Error: '+responseBody;
}
}
publicclassAmortizationRow {
publicStringYear { get; set; }
publicStringRemaining { get; set; }
publicStringInterestPaid { get; set; }
publicStringPrincipalPaid { get; set; }
}
}The CalculationService2 Apex class manages the interaction with the SpreadsheetWeb API, including constructing API requests and handling responses.
Key Methods:
calculate(): Makes a call to the SpreadsheetWeb API with the provided inputs and returns the calculation results.
publicclassCalculationService2 {
publicclassCalculationInput {
publicStringreference;
publicList<List<Map<String, String>>> value;
}
publicclassCalculationRequest {
publicMap<String, Object> request;
publicCalculationRequest(StringworkspaceId, StringapplicationId, List<CalculationInput> inputs, List<String> outputs) {
Map<String, Object> calculation=newMap<String, Object>();
calculation.put('inputs', inputs);
calculation.put('outputs', outputs);
Map<String, Object> input=newMap<String, Object>();
input.put('calculation', calculation);
Map<String, Object> innerRequest=newMap<String, Object>();
innerRequest.put('input', input);
Map<String, Object> outerRequest=newMap<String, Object>();
outerRequest.put('workspaceId', workspaceId);
outerRequest.put('applicationId', applicationId);
outerRequest.put('request', innerRequest);
this.request=outerRequest;
}
}
publicclassOutputValue {
publicStringtype;
publicStringformatType;
publicStringformat;
publicStringtext;
publicStringvalue;
publicStringoverwrite;
}
publicclassOutput {
publicStringreference;
publicList<List<OutputValue>> value;
publicStringoverwrite;
}
publicclassCalculation {
publicBooleansuccess;
publicList<Output> outputs;
publicList<String> validations;
publicList<String> messages;
}
publicclassOutputResponse {
publicBooleansuccess;
publicCalculationcalculation;
publicStringgoalSeek;
publicStringsolver;
}
publicclassResponse {
publicStringapplicationId;
publicOutputResponseresponse;
publicStringsaveResult;
publicIntegerusedTransactionSequenceId;
publicStringrequestId;
publicBooleansuccess;
publicStringeventCreationDate;
publicStringretryIndex;
publicStringdebugRetryAllowFailureCount;
}
publicclassCalculationResponse {
publicResponseresponse;
publicMap<String, Double> timingsSeconds;
publicStringperformanceInformation;
publicBooleanisError;
publicList<String> messages;
publicStringrawResponse;
}
publicstaticCalculationResponsecalculate(StringworkspaceId, StringapplicationId, List<CalculationInput> inputs, List<String> outputs) {
Stringtoken=TokenService.getBearerToken();
if(token==null) {
thrownewCalloutException('Token is null. Cannot proceed with the API call.');
}
Httphttp=newHttp();
HttpRequestrequest=newHttpRequest();
request.setEndpoint('https://api.spreadsheetweb.com/calculations/calculatesingle');
request.setMethod('POST');
request.setHeader('Authorization', 'Bearer '+token);
request.setHeader('Content-Type', 'application/json');
CalculationRequestcalculationRequest=newCalculationRequest(workspaceId, applicationId, inputs, outputs);
Stringbody=JSON.serialize(calculationRequest);
request.setBody(body);
HttpResponseresponse=http.send(request);
if (response.getStatusCode() ==200) {
StringresponseBody=response.getBody();
CalculationResponsecalculationResponse= (CalculationResponse) JSON.deserialize(responseBody, CalculationResponse.class);
calculationResponse.rawResponse=responseBody;
returncalculationResponse;
} else {
thrownewCalloutException('Failed to perform calculation: '+response.getBody());
}
}
}The TokenService Apex class retrieves an access token from the SpreadsheetWeb identity service, which is used to authenticate API requests.
Key Methods:
getBearerToken(): Sends a request to the SpreadsheetWeb identity service to retrieve an access token.
publicclassTokenService {
publicclassTokenResponse {
publicStringaccess_token;
publicStringtoken_type;
publicIntegerexpires_in;
}
publicstaticStringgetBearerToken() {
Httphttp=newHttp();
HttpRequestrequest=newHttpRequest();
request.setEndpoint('https://identity.spreadsheetweb.com/connect/token');
request.setMethod('POST');
request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
StringclientId='YourClientID';
StringclientSecret='YourClientSecret';
Stringbody='grant_type=client_credentials&client_id='+clientId+'&client_secret='+clientSecret;
request.setBody(body);
HttpResponseresponse=http.send(request);
if (response.getStatusCode() ==200) {
TokenResponsetokenResponse= (TokenResponse) JSON.deserialize(response.getBody(), TokenResponse.class);
returntokenResponse.access_token;
} else {
thrownewCalloutException('Failed to get token: '+response.getBody());
}
}
}The Visualforce page provides a user interface for inputting loan details and displaying the results.
<apex:pagecontroller="CalculationController2"><apex:form><apex:pageBlocktitle="Loan Calculator"><apex:pageBlockSectiontitle="Loan Calculator" columns="2"><apex:inputTextvalue="{!loanAmount}" label="Loan Amount"/><apex:inputTextvalue="{!interestRate}" label="Interest Rate"/><apex:inputTextvalue="{!loanPeriod}" label="Loan Period"/><apex:inputTextvalue="{!loanStartDate}" label="Loan Start Date"/><apex:commandButtonvalue="Calculate" action="{!calculate}"/></apex:pageBlockSection></apex:pageBlock><apex:pageBlocktitle="Scheduled Payment Amount"><apex:outputTextvalue="{!scheduledPaymentAmt}" rendered="{!scheduledPaymentAmt != null}"/></apex:pageBlock><apex:pageBlocktitle="Amortization Chart"><apex:pageBlockTablevalue="{!amortizationChart}" var="row" rendered="{!amortizationChart != null}"><apex:columnvalue="{!row.Year}" headerValue="Year"/><apex:columnvalue="{!row.Remaining}" headerValue="Remaining"/><apex:columnvalue="{!row.InterestPaid}" headerValue="Interest Paid"/><apex:columnvalue="{!row.PrincipalPaid}" headerValue="Principal Paid"/></apex:pageBlockTable></apex:pageBlock></apex:form></apex:page>Here are examples of how the amortization chart and scheduled payment amount will be displayed:
Scheduled Payment Amount: Scheduled Payment Amount: $2,684
Amortization Chart
Here is an example of how the amortization chart will be displayed:
| Year | Remaining Balance | Interest Paid | Principal Paid |
|---|---|---|---|
| 0 | $500,000 | $2,083 | $1,601 |
| 1 | $480,344 | $26,555 | $21,338 |
| 2 | $459,683 | $50,017 | $42,086 |
| 3 | $437,965 | $72,417 | $63,895 |
| ... | ... | ... | ... |
This project demonstrates a practical integration between Salesforce and the SpreadsheetWeb API for loan calculations. By following the setup instructions and using the provided components, you can easily implement similar functionality in your own Salesforce org.
For further assistance or questions, please refer to the SpreadsheetWeb API documentation or contact support.