/* ** ******************************************************************* ** *
* PRIVATE CONSTANTS *
* ** ******************************************************************* ** */
// Values
final String TST_VAL_EMAIL_EXTENSION = '@example.com.invalid';
final String TST_VAL_UNAME_EXTENSION = '@example.com.demo';
final String TST_VAL_SYS_ADMIN_PROFILE_NAME = 'System Administrator';
// Specify a start url so users can easily be redirected during demos
final String TST_VAL_START_URL = '/lightning/n/Tree_Applications';
// Override any values on the user object
Map<String,Object> customData = new Map<String,Object>{
'FirstName' => 'Henk 02',
'LastName' => 'de Vries 02',
'Alias' => 'hdv02',
'FederationIdentifier' => 'hdv02'
};
try{
// Create the user
User u = createUser(TST_VAL_SYS_ADMIN_PROFILE_NAME, customData);
// Create the user
insert u;
// Create a random password
String password = EncodingUtil.convertToHex(Crypto.generateDigest('SHA1', Blob.valueOf(u.username + DateTime.now().getTime())));
// Set the password
System.setPassword(u.Id,password);
// Output an easy login url details
System.debug(password);
// Very bad, but good for demos 8-)
System.debug(URL.getSalesforceBaseUrl().toExternalForm() + '/login.jsp?un=' + u.Username + '&startURL='+TST_VAL_START_URL);
// Debug the user info
System.debug(JSON.serializePretty(u));
}catch(Exception e){
System.debug(e.getMessage());
}
/* ** ******************************************************************* ** *
* PUBLIC SUPPORT METHODS *
* ** ******************************************************************* ** */
/**
* Method for creating a user record
*/
public User createUser(String profileName, Map<String,Object> dataMap){
// Check profile name is not blank
if(String.isBlank(profileName)){
throw new StringException('Profile name cannot be blank');
}
// Query profile Id
String profileId = [SELECT Id FROM Profile WHERE Name = :profileName WITH SECURITY_ENFORCED LIMIT 1]?.Id;
// Check the profile exists else throw an error
if(profileId == null){
throw new StringException('Profile does not exist, check the profile name');
}
// Setup a user, so you can run the test code for a specific profile / permission set
// In out best practice we use permission sets, so it's best to test these permission sets accordingly with the proper user Profile
User runAsUser = new User();
runAsUser.Alias = 'testuser';
runAsUser.Email = 'testuser' + TST_VAL_EMAIL_EXTENSION;
runAsUser.EmailEncodingKey = 'UTF-8';
runAsUser.LastName = 'testuser';
runAsUser.LanguageLocaleKey = 'en_US';
runAsUser.LocaleSidKey = 'en_US';
runAsUser.ProfileId = profileId;
runAsUser.TimeZoneSidKey = 'Europe/London';
// Create a SHA1 hashed username so the username will always be random
runAsUser.UserName = EncodingUtil.convertToHex(Crypto.generateDigest('SHA1', Blob.valueOf('testuser' + DateTime.now().getTime()))) + TST_VAL_UNAME_EXTENSION;
// Allow the developers to populate additional data to the user record or override any of the default values
populateSObjectDataFromMap(runAsUser,dataMap);
return runAsUser;
}
/* ** ******************************************************************* ** *
* PRIVATE SUPPORT METHODS (CLASS SPECIFIC) *
* ** ******************************************************************* ** */
/**
* Method to populate data on an sObject from a data map
*/
private static void populateSObjectDataFromMap(sObject sObj, Map<String,Object> dataMap){
// Null check
if(sObj == null || dataMap == null){return;}
// Fetch the fields from the metadata so we can check they exist
Schema.SObjectType sObjectType = sObj.getSObjectType();
Map<String, Schema.SObjectField> fieldMap = sObjectType.getDescribe(SObjectDescribeOptions.DEFERRED).fields.getMap();
// Populate the data from the data map
for(String fieldName : dataMap.keySet()){
// Check the field exists on the sObject
if(!fieldMap.containsKey(fieldName)){
throw new StringException(String.format('The field "{0}" on sObject "{1}" cannot be found in the metadata', new String[]{fieldName,String.valueOf(sObjectType)}));
}
// Populate the field
sObj.put(fieldName,dataMap.get(fieldName));
}
}
{
"swagger": "2.0",
"basePath": "/",
"info": {
"version": "1.0",
"title": "External Service for demo bank",
"description": "### External Service for demo bank",
"x-vcap-service-name": "DemoBankRestServices"
},
"securityDefinitions": {
"basicAuth": {
"type": "basic"
}
},
"security": [{
"basicAuth": []
}],
"tags": [{
"name": "DemoBankRestServices"
}],
"paths": {
"/accounts/{accountName}": {
"get": {
"operationId": "getAccount",
"summary": "Retrieves an account",
"description": "Retrieves the account with specific name",
"consumes": ["text/plain"],
"produces": ["application/json"],
"parameters": [{
"name": "accountName",
"in": "path",
"required": true,
"type": "string",
"description": "Name of the account"
}],
"responses": {
"200": {
"description": "The response when system finds an account with given name",
"schema": {
"$ref": "#/definitions/accountDetails"
}
},
"400": {
"description": "Error response if the account name parameter is less than minimum characters",
"schema": {
"$ref": "#/definitions/errorModel"
}
},
"404": {
"description": "Error response if the account is not supported by service or account is not found",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
},
"delete": {
"operationId": "DeleteAccount",
"summary": "Deletes an account",
"description": "Deletes the account with specific name",
"consumes": ["text/plain"],
"produces": ["application/json"],
"parameters": [{
"name": "accountName",
"in": "path",
"required": true,
"type": "string",
"description": "Name of the account"
}],
"responses": {
"204": {
"description": "The response when system finds an account with given name",
"schema": {
"type": "string"
}
},
"400": {
"description": "Error response if the account name parameter is less than minimum characters",
"schema": {
"$ref": "#/definitions/errorModel"
}
},
"404": {
"description": "Error response if the account is not supported by service or account is not found",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
},
"post": {
"operationId": "addAccount",
"summary": "Add an account",
"description": "Add an account to the database",
"consumes": ["text/plain"],
"produces": ["application/json"],
"parameters": [{
"name": "accountName",
"in": "path",
"required": true,
"type": "string",
"description": "Name of the account"
}, {
"name": "accountType",
"in": "query",
"required": true,
"type": "string",
"description": "The type of account"
}],
"responses": {
"201": {
"description": "The response when the account does not already exist and we can create one",
"schema": {
"$ref": "#/definitions/accountDetails"
}
},
"409": {
"description": "The response when the account already exists and we cannot create one",
"schema": {
"$ref": "#/definitions/accountDetails"
}
},
"400": {
"description": "Error response if the account name parameter is less than minimum characters",
"schema": {
"$ref": "#/definitions/errorModel"
}
},
"404": {
"description": "Error response if the account is not supported by service or account is not found",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
},
"put": {
"operationId": "updateAccount",
"summary": "Updates an account",
"description": "Updates the account with specified name",
"consumes": ["text/plain"],
"produces": ["application/json"],
"parameters": [{
"name": "accountName",
"in": "path",
"required": true,
"type": "string",
"description": "Name of the account"
}, {
"name": "accountType",
"in": "query",
"required": true,
"type": "string",
"description": "The type of account"
}],
"responses": {
"200": {
"description": "The response when system finds an account with given name",
"schema": {
"$ref": "#/definitions/accountDetails"
}
},
"400": {
"description": "Error response if the account name parameter is less than minimum characters",
"schema": {
"$ref": "#/definitions/errorModel"
}
},
"404": {
"description": "Error response if the account is not supported by service or account is not found",
"schema": {
"$ref": "#/definitions/errorModel"
}
}
}
}
}
},
"definitions": {
"accountDetails": {
"required": ["id", "name", "type", "availableBal"],
"properties": {
"id": {
"type": "string",
"description": "id"
},
"name": {
"type": "string",
"description": "name"
},
"type": {
"type": "string",
"description": "type"
},
"availableBal": {
"type": "string",
"description": "availableBal"
}
}
},
"errorModel": {
"required": ["errorCode", "errorMessage"],
"properties": {
"errorCode": {
"type": "string",
"description": "A service-specific error code."
},
"errorMessage": {
"type": "string",
"description": "A service-specific error code."
}
}
}
}
}
public static String generateXMLSignature(String docBodyString){
// 1. Apply the Transforms, as determined by the application, to the data object.
// (no transforms are done, since the body should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))
// 2. Calculate the digest value over the resulting data object.
Blob bodyDigest = Crypto.generateDigest('SHA1',Blob.valueOf(docBodyString));
// 3. Create a Reference element, including the (optional) identification of the data object, any (optional) transform elements, the digest algorithm and the DigestValue.
// (Note, it is the canonical form of these references that are signed and validated in next steps.)
String referenceString = '';
referenceString += '';
referenceString += '<ds:Reference URI="#msgBody">';
referenceString += '<ds:Transforms>';
referenceString += '<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>';
referenceString += '<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>';
referenceString += '</ds:Transforms>';
referenceString += '<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>';
referenceString += '<ds:DigestValue>'+EncodingUtil.base64Encode(bodyDigest)+'</ds:DigestValue>';
referenceString += '</ds:Reference>';
// 4. Create SignedInfo element with SignatureMethod, CanonicalizationMethod and Reference(s).
String signedInfoString = '';
signedInfoString += '<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signedInfoString += '<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>';
signedInfoString += '<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>';
signedInfoString += referenceString;
signedInfoString += '</ds:SignedInfo>';
// 5. Canonicalize and then calculate the SignatureValue over SignedInfo based on algorithms specified in SignedInfo.
// (no canonicalization is done, since the signedinfo element should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))
String algorithmName = 'RSA-SHA1';
// decrypted private key pkcs8 format from certificate
String key = '[decryptedPrivateKeypkcs8formatfromcertificate]';
Blob privateKey = EncodingUtil.base64Decode(key);
Blob siBlob = Blob.valueOf(signedInfoString);
Blob signatureValue = Crypto.sign(algorithmName, siBlob, privateKey);
String signatureValueString = EncodingUtil.base64Encode(signatureValue);
// 6. Construct the Signature element that includes SignedInfo, Object(s) (if desired, encoding may be different than that used for signing), KeyInfo (if required), and SignatureValue.
String signatureString = '';
signatureString += '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signatureString += signedInfoString;
signatureString += '<ds:SignatureValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'+signatureValueString+'</ds:SignatureValue>';
signatureString += '<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signatureString += '<ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signatureString += '<ds:X509Certificate xmlns:ds="http://www.w3.org/2000/09/xmldsig#">[the certificate used for signing]</ds:X509Certificate>';
signatureString += '</ds:X509Data>';
signatureString += '</ds:KeyInfo>';
signatureString += '</ds:Signature>';
return signatureString;
}
// Data and private key. Use a method for getting the keys and signature
// so the example is more readable
private final String DATA = 'The quick brown fox jumps over the lazy dog';
private final String SIGNATURE = getSignature();
private final String PUBLIC_KEY = getPublicKey();
private final String PRIVATE_KEY= getPrivateKey();
// Performance measuring
Integer startTime = Limits.getCpuTime();
// Validate the newly created signature is as expected
System.assertEquals(
SIGNATURE,
createSignature(DATA, PRIVATE_KEY),
'The expected signature does not match the actual signature created using the private key.'
);
// VERIFY the signature is as expected
System.assertEquals(
true,
verifySignature(
DATA,
SIGNATURE,
PUBLIC_KEY
),
'Error verifying the signature using the public key'
);
// If no errors let us know, also output the time it took, as signatures might be CPU intensive
System.debug('All Key tests were OK. Execution took: ' +( Limits.getCpuTime() - startTime) + 'ms');
/**
* Method to get the signature when signing some data
* @param dataToSign The data that needs to be signed
* @param privateKey The RSA Private key
*/
public String createSignature(String dataToSign, String privateKey){
return EncodingUtil.base64Encode(
Crypto.sign(
'RSA-SHA256', // The algorith you want to use
Blob.valueOf(dataToSign), // Convert the string to a blog
EncodingUtil.base64Decode(privateKey) // decode converts string into a blob
)
);
}
/**
* Method to verify a signature
* @param dataToVerify The data that needs to be verified
* @param signature The B64 encoded signature that needs to be verified
* @param publicKey The B64 encoded RSA Public key
*/
public Boolean verifySignature(String dataToVerify, String signature, String publicKey){
return Crypto.verify(
'RSA-SHA256', // The algorith you want to use
Blob.valueOf(dataToVerify), // Convert to blob
EncodingUtil.base64Decode(signature), // Creates Blob from String
EncodingUtil.base64Decode(publicKey) // Creates Blob from String
);
}
/**
* Method to get the expected signature. This is for testing only, just so you have an
* idea what the output of the signature looks like
*/
private String getSignature(){
return 'RzgwUN39Dicag301cyf79neCKTZjotoguttb34gZjKKvT3kRj1ZvBtmynqHIRWBET0vDKugtKg/MtPu5h1Y8hqSHmZnkXbld/i737ptVEB6PQdRp26izWlLqpEyzkGrJ/FuJYyRNEV1xJdrnsTP3nBZ8qB0NPwCr9ZInZOWfO3DKspCOBWGaQ7H2ewC1OEr0OKdWqB1ThTP7U9+42KjABgNsnLpVge5xsPQja5QPbvqq+1Krl5z66xTTQQNxMvnKQt1fjUMqaDB52vDYbm8yh8iM4+fDPhbfmoDB5eFqwADjQ3b7kcKYgCF2D56KBxrVVHzFMMR70lkn8q6krZ4Ie9djkfBYAkVTkWSwlxwg70u7mg3O123Ddlcbasikg7FihdE3rRXrV69MkG7XzaxPAhEF0Abtubodoe6B78M8GrGUMGGdS8ad13BOGPTkCo9au8uZO7PjjKr16Hi8z4UAc3j6yFAjwkx6Hc5BgBlrVzdmfeRN6GBEMFkj1X5/OdBDLqSgUQ7t8QQr5GE1p24mMOGSHpvp45EQELJjyqGzJb4SL3CIBN82q0HOryKXE3NUd3PiZK+WanAQKYZfjCn3FpeSnbjXpRu+Fst7oqrv5Zkh4FucwihLqjGfEtwJhJ/gG3mUXqNOTHZ0S03pPuIVo4iiWrBRzY4EeqBDwkqgJCc=';
}
/**
* Method to get a 256 Bit SHA 256 Private key to give an idea of the format
* Your key should come from a secure certificate or secret store
* and NEVER be stored in code or where it is user readable
*/
private String getPrivateKey(){
String privateKey ='';
privateKey+='MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDaVmKLA1RFOpv5';
privateKey+='uCI8QgpeAxpDlkNSxksSn+aJGnLHzBjhAQRLD0skGXk3DH8FQp3ffEX3BC6/M8Ga';
privateKey+='IF7pAdOt+j3xHv+0+dQ7qpDqk433KKYLJaoI6AfDA5R+CfsJH4RcR0W8kTHF/+SB';
privateKey+='3aNGq5fDFD5AymIJiQeuJ3GO/r3gbRd5MpC6tI8UzGUQlnKFZvBK0W63nnYkxHEk';
privateKey+='E7gu/JWoZdfY7UcXEfhWte5DjOwtzq3DsGu4J6peGkYCAOtwCCrkpaumiDe3AqrQ';
privateKey+='eKoDsT+KDj1qMoD4Tnq1buCKMFQEKHY0KqCeT5IrcHZzsSNYZF4zz2gT6h5N+56G';
privateKey+='+F281mkHAFzIV582N5P0gh38FCJ/Qg6crRyYKeoxqm12sbxVbGEw0pROMURbHYnc';
privateKey+='nJlTTFQploVz5ZW8jgO8XEEjWbjkF7IBuu+FCgHFZ3JwoPyMeaAcGPPPRBrJAlR7';
privateKey+='1sm5f2WelHfJZKBLG8vxOFY9W85v7fMLZztE4yL1+0LiI2W7426ADfJRQ3sO2EA/';
privateKey+='WyOlTrBrk3Mvsj/9bkQ6C2aIIqNsmnvtQU+hzaZ10tyoIuQiUmHuDWvvKHiLw3EQ';
privateKey+='c7BLSlZaQkwG66kZUf/OUwsf5u2knywxw3oEbptrhuA8iZ17lQWoxbvkum2an6tA';
privateKey+='J7cL5yOVEYiq+C/O4gIVAXo7OTfDKwIDAQABAoICAQDXbq8zDSjkWi023EnjfSIv';
privateKey+='mw4aLDTngsLmcKIPG+qvW6IcuV0cFs4Eo6HoAEuAzDdsIXoDfrwFazMXOeOMM4JD';
privateKey+='QwxFU1npnyybZvQwkUFd41za2OIKga/O60RkjMKHQPLf/m8/3V/oR1KYFTIa9Ar9';
privateKey+='sIawdUEAuZW7cC3rc021GM81hgAqSSh41CjQkjITkPt+R2Hgidl28+HTQCXXLuEY';
privateKey+='VM0CvTUM/W01WbPsMSuFE05/LR9MiM7gpTn4liG43EY+b32MBxI6YMZchwLyO70/';
privateKey+='IkfU9lotJ3qKdQnXMFJl++qOFJWBP3AxrnkiNGF0wSv2IStHoxUdyy2jbYNIZaMg';
privateKey+='MmQE+kFhUz0mNaiJqHKoELDT6xEBMxWrfEVt8iQeISplCAh4cBtPUL+xFQ94yIFo';
privateKey+='CFecmgDk3noUwWu5fbEIkx2ui2mb4aWUN2FICd2KLyJSY/wbtFiKJ4eX49mukGId';
privateKey+='chNlAymrtVWI0k1GVa3JF+rjGbzJHG/hkxp2ccK4aq3Kl7mmvvYyKVXcgRQ3UQiA';
privateKey+='EuawNmqXtaTXwWOiBFYG6Gq/GIzsMZAPI2slg3pM0rCR1rJx+EqE8Fd8f4/Ua8WM';
privateKey+='m3rP50izBEtKWuNNSCopWyWDFH/HRvgKixU71NkLYQ5MHvfcpealH4i7zi3W6aeR';
privateKey+='5weNmGZ47o5l9xz/JIKpsQKCAQEA//Df6UsDNuCAcyk89d0+AELZ2T0i4xk+7p2M';
privateKey+='hgPo2iTm5MTgRl01NJ/LmsNJJ813h2dwyV6bMzdx+daziGybbivRJpz2mkCOwSqD';
privateKey+='E4+7wRjyaYRK8/aBY8KbcTYt6GY+UhBMh01Yg7mRZWSmFo0LL6V6vVVo3irkY6Kh';
privateKey+='Oy3plW6mTa1LNUMoavqEVyn/v9tYVTsRxiZVtvxjzsfcTuQ+gS1INKs8sTIfY9PV';
privateKey+='keho+gDaJYn/ZmFb1Tt70zSkQblwAG7nIA0pQtuuUF874FzhH7hUkwuASbjv2D5G';
privateKey+='5TwXOHbrR+MBrp2XbXSQ074DrCCMuw0xofSeELsaADK9c1p0GQKCAQEA2mNJvB1/';
privateKey+='TrJY2I/uzILEBl2qo66Sr8o8U0FCl/6/kVWzMIPhZz92+KedFKmJ5DGgFTUWri9F';
privateKey+='vz8ZxoeUDizikQKG9R26ZxZDJtvlkyKqY28ja9D0Iyb3QhV1qwExO4Fo2XCFgzCY';
privateKey+='2bqEwMCZ9G9LI3vWE3AOXQi0LdE39ImSRLoYJQmgfxDnoNQeofd3v7mbrwkyKuBv';
privateKey+='6jB+apeysiXSEsK3c/Xmrc2ceNnwHeZbWJJW+9aMWB0g6kK4fneXOz84F6gaAtU5';
privateKey+='q5kje02qRJZW8tKGBt07DnflhqAQRuuIIipk5yxCFmnJXUVSyzohoO5QSa53NAS4';
privateKey+='J7a8t9ivhJt54wKCAQA+IxXJhuut3A1zaBSjwGX4HELVihE5P3zW46slMjfLFmB0';
privateKey+='NBQbIS/0qcL9vOG65xhY6FUqnmxhn9ltBaIqwetucPbjQAJi6r99yDtweVnlBJB9';
privateKey+='659i4XsCZFHmx3eXz5Lby5c41h9iQ6A4FJp6KR4JIEzPQLgoEBPI2Mf6HShznhyE';
privateKey+='CUmUEczzRATzQIAV7UPh1Wh4SgBPX7E/l6g2AxXluL+qAdTHVFromppkRR+ParuL';
privateKey+='l5hJG+P2ve1PFp22UzYM9N/qGSfmUn1ch/J6gzIoyFDILmej/mFEh8Igj1k33S88';
privateKey+='EbHr2djUuxMaRSyREon4M7jUEBZ8C2DBoY/7PH+pAoIBAF7wqR6ByFWjDSLgt9Tq';
privateKey+='yGNoFwXfn9+SUNV02omPcyKwmhzuSHCFU6hX3d8csVimBk0R7lE9NdoliYQYbtIW';
privateKey+='y0x1R8yJ5v5n3Dupf02O1Xoy17hId8pMZ1OwVp5H/2o6ISXeV/ynhNuqzYmqcYOl';
privateKey+='WooLjQ6YOXZSkVoVyXii0hbUvChl3gM/iyMM9GA/YCzWeQsIOWoQdjbebCbU2he+';
privateKey+='f2wugGiGL4nBiFO1k0C8Y1vHCs+i/xJTX3rYFLLONM3J9w25w80Ve2PRSG6TXgFB';
privateKey+='Rn177k5PoRvyHbOAJHNgc6c+vO0O/ZAW3zaQK6U0GWiIEhlmImZX4uNI+xQFvJAu';
privateKey+='szcCggEAdOOsFRBiTMWgCZ3Q3vqB6rkzIUiqZ9vwJzJHy3mIoENkQLsjZgKSsJBq';
privateKey+='Ter+eMX8HaObGbKV7f10RBzJnV6yq/z7BohuUbkQ+an8BWYSnveWgu1eHifyaDVG';
privateKey+='h7SivmPeJDq6HE0Nh2jehfjpsmZmnUSTk0Lz7FBshT5o7R5T8Od7hXWIZMLBoXih';
privateKey+='CSjMXI6IbT94nG0110GbvlHkwbbK/TN/7rUA3Afepzq74kKgY5zJP3aj6tq1FlCh';
privateKey+='8Y5Y9uoB29EfrzSWrRkXjZQhR/yiyIulUV/NZS2KqWMtQGJjw9aeUVLqyglIzS5q';
privateKey+='SB/8ZTFkXV09anyt4L5j8t6Th1DUdA==';
return privateKey;
}
/**
* Method to get the public key
* Again this is just for illustative purposes, you would NEVER put a key like this in your code
*/
private String getPublicKey(){
String publicKey ='';
publicKey+='MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2lZiiwNURTqb+bgiPEIK';
publicKey+='XgMaQ5ZDUsZLEp/miRpyx8wY4QEESw9LJBl5Nwx/BUKd33xF9wQuvzPBmiBe6QHT';
publicKey+='rfo98R7/tPnUO6qQ6pON9yimCyWqCOgHwwOUfgn7CR+EXEdFvJExxf/kgd2jRquX';
publicKey+='wxQ+QMpiCYkHridxjv694G0XeTKQurSPFMxlEJZyhWbwStFut552JMRxJBO4LvyV';
publicKey+='qGXX2O1HFxH4VrXuQ4zsLc6tw7BruCeqXhpGAgDrcAgq5KWrpog3twKq0HiqA7E/';
publicKey+='ig49ajKA+E56tW7gijBUBCh2NCqgnk+SK3B2c7EjWGReM89oE+oeTfuehvhdvNZp';
publicKey+='BwBcyFefNjeT9IId/BQif0IOnK0cmCnqMaptdrG8VWxhMNKUTjFEWx2J3JyZU0xU';
publicKey+='KZaFc+WVvI4DvFxBI1m45BeyAbrvhQoBxWdycKD8jHmgHBjzz0QayQJUe9bJuX9l';
publicKey+='npR3yWSgSxvL8ThWPVvOb+3zC2c7ROMi9ftC4iNlu+NugA3yUUN7DthAP1sjpU6w';
publicKey+='a5NzL7I//W5EOgtmiCKjbJp77UFPoc2mddLcqCLkIlJh7g1r7yh4i8NxEHOwS0pW';
publicKey+='WkJMBuupGVH/zlMLH+btpJ8sMcN6BG6ba4bgPImde5UFqMW75Lptmp+rQCe3C+cj';
publicKey+='lRGIqvgvzuICFQF6Ozk3wysCAwEAAQ==';
return publicKey;
}
public class Sale {
public String messageId;
public String eventId;
public Decimal eventSchemaVersion;
public String eventInstanceOriginationDateTime;
public SaleEventData eventData;
}
public class SaleEventData {
public Integer siteId;
public Integer saleId;
public String purchasingClientId;
public List<PaymentDetails> payments;
public String saleDateTime;
public String soldById;
public String soldByName;
public Integer locationId;
public Integer totalAmountPaid;
public List<SaleItemDetails> items;
}
public class PaymentDetails {
public Integer paymentId;
public Integer paymentMethodId;
public String paymentMethodName;
public String paymentAmountPaid;
public Integer paymentLastFour;
public String paymentNotes;
}
public class SaleItemDetails {
public Integer itemId;
public String type;
public String name;
public Integer amountPaid;
public Integer amountDiscounted;
public Integer quantity;
public String recipientClientId;
public Integer paymentReferenceId;
}
Sale mbSale = (Sale)JSON.deserialize(jsonInput, Sale.class);
Comments