Como Configurar um WebService em AdvPL utilizando SOAP

Hoje vou mostrar para vocês, como configurar um serviço de WebService em AdvPL, usando SOAP.

Primeiramente devemos entender como funciona um WebService, nesse sentido, basicamente existem 2 pontos principais, o que recebe informações e o que manda informações.

Por exemplo, o WebService do SEFAZ possui vários serviços, dentro deles, alguns métodos, como o de transmitir uma nota, ele RECEBE uma informação XML, e MANDA de volta a informação se deu certo ou errada a transmissão.

Portanto, iremos configurar o nosso Protheus para que ele também tenha isso, ou seja, receba requisições externas, trate e mande de volta informações tratadas. Para isso usaremos o protocolo SOAP para troca de mensagens, e nesse exemplo, utilizaremos JSON (você também pode usar XML ou outros padrões texto).

Antes de iniciar, baixe o pacote compactado com todos os arquivos necessários e de exemplos, Clicando Aqui.

Configurando um novo serviço (incluindo um novo appserver.ini)

01. Crie uma pasta appserver que será um novo serviço, por exemplo, “TOTVS | WebServices” que será responsável por receber as conexões

02. Crie um novo apo também, que irá conter um RPO exclusivo para esse serviço de WS. No nosso arquivo, nosso ambiente se chamará AMB_TESTE

03. No seu appserver.ini, você deve se atentar para a chave http, deixando ela ativa e setando uma porta que ficará liberada (nesse exemplo 8091)

;Liberando a porta 8091 para conexões com o WebService
[HTTP]
	ENABLE=1
	PORT=8091

04. No mesmo arquivo, no fim dele, configure o IP e a porta que serão disponíveis para conexão juntamente com um job, no exemplo abaixo, é configurado tanto o ip interno quanto externo

;Configurando IP Interno
[192.168.XXX.XXX:8091/ws]
	ENABLE=1
	PATH=D:\TOTVS\TESTE\Protheus_Data\web\ws
	ENVIRONMENT=AMB_TESTE
	INSTANCENAME=ws
	RESPONSEJOB=JOB_WS_0101
	DEFAULTPAGE=wsindex.apw

;Configurando IP Externo (se for necessário)
[177.87.XXX.XXX:8091/ws]
	ENABLE=1
	PATH=D:\TOTVS\TESTE\Protheus_Data\web\ws
	ENVIRONMENT=AMB_TESTE
	INSTANCENAME=ws
	RESPONSEJOB=JOB_WS_0101
	DEFAULTPAGE=wsindex.apw

05. Por último, no mesmo arquivo, agora será inserido as informações de JOB, que serão executadas automaticamente, “esperando as requisições”

;Configurando para Empresa 01 e Filial 01 e 99 instâncias de conexão
[JOB_WS_0101]
	TYPE=WEBEX
	ENVIRONMENT=AMB_TESTE
	INSTANCES=1,99
	SIGAWEB=WS
	INSTANCENAME=ws
	ONSTART=__WSSTART
	ONCONNECT=__WSCONNECT
	PREPAREIN=01,01

;Instrução para quando iniciar o serviço, iniciar o JOB_WS_0101
[ONSTART]
	JOBS=JOB_WS_0101

Obs.: Se você baixar o appserver.ini disponibilizado no arquivo, faça a configuração devida apontando para o seu license, altere as pastas e caminhos e alterne entre dbf / ctree

Copiando os arquivos para Protheus Data

06. Extraia o arquivo que foi baixado

07. Copie a pasta “web” para a raíz da sua Protheus_Data

Preparando um fonte em AdvPL

08. No arquivo baixado, separe as 3 includes que veio (aarray.ch, json.ch e shash.ch) e coloque na sua pasta de bibliotecas

09. Em integrações via SOAP geralmente existe algum TOKEN ou CHAVE para validar a conexão entre as duas partes, no nosso exemplo, é usado um parâmetro chamado MV_X_TOKEN. Crie na sua base esse parâmetro do tipo Caracter, e coloque um conteúdo único nele, esse conteúdo disponibilize apenas para programadores que irão consumir o seu serviço. No nosso caso, o conteúdo do parâmetro será “terminal@2019!tst123”

10. Crie um novo fonte, no nosso caso será um WebService de consulta de Clientes, iremos chamar o fonte de zWsCliente.prw

11. Dentro desse fonte, teremos 2 métodos, 1 para testar se está funcionando (método TstServ), e outro que irá retornar uma lista de clientes (método RetListCli), recebendo alguns parâmetros de filtro

12. Cada método terá 2 atributos, 1 de recebimento e 1 de envio, abaixo o fonte completo desenvolvido (também está no pacote para download):

//Bibliotecas
#Include "Protheus.ch"
#Include "APWebSrv.ch"
#Include "TBIConn.ch"
#Include "TBICode.ch"
#Include "TopConn.ch"
#Include "aarray.ch"
#Include "json.ch"
#Include "shash.ch"

/*
	Links de acesso
	Interno - http://192.168.XXX.XXX:8091/ws/
	Externo - http://177.87.XXX.XXX:8091/ws/
	
	WSDL Interno - http://192.168.XXX.XXX:8091/ws/zWsCliente.apw?WSDL
	WSDL Externo - http://177.87.XXX.XXX:8091/ws/zWsCliente.apw?WSDL
*/

WsService zWsCliente Description "WebService com funcoes de teste"
	//Atributos
	WsData   cTstRece  as String
	WsData   cTstSend  as String
	WsData   cFiltRece as String
	WsData   cFiltSend as String

	//Métodos
	WsMethod TstServ       Description "Metodo para testar se servico esta em funcionamento"
	WsMethod RetListCli    Description "Metodo para retornar uma lista de clientes filtrando informacoes"
EndWsService

/*
	Método TstServ
	Metodo para testar se servico esta em funcionamento
*/

WsMethod TstServ WsReceive cTstRece WsSend cTstSend WsService zWsCliente
	::cTstSend := "It's Works - Date " + dToC(Date()) + ", Time "+Time()
Return .T.


/*
	Método RetListCli
	Metodo para retornar uma lista de clientes filtrando informacoes
	
	Exemplo do JSON que será recebido
	{
		"Dados": {
			"Vendedor": "000000",
			"Estado": "SP",
			"Cidado": "Bauru"
    	},
    	"Token" : "aaaa"
	}
	
	Exemplo de JSON que será enviado de volta (se tudo estiver certo)
	{
		"Clientes": {
			"000001" : {
				"Codigo":"XXXXXX",
				"RazaoSocial":"Beluga Corp",
				"NomeFantasia":"Beluga",
				"CGC":"00.000.000/0000-00"
			},
			"000002" : {
				"Codigo":"YYYYYY",
				"RazaoSocial":"Beluga 2 Corp",
				"NomeFantasia":"Beluga 2",
				"CGC":"00.000.000/0000-00"
			},
			...
    	}
	}
*/

WsMethod RetListCli WsReceive cFiltRece WsSend cFiltSend WsService zWsCliente
	//Retorno do Método RetListCli (.T. se está tudo certo ou .F. se houve falha)
	Local lRet       := .T.
	
	//Variável de Token pegando da tabela SX6
	Local cTokWs     := Alltrim(GetMV('MV_X_TOKEN'))
	
	//Parâmetros recebidos pelo JSON (variável cFiltRece)
	Local cParVend   := ""
	Local cParEst    := ""
	Local cParMun    := ""
	Local cParToken  := ""
	
	//Consulta SQL para filtragem dos dados e variáveis usadas
	Local cQryCli    := ""
	Local nAtual     := 0
	Local cCGC       := ""
	
	//Variáveis usadas para transformar o JSON em Objeto
	Private oJSON    := Nil
	Private oDados   := Nil
	
	
	//Deserializando o JSON (transformando a "string" em "objeto")
	If (FWJsonDeserialize(::cFiltRece, @oJSON))
	
		//Separando o objeto de dados, e o Token
		oDados     := oJSON:Dados
		cParToken  := Iif(Type("oJSON:Token") != "U", oJSON:Token, "")
		
		//Se o Token recebido for o mesmo do parâmetro, prossegue
		If cParToken == cTokWs
			
			//Pegando os 3 filtros possíveis vindo dentro de "Dados"
			cParVend   := Alltrim(Iif(Type("oDados:Vendedor") != "U", oDados:Vendedor, ""))
			cParEst    := Alltrim(Iif(Type("oDados:Estado")   != "U", oDados:Estado,   ""))
			cParMun    := Alltrim(Iif(Type("oDados:Cidade")   != "U", oDados:Cidade,   ""))
			
			//Se os 3 parâmetros estiverem em branco, retorna erro, só pode prosseguir se pelo menos 1 estiver preenchido
			If Empty(cParVend) .And. Empty(cParEst) .And. Empty(cParMun)
				SetSoapFault('Erro', 'Os 3 parametros estao em branco, preencha pelo menos 1!')
				lRet := .F.
				
			Else
			
				//Selecionando os clientes conforme o filtro
				cQryCli := " SELECT "                                                  + CRLF
				cQryCli += "     A1_COD, "                                             + CRLF
				cQryCli += "     A1_NOME, "                                            + CRLF
				cQryCli += "     A1_NREDUZ, "                                          + CRLF
				cQryCli += "     A1_CGC "                                              + CRLF
				cQryCli += " FROM "                                                    + CRLF
				cQryCli += "     " + RetSQLName('SA1') + " SA1 "                       + CRLF
				cQryCli += " WHERE "                                                   + CRLF
				cQryCli += "     A1_FILIAL = '" + FWxFilial('SA1') + "' "              + CRLF
				If ! Empty(cParVend)
					cQryCli += "     AND UPPER(A1_VEND) LIKE '%" + cParVend + "%' "    + CRLF
				EndIf
				If ! Empty(cParEst)
					cQryCli += "     AND UPPER(A1_EST)  LIKE '%" + cParEst  + "%' "    + CRLF
				EndIf
				If ! Empty(cParMun)
					cQryCli += "     AND UPPER(A1_MUN)  LIKE '%" + cParMun  + "%' "    + CRLF
				EndIf
				cQryCli += "     AND SA1.D_E_L_E_T_ = ' ' "                            + CRLF
				TCQuery cQryCli New Alias "QRY_CLI"
				
				//Se existirem dados da consulta
				If ! QRY_CLI->(EoF())	
				
					//Começa a montar o JSON de retorno
					::cFiltSend += ' { '                  + CRLF
					::cFiltSend += '  "Clientes" : { '    + CRLF
					
					//Enquanto houver clientes
					While ! QRY_CLI->(EoF())
						//Incrementa o contador
						nAtual++
						
						//Transformando o CNPJ ou CPF para visualização
						cCGC := Alltrim(QRY_CLI->A1_CGC)
						If Len(Alltrim(SA2->A2_CGC)) > 11
							cCGC := Alltrim(Transform(cCGC, "@R 99.999.999/9999-99"))
						Else
							cCGC := Alltrim(Transform(cCGC, "@R 999.999.999-99"))
						EndIf
						
						//Monta o cliente atual
						::cFiltSend +=           '   "' + StrZero(nAtual, 6) + '": { '                           + CRLF
						::cFiltSend +=           '     "Codigo":"'       + QRY_CLI->A1_COD             + '", '   + CRLF
						::cFiltSend +=           '     "RazaoSocial":"'  + Alltrim(QRY_CLI->A1_NOME)   + '", '   + CRLF
						::cFiltSend +=           '     "NomeFantasia":"' + Alltrim(QRY_CLI->A1_NREDUZ) + '", '   + CRLF
						::cFiltSend +=           '     "CGC":"'          + cCGC                        + '"'     + CRLF
						::cFiltSend +=           '   }'
						
						//Pula para o próximo cliente
						QRY_CLI->(DbSkip())
						
						//Se não for o último cliente, acrescenta a vírgula
						If ! QRY_CLI->(EoF())
							::cFiltSend += ','
						EndIf
						::cFiltSend += CRLF
					EndDo
					::cFiltSend += '  } '     + CRLF
					::cFiltSend += ' }'       + CRLF
					
				//Se não existirem clientes, retorna mensagem de falha
				Else
					SetSoapFault('Erro', 'Nao existem clientes nesse filtro usado!')
					lRet := .F.
				EndIf
				QRY_CLI->(DbCloseArea())
			EndIf
			
		//Caso seja um token inválido
		Else
			SetSoapFault('Erro', 'Token invalido!')
			lRet := .F.
		EndIf
		
	//Se houve erro ao deserializar o JSON
	Else
		SetSoapFault('Erro', 'JSON nao deserializado!')
		lRet := .F.
	EndIf

Return lRet

13. Compile a rotina nesse ambiente, e inicie o serviço

Pegando o WSDL

14. O WSDL é o endereço que ficará disponível para acessar a sua rotina, para pegar o endereço, acesse o IP e a porta configurados no appserver.ini seguido por “/ws”, por exemplo, “192.168.XXX.XXX:8091/ws”

15. Nessa página, procure pelo seu serviço zWsCliente e clique nele

Encontrando o serviço

16. Na descrição do serviço, clique nesse link

Pegando o WSDL

17. Copie a URL, no nosso exemplo, ficou 192.168.XXX.XXX:8091/ws/ZWSCLIENTE.apw?WSDL

Testando a conexão com o SoapUI

18. Baixe o aplicativo SoapUI e configure adicionando essa WSDL nele. Para saber como configurar, leia esse artigo Como configurar o SoapUI

19. Faça um teste no método TstServ, o resultado deve ser o similar ao abaixo

Testando método

20. Efetue testes no método RetListCli, lembrando que se houver erros, serão mostrados

Verificando mensagem de falha

21. Caso dê certo, exibirá uma lista de clientes

Verificando serviço com êxito

Testando a conexão através do PHP

22. Caso você tenha um servidor rodando PHP, preparei um pequeno fonte para testar a integração com o Protheus, segue abaixo (também está no arquivo compactado para download)

<html>
<head>
<title>Testes Clientes</title>
</head>
<body>
	<?php
		//Cria as variaveis usadas em branco
		$vendedor = '';
		$estado   = '';
		$cidade   = '';
		$ok       = false;
		$mensagem = '';
		$clientes = null;
		
		//Pega os dados que foram confirmados
		if (isset($_POST['vendedor'])) {
			$vendedor = $_POST['vendedor'];
			$ok = true;
		}
		if (isset($_POST['estado'])) {
			$estado = $_POST['estado'];
			$ok = true;
		}
		if (isset($_POST['cidade'])) {
			$cidade = $_POST['cidade'];
			$ok = true;
		}
		
		//Se algum parâmetro foi preenchido
		if ($ok) {
			//Tenta conectar com o WebService do Protheus
			try {
				$soapClient = new SoapClient('http://192.168.XXX.XXX:8091/ws/zWsCliente.apw?WSDL', ['exceptions' => true]);
			} catch (SoapFault $exception) {
				$mensagem = 'Houve um erro ao buscar os dados, <b>contate o Administrador</b>. Exception: <br>';
				$mensagem .= $exception->getMessage();
			}
			
			//Tenta executar o metodo que retorna a lista de clientes
			try {
				$requestData = array(
					"RETLISTCLI" => array(
						"CFILTRECE" => 
							'{'.
							'	"Dados": {'.
							'		"Vendedor":"' . $vendedor . '", '.
							'		"Estado":"'   . $estado   . '", '.
							'		"Cidade":"'   . $cidade   . '"  '.
							'	},'.
							'	"Token": "terminal@2019!tst123"'.
							'}'
						)
					);
				
				$response = $soapClient->__soapCall("RETLISTCLI", $requestData);
				
				$jsonLogin = json_decode($response->RETLISTCLIRESULT);
				if (json_last_error() == 0) {
					$clientes = $jsonLogin;
				}
				
			} catch (SoapFault $exception) {
				$mensagem = 'Houve um erro ao buscar os dados, <b>contate o Administrador</b>. Exception: <br>';
				$mensagem .= $exception->getMessage();
			}
			
		}
	?>
	
	<form action="clientes.php" method="post">
	Vendedor:<br>
	<input type="text" id="vendedor" name="vendedor" placeholder="Digite o código do Vendedor" maxlength="6" style="width: 200px;" value=<?php echo $vendedor;?>><br>
	
	Estado:<br>
	<select id="estado" name="estado">
		<option <?php if (empty($estado))  echo 'selected="selected"'; ?>></option>
		<option <?php if ($estado == "AC") echo 'selected="selected"'; ?>>AC</option>
		<option <?php if ($estado == "AL") echo 'selected="selected"'; ?>>AL</option>
		<option <?php if ($estado == "AP") echo 'selected="selected"'; ?>>AP</option>
		<option <?php if ($estado == "AM") echo 'selected="selected"'; ?>>AM</option>
		<option <?php if ($estado == "BA") echo 'selected="selected"'; ?>>BA</option>
		<option <?php if ($estado == "CE") echo 'selected="selected"'; ?>>CE</option>
		<option <?php if ($estado == "DF") echo 'selected="selected"'; ?>>DF</option>
		<option <?php if ($estado == "ES") echo 'selected="selected"'; ?>>ES</option>
		<option <?php if ($estado == "GO") echo 'selected="selected"'; ?>>GO</option>
		<option <?php if ($estado == "MA") echo 'selected="selected"'; ?>>MA</option>
		<option <?php if ($estado == "MT") echo 'selected="selected"'; ?>>MT</option>
		<option <?php if ($estado == "MS") echo 'selected="selected"'; ?>>MS</option>
		<option <?php if ($estado == "MG") echo 'selected="selected"'; ?>>MG</option>
		<option <?php if ($estado == "PA") echo 'selected="selected"'; ?>>PA</option>
		<option <?php if ($estado == "PB") echo 'selected="selected"'; ?>>PB</option>
		<option <?php if ($estado == "PR") echo 'selected="selected"'; ?>>PR</option>
		<option <?php if ($estado == "PE") echo 'selected="selected"'; ?>>PE</option>
		<option <?php if ($estado == "PI") echo 'selected="selected"'; ?>>PI</option>
		<option <?php if ($estado == "RJ") echo 'selected="selected"'; ?>>RJ</option>
		<option <?php if ($estado == "RN") echo 'selected="selected"'; ?>>RN</option>
		<option <?php if ($estado == "RS") echo 'selected="selected"'; ?>>RS</option>
		<option <?php if ($estado == "RO") echo 'selected="selected"'; ?>>RO</option>
		<option <?php if ($estado == "RR") echo 'selected="selected"'; ?>>RR</option>
		<option <?php if ($estado == "SC") echo 'selected="selected"'; ?>>SC</option>
		<option <?php if ($estado == "SP") echo 'selected="selected"'; ?>>SP</option>
		<option <?php if ($estado == "SE") echo 'selected="selected"'; ?>>SE</option>
		<option <?php if ($estado == "TO") echo 'selected="selected"'; ?>>TO</option>
	</select><br>
				  
	Cidade:<br>
	<input type="text" id="cidade" name="cidade" placeholder="Digite o nome da Cidade" maxlength="30" style="width: 200px;" value=<?php echo $cidade;?>><br>
	<br><br>
	<button type="submit">Confirmar</button>
	</form>
	<hr>
	Resultado(s):<br>
	<?php
		//Se existe mensagem de erro, mostra ela
		if (! empty($mensagem)) {
			echo '<font color="red">' . $mensagem . '</font><br>';
		}
	?>
	<br>
	<table border="2">
		<thead>
			<tr>
				<th><b>Código</b></th>
				<th><b>Razão Social</b></th>
				<th><b>Nome Fantasia</b></th>
				<th><b>CGC</b></th>
			</tr>
		</thead>
		<tbody>
			<?php
				if ($clientes != null) {
					foreach ($clientes->Clientes as $cli) {
						echo '<tr>';
						echo '	<td>' . $cli->Codigo        . '</td>';
						echo '	<td>' . $cli->RazaoSocial   . '</td>';
						echo '	<td>' . $cli->NomeFantasia  . '</td>';
						echo '	<td>' . $cli->CGC           . '</td>';
						echo '</tr>';
					}
				}
			?>
		</tbody>
	</table>
</body>
</html>

23. Abrindo a página php, note que na área de cima são os filtros, e em baixo os resultados. Se ocorrer algum erro, será mostrado a mensagem nos resultados

Mostrando erro em php

24. Se tudo der certo, será mostrado o resultado

Mostrando o resultado em php


Basicamente é isso jovens, espero que tenham gostado do tutorial (deu trabalho para montar rs). Com essa premissa é possível fazer várias coisas com o Protheus, como por exemplo, um Painel de Vendas, inclusão de Documento de Entrada, inclusão de Pedidos, consulta de indicadores, consulta de títulos, etc.

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan_Atilio
Analista e desenvolvedor de sistemas. Técnico em Informática pelo CTI da Unesp. Graduado em Banco de Dados pela Fatec Bauru. Entusiasta de soluções Open Source e blogueiro nas horas vagas.

8 Responses

  1. Vagner Almeida disse:

    Dan, boa noite.
    Como sempre, mais uma excelente aula.
    Estou iniciando o estudo de webservice com Rest.
    Você poderia montar um material com esta opção.
    Um forte abraço.

  2. Edivaldo Duque de Souza disse:

    Bom dia!
    Mais uma vez um excelente conteúdo, obrigado por compartilhar!
    Abraço

  3. Guilherme Leonel disse:

    Isso não foi um tutorial, foi um curso completo. Parabens pelo conteudo.

  4. FLAVIO disse:

    Aparece para mim no teste. Erro : JSON nao deserializado!
    Oque sera que esta faltando?

Deixe uma resposta