Stripe Webhooks subscription with Salesforce Endpoint - Apex class and Apex Test Class
Steps in making this integration possible:
Problem:
A new field('Stripe Transaction status') in Salesforce contact object that should show the Stripe transaction 'status' attribute of events such as "charge.succeeeded/charge.refunded/charge.failed/charge.pending/charge.refunded" based on the contact email address.
1) In Salesforce, create a new field under the contact object(via the contacts page layouts).
2) Now, code your salesforce apex class to implemenet the Endpoint. Find the code below:
note: the code below parses the json data pass from Stripe webhooks as of date :Aug 14th, 2017.
------------------------------------------------------------------------------
/**
* Hint:
* - Site name : https://<<force.comurl>/<your custom string url>
* - Relative endpoint : https://<<force.comurl>/<your custom string url>/services/apexrest/StripeEndpointService
*/
@RestResource(urlMapping='/StripeEndpointService')
global class GNZ_StripeEndpointAPI {
@HttpGet
global static void doGet() {
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
res.addHeader('Content-Type', 'application/json');
String resBody='';
try{
res.statusCode=200;
resBody = '{"success":true}';
} catch(Exception e) {
res.statusCode=500;
resBody = '{"success":false}';
}
res.responseBody = Blob.valueOf(resBody);
}
@HttpPost
global static void handlePost() {
system.debug('In Post Method');
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
String reqJson= req.requestBody.toString();
String resBody='';
try{
res.statusCode=200;
resBody = '{"success":true}';
// Parse entire JSON response.
String[] strings = getTypeEmail(reqJson);
List<Contact> contacts = [SELECT Id,Stripe_Transaction_Status__c FROM Contact WHERE Email = :strings.get(1)];
for (Contact contact: contacts) {
contact.Stripe_Transaction_Status__c = strings.get(0);//reqJson;
}
update contacts;
} catch (Exception e) {
res.statusCode=500;
resBody = '{"success":false}';
}
res.responseBody = Blob.valueOf(resBody);
//types of transactions subscribed to: //charge.captured/charge.failed/charge.succeeded/charge.pending/charge.refunded
}
global static String[] getTypeEmail(String reqJson){
JSONParser parser = JSON.createParser(reqJson);
String[] strs=new String[]{'status','contactEmail'};
while (parser.nextToken() != null) {
if(parser.getText()=='metadata') {
while (parser.nextToken() != null) {
if(parser.getText()=='email'){
parser.nextToken();
strs[1]=parser.getText();
}
}
}
}//While ends
JSONParser parser2 = JSON.createParser(reqJson);
while (parser2.nextToken() != null) {
if(parser2.getText()=='data') {
while (parser2.nextToken() != null) {
if(parser2.getText()=='object') {
while (parser2.nextToken() != null) {
//if(parser2.getText()=='data') {parser.skipChildren(); break;}
if(parser2.getText()=='status'){//seller_message') {//type'){//
parser2.nextToken();
strs[0]=parser2.getText();
}
}
}
}
}
}//While ends
return strs;
}
}
------------------------------------------------------------------------------
3) Implement force.com pages to make your endpoint publicly accessible without authentication requirement, so that the webhooks can post response to your endpoint class implemented in step 2. Remember your activate your force.com site.
4) Now add this endpoint url in your Stripe account as a subscription link as in snapshot below:
- Sign into Stripe account. Navigate to "api". Then select "webhooks".
- Add new endpoint
- paste your endpoint url (----*) here and select teh events you want to be subscribed to.
5)Now, try to use the gateway for payments. This should post the Stripe event into the field created in step 1 under Salesforce Contact object.
Further notes:
The Apex test class to obtain 85% code coverage for the above class:
--------------------------------------------------------------------------------------
@isTest
private class StripeEndpointAPI_test{
static testMethod void myUnitTest() {
String json = '{'+
' \"id\": \"evt_1AoxgFCxjVKdCysVBtgPzEzw\",'+
' \"object\": \"event\",'+
' \"api_version\": \"2017-06-05\",'+
' \"created\": 1502298555,'+
' \"data\": {'+
' \"object\": {'+
' \"id\": \"ch_1AoxgBCxjVKdCysVCxiZY6bE\",'+
' \"object\": \"charge\",'+
' \"amount\": 100,'+
' \"amount_refunded\": 0,'+
' \"application\": \"ca_9SeNjLFU139kX1AeHVL71uUDJdfuRRjJ\",'+
' \"application_fee\": null,'+
' \"balance_transaction\": \"txn_1AoxgFCxjVKdCysVs7qobRsC\",'+
' \"captured\": true,'+
' \"created\": 1502298551,'+
' \"currency\": \"usd\",'+
' \"customer\": null,'+
' \"description\": null,'+
' \"destination\": null,'+
' \"dispute\": null,'+
' \"failure_code\": null,'+
' \"failure_message\": null,'+
' \"fraud_details\": {'+
' },'+
' \"invoice\": null,'+
' \"livemode\": false,'+
' \"metadata\": {'+
' \"envelopeId\": \"0ce62a87-hoiuohbouh-4649-87d1\",'+
' \"name\": \"contact name\",'+
' \"email\": \"contact email\",'+
' \"item-1\": \"1;;;\"'+
' },'+
' \"on_behalf_of\": null,'+
' \"order\": null,'+
' \"outcome\": {'+
' \"network_status\": \"approved_by_network\",'+
' \"reason\": null,'+
' \"risk_level\": \"normal\",'+
' \"seller_message\": \"Payment complete.\",'+
' \"type\": \"authorized\"'+
' },'+
' \"paid\": true,'+
' \"receipt_email\": null,'+
' \"receipt_number\": null,'+
' \"refunded\": false,'+
' \"refunds\": {'+
' \"object\": \"list\",'+
' \"data\": ['+
''+
' ],'+
' \"has_more\": false,'+
' \"total_count\": 0,'+
' \"url\": \"/v1/charges/ch_1AoxgBCxjVKdCysVCxiZY6bE/refunds\"'+
' },'+
' \"review\": null,'+
' \"shipping\": null,'+
' \"source\": {'+
' \"id\": \"card_1AoxgBCxjVKdCysVhDj3krwC\",'+
' \"object\": \"card\",'+
' \"address_city\": null,'+
' \"address_country\": null,'+
' \"address_line1\": null,'+
' \"address_line1_check\": null,'+
' \"address_line2\": null,'+
' \"address_state\": null,'+
' \"address_zip\": null,'+
' \"address_zip_check\": null,'+
' \"brand\": \"Visa\",'+
' \"country\": \"US\",'+
' \"customer\": null,'+
' \"cvc_check\": \"pass\",'+
' \"dynamic_last4\": null,'+
' \"exp_month\": 12,'+
' \"exp_year\": 2020,'+
' \"fingerprint\": \"X8LJHJfP0sHfIOuU\",'+
' \"funding\": \"credit\",'+
' \"last4\": \"4242\",'+
' \"metadata\": {'+
' },'+
' \"name\": null,'+
' \"tokenization_method\": null'+
' },'+
' \"source_transfer\": null,'+
' \"statement_descriptor\": null,'+
' \"status\": \"succeeded\",'+
' \"transfer_group\": null'+
' },'+
' \"previous_attributes\": {'+
' \"balance_transaction\": null,'+
' \"captured\": false'+
' }'+
' },'+
''+
' \"type\": \"charge.captured\"'+
'}';
String jsonWrong = '{'+
' \"id\": \"evt_1AoxgFCxjVKdCysVBtgPzEzw\",'+
' \"object\": \"event\",'+
' \"api_version\": \"2017-06-05\",'+
' \"created\": 1502298555,'+
' \"data\": {'+
' \"object\": {'+
' \"id\": \"ch_1AoxgBCxjVKdCysVCxiZY6bE\",'+
' \"object\": \"charge\",'+
' \"amount\": 100,'+
' \"amount_refunded\": 0,'+
' \"application\": \"ca_9SeNjLFU139kX1AeHVL71uUDJdfuRRjJ\",'+
' \"application_fee\": null,'+
' \"balance_transaction\": \"txn_1AoxgFCxjVKdCysVs7qobRsC\",'+
' \"captured\": true,'+
' \"created\": 1502298551,'+
' \"currency\": \"usd\",'+
' \"customer\": null,'+
' \"description\": null,'+
' \"destination\": null,'+
' \"dispute\": null,'+
' \"failure_code\": null,'+
' \"failure_message\": null,'+
' \"fraud_details\": {'+
' },'+
' \"invoice\": null,'+
' \"livemode\": false,'+
' \"metadata\": {'+
' \"envelopeId\": \"0ce6jpnpiib75dee1a4\",'+
' \"name\": \"contact name\",'+
' \"email\": \"contact email\",'+
' \"item-1\": \"1;;;\"'+
' },'+
' \"on_behalf_of\": null,'+
' \"order\": null,'+
' \"outcome\": {'+
' \"network_status\": \"approved_by_network\",'+
' \"reason\": null,'+
' \"risk_level\": \"normal\",'+
' \"seller_message\": \"Payment complete.\",'+
' \"type\": \"authorized\"'+
' },'+
' \"paid\": true,'+
' \"receipt_email\": null,'+
' \"receipt_number\": null,'+
' \"refunded\": false,'+
' \"refunds\": {'+
' \"object\": \"list\",'+
' \"data\": ['+
''+
' ],'+
' \"has_more\": false,'+
' \"total_count\": 0,'+
' \"url\": \"/v1/charges/ch_1AoxgBCxjVKdCysVCxiZY6bE/refunds\"'+
' },'+
' \"review\": null,'+
' \"shipping\": null,'+
' \"source\": {'+
' \"id\": \"card_1AoxgBCxjVKdCysVhDj3krwC\",'+
' \"object\": \"card\",'+
' \"address_city\": null,'+
' \"address_country\": null,'+
' \"address_line1\": null,'+
' \"address_line1_check\": null,'+
' \"address_line2\": null,'+
' \"address_state\": null,'+
' \"address_zip\": null,'+
' \"address_zip_check\": null,'+
' \"brand\": \"Visa\",'+
' \"country\": \"US\",'+
' \"customer\": null,'+
' \"cvc_check\": \"pass\",'+
' \"dynamic_last4\": null,'+
' \"exp_month\": 12,'+
' \"exp_year\": 2020,'+
' \"fingerprint\": \"X8LJHJfP0sHfIOuU\",'+
' \"funding\": \"credit\",'+
' \"last4\": \"4242\",'+
' \"metadata\": {'+
' },'+
' \"name\": null,'+
' \"tokenization_method\": null'+
' },'+
' \"source_transfer\": null,'+
' \"statement_descriptor\": null,'+
' \"status\": \"succeeded\",'+
' \"transfer_group\": null'+
' },'+
' \"previous_attributes\": {'+
' \"balance_transaction\": null,'+
' \"captured\": false'+
' }'+
' },';
RestRequest req3 = new RestRequest();
RestResponse res3 = new RestResponse();
req3.requestURI = '/services/apexrest/StripeEndpointService';
//https://<<force.comurl>/<your custom string url>/services/apexrest/StripeEndpointService
req3.httpMethod = 'POST';
req3.addParameter('Content-Type', 'application/json');
req3.requestBody = Blob.valueOf(json);
RestContext.request = req3;
RestContext.response = res3;
GNZ_StripeEndpointAPI.handlePost();
RestRequest req = new RestRequest();
RestResponse res = new RestResponse();
req.requestURI = 'https://<<force.comurl>/<your custom string url>/services/apexrest/StripeEndpointService';
req.httpMethod = 'GET';
RestContext.request = req;
RestContext.response = res;
GNZ_StripeEndpointAPI.doGet();
}
}
--------------------------------------------------------------------------------------
Comments
Post a Comment