Happy Business Starts Here

Zuora Alumni

Custom Logic When a BillRun Completes or an Invoice is Posted

How can custom logic be run whenever a BillRun completes or a Invoice is Posted?



If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √

2 REPLIES 2
Zuora Alumni

Re: Custom Logic When a BillRun Completes or an Invoice is Posted

Overview

Attached are 2 event handlers written as JSP.

These need to be hosted in a Servlet container.

These handlers receive BillRun and Invoice events, and then examines invoices where there is a balance that is less than 0

If an invoice is encountered with a less than zero balance, it is put into Draft mode (if necessary), and then cancelled.

From the time an event is received, this should take about a second or less per invoice.


Notifications

Notifications need to be configured for:

  • Bill Run Completion | Completed Status

The Base URL for this notification is the entire path to the JSP which handles BillRun Completion events

This requires the field: DataSource.BillingRun.Id

ex: https://your.server.com/path/notifications/billrun/index.jsp

  • Invoice Posted | Invoice Posted Manually
  • Invoice Posted | Invoice Posted via API
  • Invoice Posted | Invoice Posted within a Bill Run of Auto-Post

The Base URL for all 3 of these notifications is the entire path to the JSP which handles Invoice Posted events

This requires the fields: DataSource.Invoice.Id | DataSource.Invoice.InvoiceNumber


Java

This example requires JDK 8, but could run on earlier versions if the stream helper classes are not used.

The JSP have several objects which deserialize the JSON event messages from Zuora

These are included (for simplicity) as inline as static classes, but really should be made into classes

  • Invoice
  • BillRun

The only external library being used in the example is Gson - Google's JSON library, which is used to deserialize the Event messages

All other libraries (in this example) are standard libraries which are included in the JDK 8


Error Handling

These example JSP have a single try/catch which will catch any possible error and print the stack trace to the Response Output Stream, and also the standard output stream.

In Zuora, you can see errors (if they happen) in the Notification History > Callout History

Then select the error link to see the details


Other Considerations

  • Security : some type of security should be configured to prevent unauthorized messages (not sent by Zuora)
  • Testing : this code should go through standard testing and follow your current processes for QA prior to deployment


If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √

Zuora Alumni

Re: Custom Logic When a BillRun Completes or an Invoice is Posted

Example : Invoice Posted Callout Handler JSP

 

<%@ page session="false" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.stream.Collectors" %>
<%@ page import="com.google.gson.Gson" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.util.stream.Stream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.net.MalformedURLException" %>
<%!

  private static class Invoice{
	String Id = "";
	String InvoiceNumber = "";
	String Status = "";
	Double Amount;
	Double Balance;

	public String toString(){
		return "Id:"+Id+"\nInvoiceNumber:"+InvoiceNumber;
	}
  }

  private static class Records{

	Invoice[] records;
	int size;
	boolean done;

  }

  public static String rest( String key, String secret, String method, URL url, String payload ) throws IOException {

	InputStream is = null;
	OutputStream os = null;

	String response = null;

	try{
        	HttpURLConnection http = (HttpURLConnection) url.openConnection();

        	http.setRequestMethod(method);

        	http.setRequestProperty("Authorization","Basic ****");
        	http.setRequestProperty("Content-Type","application/json");

        	http.setDoOutput(true);
        	http.setDoInput(true);

        	byte[] data = payload.getBytes();

        	http.setRequestProperty("Content-Length",""+data.length);

        	http.connect();

        	os = http.getOutputStream();
        	os.write(data);
        	os.flush();

		is = http.getInputStream();

        	BufferedReader br = new BufferedReader(new InputStreamReader(is));
        	Stream<String> lines = br.lines();
        	response = lines.collect(java.util.stream.Collectors.joining(System.lineSeparator()));
	
	}finally{
		
		os.close();
		is.close();
	
	}

        return response;

  }

%>
<%

BufferedReader br = request.getReader();
Stream<String> lines = br.lines();
String body = lines.collect(java.util.stream.Collectors.joining(System.lineSeparator()));

Gson gson = new Gson();

Invoice invoice = gson.fromJson(body,Invoice.class);

try{

	String uid = "USERID";
	String pw = "PW";

	Records results = gson.fromJson(
		rest
        	(
                	uid,pw,"GET",new URL("https://rest.apisandbox.zuora.com/v1/action/query"),
                	"{\"queryString\":\"select id,invoicenumber,amount,status from invoice where id='"+invoice.Id+"'\"}"
        	),
		Records.class
	);

	for(Invoice i: results.records){

		if( i.Amount < 0 ){

			String draft =
                		rest
                		(
                        		uid,pw,"PUT",new URL("https://rest.apisandbox.zuora.com/v1/object/invoice/"+i.Id),
                        		"{\"Id\":\""+i.Id+"\",\"Status\":\"Draft\"}"
                		);
			System.out.println(draft);

                        String cancel =
                                rest
                                (
                                        uid,pw,"PUT",new URL("https://rest.apisandbox.zuora.com/v1/object/invoice/"+i.Id),
                                        "{\"Id\":\""+i.Id+"\",\"Status\":\"Canceled\"}"
                                );
                        System.out.println(cancel);

		}

	}
	
}catch(Throwable t){

	t.printStackTrace();
	t.printStackTrace(response.getWriter());
	
}

%>

 

Example : BillRun Callout Handler JSP

 

<%@ page session="false" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.stream.Collectors" %>
<%@ page import="com.google.gson.Gson" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.util.stream.Stream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.net.MalformedURLException" %>
<%!

  private static class BillRun{
	String Id = "";
	public String toString(){
		return "Id:"+Id;
	}
  }

  private static class Invoice{
	String Id = "";
	String InvoiceNumber = "";
	String Status = "";
	Double Amount;
	Double Balance;

	public String toString(){
		return "Id:"+Id+"|InvoiceNumber:"+InvoiceNumber+"|Amount:"+Amount;
	}
  }

  private static class Records{

	Invoice[] records;
	int size;
	boolean done;

  }

  public static String rest( String key, String secret, String method, URL url, String payload ) throws IOException {

	InputStream is = null;
	OutputStream os = null;

	String response = null;

	try{
        	HttpURLConnection http = (HttpURLConnection) url.openConnection();

        	http.setRequestMethod(method);

        	http.setRequestProperty("Authorization","Basic ****");
        	http.setRequestProperty("Content-Type","application/json");

        	http.setDoOutput(true);
        	http.setDoInput(true);

        	byte[] data = payload.getBytes();

        	http.setRequestProperty("Content-Length",""+data.length);

        	http.connect();

        	os = http.getOutputStream();
        	os.write(data);
        	os.flush();

		is = http.getInputStream();

        	BufferedReader br = new BufferedReader(new InputStreamReader(is));
        	Stream<String> lines = br.lines();
        	response = lines.collect(java.util.stream.Collectors.joining(System.lineSeparator()));
	
	}finally{
		
		os.close();
		is.close();
	
	}

        return response;

  }

%>
<%

BufferedReader br = request.getReader();
Stream<String> lines = br.lines();
String body = lines.collect(java.util.stream.Collectors.joining(System.lineSeparator()));

Gson gson = new Gson();

BillRun billrun = gson.fromJson(body,BillRun.class);

try{

	String uid = "USERID";
	String pw = "PW";

	Records results = gson.fromJson(
		rest
        	(
                	uid,pw,"GET",new URL("https://rest.apisandbox.zuora.com/v1/action/query"),
                	"{\"queryString\":\"select id,invoicenumber,amount,status from invoice where status='Draft' and Amount < 0 \"}"
        	),
		Records.class
	);

	for(Invoice i: results.records){

		System.out.println("checking:"+i);

		if(i.Status.equals("Posted")){
			String draft =
                		rest
                		(
                        		uid,pw,"PUT",new URL("https://rest.apisandbox.zuora.com/v1/object/invoice/"+i.Id),
                        		"{\"Id\":\""+i.Id+"\",\"Status\":\"Draft\"}"
                		);
			System.out.println("Draft:"+draft);
		}
                String cancel =
                rest
                (
                        uid,pw,"PUT",new URL("https://rest.apisandbox.zuora.com/v1/object/invoice/"+i.Id),
                        "{\"Id\":\""+i.Id+"\",\"Status\":\"Canceled\"}"
                );
                System.out.println("Cancel:"+cancel);

	}
	
}catch(Throwable t){

	t.printStackTrace();
	t.printStackTrace(response.getWriter());
	
}

%>


If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √