quinta-feira, 2 de junho de 2011

Carrinho de Compras - Parte 2 (Integração com PagSeguro do UOL)

Como eu tinha prometido vamos continuar com a série "Carrinho de Compras", quem não viu o primeiro post Carrinho de Compras - Parte 1, recomendo dar uma olhada apenas para ter uma idéiade como nosso carrinho funciona.

Hoje a idéia é falar da integração com o PagSeguro do UOL através do nosso ColdFusion. O primeiro desafio é documentação em ColdFusion, que por ser uma linguagem extremamente fácil e produtiva, as pessoas acabam esquecendo de criar documentação, meu objetivo é mudar isso radicalmente.

Para a integração, vamos precisar de dois manuais no site do PagSeguro. O primeiro é o da integração com Carrinho Próprio e o segundo que é o Retorno Automático de Dados

Apenas lembrando que a mágica depende da configuração correta na sua conta PagSeguro, se você ainda não o fez ou quer revizar vamos aos passos:

  1. Criar uma conta no PagSeguro
  2. Se for usar a conta em produção, é bom validar os dados.
  3. Lembre-se de configurar o Frete, no menu Preferências/Frete
  4. Defina o link para o Retorno Automático de Dados, e gere o Token no menu Preferências/Retorno automático de dados
 Após esses passos simples, vamos a parte código da nossa integração. Na página de integração com o carrinho, é explicado que você tem duas opções para o envio de dados do comprador. Uma você deixa isso a cargo do PagSeguro, e outra você realiza este envio.

Para este post vamos fazer o modelo de enviarmos todos os dados, até porque você provavelmente nesse ponto, já esta com o usuário logado no seu e-commerce e cadastrado. Lembre-se, esse é o momento de finalização da compra, então você já deve verificar seu estoque e estar com tudo pronto.

Para facilitar nossa vida, vamos criar um novo componente chamado PagSeguro.cfc, que espero que ajude na integração da maioria dos projetos. Os métodos que percebi são:
  • setCart: vai ser o método responsável por receber os dados a serem enviados ao PagSeguro;
  • setType: para informar o tipo do carrinho, seja próprio, ou PagSeguro
  • setCoin: serve para informar o tipo de moeda, hoje o pagSeguro só trabalha com Real
  • setExtras: será utilizado para valores adicionais ou descontos
  • setClient: irá servir para informar os dados do fulano que esta fazendo a compra
  • setShipping: informaremos o tipo de envio do produto
  • parseCurrency: esse método irá converter o valor para o padrão PagSeguro
  • send: esse método irá realizar a mágica de enviar os dados para o PagSeguro
 PagSeguro.cfc
   1: <cfset Variables.ACTION = "https://pagseguro.uol.com.br/checkout/checkout.jhtml" />
   2: <cfset Variables.dsEmail = "EMAIL_CADASTRADO_PAGSEGURO" />
   3: <cfset Variables.tpCart = "CP" />
   4: <cfset Variables.tpCoin = "BRL" />
   5: <cfset Variables.tpShipping = "UC" />
   6: <cfset Variables.vlExtras = 0 />
   7: <cfset Variables.userData = structNew()>
   8: <cfset variables.cart = ArrayNew(1) />
Ao iniciar nosso processo, criamos algumas váriaveis que serão controladas pelos nossos métodos antes do envio. Algumas delas, já definimos um valor padrão, para evitar falhas ou necessidades de formas de uso na programação.

É importante lembrar que um componente deve ser simples a ponto de não termos que "adaptar" nossa programação ao funcionamento dele.

setCart
   1: <cffunction name="setCart" access="public" returntype="void" output="false">
   2:     <cfargument name="cart" type="ShoppingCart" required="true">
   3:     <cfargument name="nmLogin" type="String" default="" required="false">
   4:  
   5:     <cfif len(trim(arguments.nmLogin)) and isValid("email", arguments.nmLogin)>
   6:         <cfset Variables.dsEmail = arguments.nmLogin />
   7:     </cfif>
   8:     <cfset variables.cart = Arguments.cart.list()>
   9: </cffunction>
Algumas observações e curiosidades para o nosso setCart. De cara algo diferente no atributo type da nossa cfargument. O tipo requerido para o nosso carrinho se chama ShoppingCart, que é o mesmo componente CFC que criamos no post anterior, isso nos facilita um pouco a vida, pois estamos manipulando algo que esta de acordo com nossa especificação inicial.

Muitos podem achar que o post sai do nada, porém antes de começar essa maratona, desenhei todo o fluxo UML para o nosso projeto. O único motivo dos arquivos estarem todos juntos é para facilitar o entendimento e ajudar vocês a executarem o projeto em seu próprio servidor local.

Na verificação do login, repare que não faço uso de isDefined ou structKeyExist, isso, porque defini o cfargument com a propriedade default="", que faz o nosso cfargument funcionar como um cfparam, cria a variável que você pediu com um valor padrão, caso ela não exista. Com isso, verificamos se a quantidade de caracteres é superior a zero e se o valor que temos em mãos é um e-mail.

A técnica do len(trim("STRING")) é baseada numa regra da lógica boleana que diz "Todo número diferente de zero verdadeiro". Cuidado com as entrelinhas, 1 e -1 são verdadeiro apenas 0 é falso.

A função isValid do ColdFusion é uma das mais legais e menos conhecidas, vale a pena dar uma olhada na documentação, para as possibilidades!

setType
   1: <cffunction name="setType" access="public" returntype="void" output="false">
   2:     <cfargument name="type" type="string" required="true">
   3:     <cfset var lValidTypes = "CP,CBR">
   4:     <cfif listContainsNoCase(lValidTypes, arguments.type)>
   5:         <cfset Variables.tpCart = arguments.type>
   6:     <cfelse>
   7:         <cfset Variables.tpCart = listFirst(lValidTypes)>
   8:     </cfif>
   9: </cffunction>
Esse método, é apenas para dar opção para quem não desejar usar o Carrinho Proprio, e não afeta em nada nosso envio.

setCoin
   1: <cffunction name="setCoin" access="public" returntype="void" output="false">
   2:     <cfargument name="coin" type="string" required="true">
   3:     <cfset var lValidTypes = "BRL">
   4:     <cfif listContainsNoCase(lValidTypes, arguments.coin)>
   5:         <cfset Variables.tpCoin = arguments.type>
   6:     <cfelse>
   7:         <cfset Variables.tpCoin = listFirst(lValidTypes)>
   8:     </cfif>
   9: </cffunction>
Este é mais um para a lista de não temos muito o que fazer, mas vai que o UOL resolver colocar pagSeguro recebendo em outras moedas, esperança é a última que morre.

setExtras
   1: <cffunction name="setExtras" access="public" returntype="void" output="false">
   2:     <cfargument name="value" type="numeric" required="true">
   3:     <cfargument name="isDiscount" type="boolean" default="false" required="false">
   4:     
   5:     <cfif arguments.isDiscount>
   6:         <cfset Variables.vlExtras = arguments.value * -1>
   7:     <cfelse>
   8:         <cfset Variables.vlExtras = arguments.value>
   9:     </cfif>
  10: </cffunction>
Esse método é para evitar problemas entre acressimos e descontos. Na documentação do PagSeguro para o campo extras eles falam "Utilize este campo para acrescentar ao pedido um valor adicional, caso necessário. Exemplo: você pode incluir o valor da embalagem de presente. Você também pode enviar um valor negativo neste campo, caso deseje oferecer um desconto ao seu cliente.".

Pensando nisso, o método recebe o valor e um boleano informando se é desconto ou não. Isso evita que você tenha que fazer a conversão de desconto

setClient
   1: <cffunction name="setClient" access="public" returntype="void" output="false">
   2:     <cfargument name="nmPerson" type="string" required="true">
   3:     <cfargument name="dsEmail" type="string" required="true">
   4:     <cfargument name="nbPostalCode" type="string" required="true">
   5:     <cfargument name="dsAddress" type="string" required="true">
   6:     <cfargument name="dsDistrict" type="string" required="true">
   7:     <cfargument name="dsCity" type="string" required="true">
   8:     <cfargument name="cdState" type="string" required="true">
   9:     <cfargument name="cdDDD" type="numeric" required="true">
  10:     <cfargument name="nbTelephone" type="string" required="true">
  11:     <cfargument name="nbAddress" type="string" required="false" default="S/N">
  12:     <cfargument name="dsComplement" type="string" required="false" default="">
  13:     <cfargument name="cdCountry" type="string" required="false" default="BRA">
  14:     
  15:     <cfset Variables.userData.nmPerson = arguments.nmPerson>
  16:     <cfset Variables.userData.dsEmail = arguments.dsEmail>
  17:     <cfset Variables.userData.nbPostalCode = arguments.nbPostalCode>
  18:     <cfset Variables.userData.dsAddress = arguments.dsAddress>
  19:     <cfset Variables.userData.dsDistrict = arguments.dsDistrict>
  20:     <cfset Variables.userData.dsCity = arguments.dsCity>
  21:     <cfset Variables.userData.cdState = arguments.cdState>
  22:     <cfset Variables.userData.cdDDD = arguments.cdDDD>
  23:     <cfset Variables.userData.nbTelephone = arguments.nbTelephone>
  24:     <cfset Variables.userData.nbAddress = arguments.nbAddress>
  25:     <cfset Variables.userData.dsComplement = arguments.dsComplement>
  26:     <cfset Variables.userData.cdCountry = arguments.cdCountry>
  27: </cffunction>
Esse método,  pode ser melhorado, e seguir o padrão de componentes, para endereço, pessoa e telefone, além de validar algumas informações como e-mail, mas nesse caso, ele só irá servir para guardar as informações que serão enviadas ao PagSeguro.

setShipping
   1: <cffunction name="setShipping" access="public" returntype="void" output="false">
   2:     <cfargument name="shipping" type="string" required="true">
   3:     <cfset var lValidTypes = "UC,EN,SD">
   4:     <cfif listContainsNoCase(lValidTypes, arguments.shipping)>
   5:         <cfset Variables.tpShipping = arguments.type>
   6:     <cfelse>
   7:         <cfset Variables.tpShipping = listFirst(lValidTypes)>
   8:     </cfif>
   9: </cffunction>
Mais um da série, não tenho nada a dizer. A única observação é o tipo "UC" que estamos criando especialmente para o nosso PagSeguro.cfc. Se o nosso tipo de envio for "UC", não informaremos o tipo, fazendo com que o PagSeguro pergunte ao usuário, como ele deseja receber.

parseCurrency
   1: <cffunction name="parseCurrency" access="private" returntype="String" output="false">
   2:     <cfargument name="value" type="numeric" required="yes">
   3:     
   4:     <cfset var strResult = LSCurrencyFormat(Arguments.value, "none")>
   5:     <cfset strResult = trim(REReplace(strResult, "[^[:digit:]]", "", "ALL"))/>
   6:     <cfreturn strResult>
   7: </cffunction>
Esse método privado pega o valor e coloca em conformidade com a especificação do PagSeguro "Valor ... sem vírgulas ou pontos. Para um produto que custa R$ 1,00 você deverá informar 100 (somente números)."

send
Por último e talvez mais importante, a nossa "fronteira final" que é o envio dos dados para após a operação termos o nosso retorno automático.

Muitos podem pensar de cara em usar o cfhttp para envio dos dados, mas no caso do pagSeguro, o objetivo é ser uma ferramenta simples a ponto do usuário que só sabe html, também possa usar. Para o mundo ColdFusion o CFHTTP soa até natural, porém em linguens como .NET, PHP e Java a coisa não é tão simples.

Por conta disso, o envio é feito por um formulário simples o que gera algumas dores de cabeça com pessoas fazendo compras sem passar pelo seu site. A solução proposta pelo pagseguro é a utilização do token, que eu recomendo trocar pelo menos uma vez por mês ou ao dia dependendo do volume da acessos do seu e-commerce. Depois se vocês desejarem, podemos fazer uma ferramenta com essa finalidade.
   1: <cffunction name="send" access="public" returntype="string" output="no">
   2:     <cfargument name="idTransaction" type="string" required="false" default="">
   3:     <cfsavecontent variable="returnVal">
   4:         <cfoutput>
   5:         <form id="frmPagSeguro" action="#Variables.action#" method="post">
   6:             <input type="hidden" name="encoding" value="#Variables.ENCODING#">
   7:             <input type="hidden" name="email_cobranca" value="#Variables.dsEmail#">
   8:             <input type="hidden" name="tipo" value="#Variables.tpCart#">
   9:             <input type="hidden" name="moeda" value="#Variables.tpCoin#">
  10:             
  11:             <cfif len(trim(arguments.idTransaction))><input type="hidden" name="ref_transacao" value="#arguments.idTransaction#"></cfif>
  12:             <cfif Variables.tpShipping NEQ "UC"><input type="hidden" name="tipo_frete" value="#Variables.tpShipping#"></cfif>
  13:             <cfif Variables.vlExtras NEQ 0><input type="hidden" name="extras" value="#parseCurrency(Variables.vlExtras)#"></cfif>
  14:             
  15:             <cfloop from="1" to="#ArrayLen(variables.cart)#" index="i">
  16:                 <input type="hidden" name="item_id_#i#" value="#variables.cart[i].idItem#">
  17:                 <input type="hidden" name="item_descr_#i#" value="#variables.cart[i].nmItem#">
  18:                 <input type="hidden" name="item_quant_#i#" value="#variables.cart[i].qtItem#">
  19:                 <input type="hidden" name="item_valor_#i#" value="#parseCurrency(variables.cart[i].vlItem)#">
  20:                 <cfif variables.cart[i].flFreeShipping>
  21:                     <input type="hidden" name="item_frete_#i#" value="0">
  22:                     <input type="hidden" name="item_peso_#i#" value="0">
  23:                 <cfelse>
  24:                     <input type="hidden" name="item_peso_#i#" value="#variables.cart[i].nbWeight#">
  25:                 </cfif>
  26:             </cfloop>
  27:             <cfif NOT structIsEmpty(Variables.userData)>
  28:                 <input type="hidden" name="cliente_nome" value="#Variables.userData.nmPerson#">
  29:                 <input type="hidden" name="cliente_email" value="#Variables.userData.dsEmail#">
  30:                 <input type="hidden" name="cliente_cep" value="#Variables.userData.nbPostalCode#">
  31:                 <input type="hidden" name="cliente_end" value="#Variables.userData.dsAddress#">
  32:                 <input type="hidden" name="cliente_num" value="#Variables.userData.nbAddress#">
  33:                 <input type="hidden" name="cliente_compl" value="#Variables.userData.dsComplement#">
  34:                 <input type="hidden" name="cliente_bairro" value="#Variables.userData.dsDistrict#">
  35:                 <input type="hidden" name="cliente_cidade" value="#Variables.userData.dsCity#">
  36:                 <input type="hidden" name="cliente_uf" value="#Variables.userData.cdState#">
  37:                 <input type="hidden" name="cliente_pais" value="#Variables.userData.cdCountry#">
  38:                 <input type="hidden" name="cliente_ddd" value="#Variables.userData.cdDDD#">
  39:                 <input type="hidden" name="cliente_tel" value="#Variables.userData.nbTelephone#">
  40:             </cfif>
  41:         </form>
  42:         </cfoutput>
  43:         <script type="text/javascript">
   1:  
   2:             var objForm = document.getElementById("frmPagSeguro");
   3:             objForm.submit();
   4:         
</script>
  44:     </cfsavecontent>
  45:     <cfreturn returnVal>
  46: </cffunction>
A programação do método send é algo que se pode chamar de realmente simples. Ele vai gerar o nosso formulário, que nesse caso estou chamando de "frmPagSeguro", e executando o submit dele via javaScript. Isso esta sendo feito, para evitar o exemplo onde um simples "exibir código fonte" pode colocar toda a transação a perder.

Com isso estamos integrados ao PagSeguro, a página pagSeguro.cfm mostra, como o envio fica realmente simples.

pagSeguro.cfm
   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3: <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   4: <title>Confirmação de Compra</title>
   5: </head>
   6: <body>
   7: <cfsetting showdebugoutput="false">
   8: <cfset aCart = session.shoppingCart.list()>
   9: <table border="1">
  10:     <thead>
  11:         <tr>
  12:             <th>Cód</th>
  13:             <th>Nome</th>
  14:             <th>Valor</th>
  15:             <th>Quantidade</th>
  16:             <th>Sub-Total</th>
  17:         </tr>
  18:     </thead>
  19:     <tfoot>
  20:         <tr>
  21:             <td colspan="5"><strong>Valor Total:</strong><cfoutput>#lsCurrencyFormat(session.shoppingCart.getTotal())#</cfoutput></td>
  22:         </tr>
  23:     </tfoot>
  24:     <cfoutput>
  25:     <tbody>
  26:         <cfloop array="#aCart#" index="cartItem">
  27:             <tr>
  28:                 <td>#cartItem.cdItem#</td>
  29:                 <td>#cartItem.nmItem#</td>
  30:                 <td>#lsCurrencyFormat(cartItem.vlItem)#</td>
  31:                 <td>#cartItem.qtItem#</td>
  32:                 <td>#lsCurrencyFormat(cartItem.vlTotalItem)#</td>
  33:             </tr>
  34:         </cfloop>
  35:     </tbody>
  36:     </cfoutput>
  37: </table>
  38: <form method="post">
  39:     <input type="submit" name="go" value="Confirmar Compra"/>
  40: </form>
  41: <cfif structKeyExists(form, "go")>
  42:     <cfset objPagSeguro = createObject("component", "PagSeguro")>
  43:     <cfset objPagSeguro.setCart(session.shoppingCart)>
  44:     <cfset objPagSeguro.setClient("Rafael Bandeira Rodrigues"
  45:         , "rafael_rodrigues@flagnet.inf.br"
  46:         , "00000000"
  47:         , "R. Onde eu moro"
  48:         , "Bairro"
  49:         , "Rio de Janeiro"
  50:         , "RJ"
  51:         , "21"
  52:         , "3333-3333")>
  53: <cfoutput>#objPagSeguro.send()#</cfoutput>
  54: </cfif>
  55:  
  56: </body>
  57: </html>
Agora vem a pergunta: "E o retorno automático?". Bom esse mistério será desvendado no post de amanhã.

E finalmente, para quem cansou de ler, esta tudo pronto para download no link http://www.flagnet.inf.br/downloads/shoppingCart-part2.rar

Nenhum comentário:

Postar um comentário