Ontem na lista de ColdFusion levantaram um ponto sobre a integração com o PagSeguro que é uma das muitas ferramentas, para integração de pagamento com boleto bancário, cartões de crédito e outros meios de acordo com o integrador. Para quem desejar, pode se cadastrar por este link.
O porque utilizar estes integradores explico em outro post, porém nesse momento, resolvi iniciar uma série de posts sobre Carrinho de Compras e integrações com os mais diversos meios de pagamento disponíveis. Se você trabalha com ColdFusion e deseja integrar seu carrinho com PagSeguro, Pagamento Digital, do Buscapé, Paypal ou qualquer outro do seu interesse, basta comentar que eu monto o códido explicando como tratar cada um.
Este primeiro post, vou implementar o carrinho que irei usar durante toda a série de integrações e vou adaptando ele a medida que for necessário, apesar que não espero ter que faze-lo.
Inicialmente, vamos criar um arquivo Applicatioon.cfc para iniciar a nossa session e nosso carrinho de compras. No meu caso optei por criar um carrinho com modelo de Orientação a Objetos, ou seja , teremos DOIS componentes, um que será o nosso item e outro, que será o carrinho iniciado em session.
Application.cfc
1: <cfcomponent output="false">
2: <!--- application variables --->
3: <cfset this.name = "ShoppingCartSample">
4: <cfset this.sessionManagement = true>
5: <cfset this.sessionTimeout = createTimeSpan(0, 0, 30, 0)>
6:
7: <cffunction name="onRequestStart" access="public" returnType="boolean" output="false">
8: <cfargument type="String" name="targetPage" required="true"/>
9:
10: <cfif NOT structKeyExists(session, "shoppingCart")>
11: <cfset session.shoppingCart = createObject("component", "ShoppingCart")>
12: </cfif>
13: <cfreturn true>
14: </cffunction>
15: </cfcomponent>
Nada de muito misterioso, apenas criamos as variaveis de inicialização de session e criamos o método onRequestStart que verifica "se na estrutura Session não existir a variável shoppingCart, então crie".
Alguns podem achar estranho instanciar um componente numa session, porém estamos na verdade criando o carrinho de compras de cada usuário.
Uma vez feito isso, vamos ao carrinho de compras, propriamente dito. O que o nosso ou qualquer outro carrinho de compras deve ter?
Pensei um pouco ontem e imaginei o CRUD (Create, Retrieve, Update e Delete) básico de operações e algumas coisas extras que serão úteis para o usuário, como limpar, listar todos os produtos adicionados, obter detalhes de um produto específico, listar todos os produtos, quantidade de produtos, itens (quantidade por volume) e o valor total do carrinho.
Porque fazer assim? Simples, um carrinho de compras dificilmente vai mudar. A regra de negócios dele é simples, o que pode mudar são os itens e como eles podem ser tratados.
Melhorando a vida, vamos separar por partes o que será feito no carrinho.
ShoppingCart.cfc
O primeiro passo é criar as variáveis que serão manipuladas pelo carrrinho. Todas elas estarão no topo do nosso CFC (classe) e com scoopo Variables (privat).
1: <cfset variables.cart = arrayNew(1)>
2: <cfset variables.vlTotal = 0>
3: <cfset variables.cartItem = createObject("component", "ShoppingCartItem")>
4: <cfset variables.nbMaxCartItem = 10>
- cart: Será o nosso array de itens, essa variável será manipulada pelo CRUD do nosso CFC
- vlTotal: Irá receber o valor do carrinho, ou seja, o valor da soma de todos os itens do carrinho
- cartItem: Esse nosso componente, irá acessar a base de dados e obter os detalhes do item.
- nbMaxCartItem: Essa variavel é para evitar que alguém reserve todo o estoque.
addItem
1: <cffunction name="addItem" access="public" output="no" returntype="void">
2: <cfargument name="idItem" type="numeric" required="true">
3: <cfargument name="qtItem" type="numeric" required="false" default="1">
4:
5: <cfset var Local = StructNew()>
6: <cfset Local.cartItem = Duplicate(variables.cartItem)>
7: <cfif hasItem(Arguments.idItem)>
8: <cfset updateItem(Arguments.idItem, Arguments.qtItem)>
9: <cfelse>
10: <cfset Local.cartItem.setShoppingCartItem(Arguments.idItem)>
11: <cfif (Arguments.qtItem GT variables.nbMaxCartItem) AND (variables.nbMaxCartItem NEQ 0)>
12: <cfset Local.cartItem.qtItem = variables.nbMaxCartItem>
13: <cfelse>
14: <cfset Local.cartItem.qtItem = Arguments.qtItem>
15: </cfif>
16: <cfset Local.cartItem.vlTotalItem = Local.cartItem.vlItem * Local.cartItem.qtItem>
17: <cfset variables.vlTotal = variables.vlTotal + Local.cartItem.vlTotalItem>
18: <cfset ArrayAppend(variables.cart, Local.cartItem)>
19: </cfif>
20: </cffunction>
Nossa primeira ação, é adicionar o item escolhido pelo usuário no carrinho. Repare que o método só pede duas informações, o idItem e o qtItem. O idItem é o identificador na sua base de dados e a quantidade dele que você deseja.
Em nosso primeiro passo com relação ao carrinho, verificamos se esse item já não foi adicionado, se isso ocorreu, chamamos a operação de updateItem, caso contrário, seguimos o fluxo normal.
No nosso fluxo normal, utilizamos a função Duplicate do ColdFusion para criar uma cópia idêntica, do nosso componente ShoppingCartItem, isso evita o consumo de recursos da função createObject.
Após criar minha cópia, chamo o médoto setSoppingCartItem do meu ShoppingCartItem, que é responsável pelas verificações pertinentes ao item.
Verifico se a quantidade de itens esta dentro do aceitável, caso contrário defino a quantidade de itens com o máximo que aceitamos. Se alguém achar isso loucura, da uma olhada no carrinho de compras de e-commerces "pequenos" como submarino, saraiva, e outros.
Terminada toda operação, atualizamos o valor dos itens, o valor do carrinho e adicionamos ele ao nosso array de itens.
updateItem
1: <cffunction name="updateItem" access="public" output="no" returntype="void">
2: <cfargument name="idItem" type="numeric" required="true">
3: <cfargument name="qtItem" type="numeric" required="true">
4: <cfset var Local = StructNew()>
5:
6: <cfif Arguments.qtItem EQ 0>
7: <cfset remove(arguments.productId)>
8: <cfelse>
9: <cfloop array="#variables.cart#" index="Local.currElement">
10: <cfif Local.currElement.idItem eq Arguments.idItem>
11: <cfset Local.cartItem = Local.currElement>
12: <cfbreak>
13: </cfif>
14: </cfloop>
15: <cfif Not StructKeyExists(Local, "cartItem")>
16: <cfset addItem(Arguments.idItem, Arguments.qtItem)>
17: <cfelse>
18: <cfset variables.vlTotal = variables.vlTotal - Local.cartItem.vlTotalItem>
19: <cfif (Arguments.qtItem GT variables.nbMaxCartItem) AND (variables.nbMaxCartItem NEQ 0)>
20: <cfset Local.cartItem.qtItem = variables.nbMaxCartItem>
21: <cfelse>
22: <cfset Local.cartItem.qtItem = Arguments.qtItem>
23: </cfif>
24: <cfset Local.cartItem.vlTotalItem = Local.cartItem.vlItem * Local.cartItem.qtItem>
25: <cfset variables.vlTotal = variables.vlTotal + Local.cartItem.vlTotalItem>
26: </cfif>
27: </cfif>
28: </cffunction>
Após a verificação de quantidade, partimos para localizar o item a ser atualizado e mudar sua quantidade, consequentemetne, afetando os valores.
Repare que em nenhum momento, verificamos a quantidade em estoque, até porque você não sabe se o usuário vai realmente finalizar a compra, com isso todo o processamento gira em torno do desejo do usuário, e não sobre todo o seu estoque.
remove
1: <cffunction name="remove" access="public" returntype="void">
2: <cfargument name="idItem" type="numeric" required="true">
3: <cfset var Local = StructNew()>
4: <cfloop from="1" to="#ArrayLen(variables.cart)#" index="Local.i">
5: <cfif variables.cart[Local.i].idItem eq Arguments.idItem>
6: <cfbreak>
7: </cfif>
8: </cfloop>
9: <cfset variables.vlTotal = variables.vlTotal - (variables.cart[Local.i].vlItem * variables.cart[Local.i].qtItem)>
10: <cfset arrayDeleteAt(variables.cart, Local.i)>
11: </cffunction>
Bom acredito eu que este seja um dos métodos mais simples do nosso ShoppingCart.cfc, ele simplesmete "localiza e destroi", a diferença que depois ele arruma os valores.
list
1: <cffunction name="list" access="public" returntype="array">
2: <cfreturn variables.cart>
3: </cffunction>
Ok, esse sim é o mais simples, só posso dizer.... "Ahhh Mulequeeee" rsrs. Para aqueles que não entenderam a piada, veja o video
clear
1: <cffunction name="clear" access="public" returntype="void">
2: <cfset ArrayClear(variables.cart)>
3: <cfset variables.vlTotal = 0>
4: </cffunction>
getTotalProducts
1: <cffunction name="getTotalProducts" access="public" returntype="numeric">
2: <cfreturn arrayLen(variables.cart)>
3: </cffunction>
getTotalItems
1: <cffunction name="getTotalItems" access="public" returntype="numeric">
2: <cfset var Local = StructNew()>
3: <cfset Local.qtItem = 0>
4: <cfloop array="#variables.cart#" index="Local.current">
5: <cfset Local.qtItem = Local.qtItem + Local.current.qtItem>
6: </cfloop>
7: <cfreturn Local.qtItem>
8: </cffunction>
getTotal
1: <cffunction name="getTotal" access="public" returntype="numeric">
2: <cfreturn variables.vlTotal>
3: </cffunction>
hasItem
1: <cffunction name="hasItem" access="private" output="no" returntype="boolean">
2: <cfargument name="idItem" type="numeric" required="true">
3: <cfset var Local = StructNew()>
4: <cfset Local.hasItemReturn = false>
5: <cfloop array="#variables.cart#" index="Local.currElement">
6: <cfif Local.currElement.idItem eq Arguments.idItem>
7: <cfset Local.hasItemReturn = true>
8: <cfbreak>
9: </cfif>
10: </cfloop>
11: <cfreturn Local.hasItemReturn>
12: </cffunction>
Resultado ShoppingCart.cfc
Bom, para evitar os copy and paste, ai esta como o nosso carrinho irá ficar no final.
1: <cfcomponent output="false">
2: <cfset variables.cart = arrayNew(1)>
3: <cfset variables.vlTotal = 0>
4: <cfset variables.cartItem = createObject("component", "ShoppingCartItem")>
5: <cfset variables.nbMaxCartItem = 10>
6:
7: <cffunction name="addItem" access="public" output="no" returntype="void">
8: <cfargument name="idItem" type="numeric" required="true">
9: <cfargument name="qtItem" type="numeric" required="false" default="1">
10:
11: <cfset var Local = StructNew()>
12: <cfif hasItem(Arguments.idItem)>
13: <cfset updateItem(Arguments.idItem, Arguments.qtItem)>
14: <cfelse>
15: <cfset Local.cartItem = Duplicate(variables.cartItem)>
16: <cfset Local.cartItem.setShoppingCartItem(Arguments.idItem)>
17: <cfif (Arguments.qtItem GT variables.nbMaxCartItem) AND (variables.nbMaxCartItem NEQ 0)>
18: <cfset Local.cartItem.qtItem = variables.nbMaxCartItem>
19: <cfelse>
20: <cfset Local.cartItem.qtItem = Arguments.qtItem>
21: </cfif>
22: <cfset Local.cartItem.vlTotalItem = Local.cartItem.vlItem * Local.cartItem.qtItem>
23: <cfset variables.vlTotal = variables.vlTotal + Local.cartItem.vlTotalItem>
24: <cfset ArrayAppend(variables.cart, Local.cartItem)>
25: </cfif>
26: </cffunction>
27:
28: <cffunction name="updateItem" access="public" output="no" returntype="void">
29: <cfargument name="idItem" type="numeric" required="true">
30: <cfargument name="qtItem" type="numeric" required="true">
31: <cfset var Local = StructNew()>
32:
33: <cfif Arguments.qtItem EQ 0>
34: <cfset remove(arguments.productId)>
35: <cfelse>
36: <cfloop array="#variables.cart#" index="Local.currElement">
37: <cfif Local.currElement.idItem eq Arguments.idItem>
38: <cfset Local.cartItem = Local.currElement>
39: <cfbreak>
40: </cfif>
41: </cfloop>
42: <cfif Not StructKeyExists(Local, "cartItem")>
43: <cfset addItem(Arguments.idItem, Arguments.qtItem)>
44: <cfelse>
45: <cfset variables.vlTotal = variables.vlTotal - Local.cartItem.vlTotalItem>
46: <cfif (Arguments.qtItem GT variables.nbMaxCartItem) AND (variables.nbMaxCartItem NEQ 0)>
47: <cfset Local.cartItem.qtItem = variables.nbMaxCartItem>
48: <cfelse>
49: <cfset Local.cartItem.qtItem = Arguments.qtItem>
50: </cfif>
51: <cfset Local.cartItem.vlTotalItem = Local.cartItem.vlItem * Local.cartItem.qtItem>
52: <cfset variables.vlTotal = variables.vlTotal + Local.cartItem.vlTotalItem>
53: </cfif>
54: </cfif>
55: </cffunction>
56:
57: <cffunction name="remove" access="public" returntype="void">
58: <cfargument name="idItem" type="numeric" required="true">
59: <cfset var Local = StructNew()>
60: <cfloop from="1" to="#ArrayLen(variables.cart)#" index="Local.i">
61: <cfif variables.cart[Local.i].idItem eq Arguments.idItem>
62: <cfbreak>
63: </cfif>
64: </cfloop>
65: <cfset variables.vlTotal = variables.vlTotal - (variables.cart[Local.i].vlItem * variables.cart[Local.i].qtItem)>
66: <cfset arrayDeleteAt(variables.cart, Local.i)>
67: </cffunction>
68:
69: <cffunction name="clear" access="public" returntype="void">
70: <cfset ArrayClear(variables.cart)>
71: <cfset variables.vlTotal = 0>
72: </cffunction>
73:
74: <cffunction name="getProductDetail" access="public" returntype="ShoppingCartItem">
75: <cfargument name="idItem" type="numeric" required="true">
76: <cfset var Local = StructNew()>
77: <cfloop array="#variables.cart#" index="Local.currElement">
78: <cfif Local.currElement.idItem eq Arguments.idItem>
79: <cfset Local.cartItem = Local.currElement>
80: <cfbreak>
81: </cfif>
82: </cfloop>
83: <cfreturn Local.cartItem>
84: </cffunction>
85:
86: <cffunction name="list" access="public" returntype="array">
87: <cfreturn variables.cart>
88: </cffunction>
89:
90: <cffunction name="getTotalProducts" access="public" returntype="numeric">
91: <cfreturn arrayLen(variables.cart)>
92: </cffunction>
93:
94: <cffunction name="getTotalItems" access="public" returntype="numeric">
95: <cfset var Local = StructNew()>
96: <cfset Local.qtItem = 0>
97: <cfloop array="#variables.cart#" index="Local.current">
98: <cfset Local.qtItem = Local.qtItem + Local.current.qtItem>
99: </cfloop>
100: <cfreturn Local.qtItem>
101: </cffunction>
102:
103: <cffunction name="getTotal" access="public" returntype="numeric">
104: <cfreturn variables.vlTotal>
105: </cffunction>
106:
107: <cffunction name="hasItem" access="private" output="no" returntype="boolean">
108: <cfargument name="idItem" type="numeric" required="true">
109: <cfset var Local = StructNew()>
110: <cfset Local.hasItemReturn = false>
111: <cfloop array="#variables.cart#" index="Local.currElement">
112: <cfif Local.currElement.idItem eq Arguments.idItem>
113: <cfset Local.hasItemReturn = true>
114: <cfbreak>
115: </cfif>
116: </cfloop>
117: <cfreturn Local.hasItemReturn>
118: </cffunction>
119: </cfcomponent>
ShoppingCartItem.cfc
Ok, agora vem a pergunta: "E como fica o Item do meu carrinho?" Nesse caso, a resposta é "cada um faz o seu", mas a título de exemplo vou montar um aqui como modelo e idéia.
Lembrando que modelo não é regra!! A única regra, são os métodos getShoppingCartItem e setShoppingCartItem respectivamente, para manter um padrão de integração com o nosso carrinho.
Uma dica é criar um outro CFC que terá seu acesso ao DB para verificar se o item existe, e recuperar os detalhes dele. Para nosso teste, estou gerando valores ficticios.
1: <cfcomponent output="false">
2: <cfproperty name="idItem" type="numeric">
3: <cfproperty name="cdItem" type="string">
4: <cfproperty name="nmItem" type="string">
5: <cfproperty name="vlItem" type="numeric">
6: <cfproperty name="nbWeight" type="numeric">
7: <cfproperty name="flFreeShipping" type="boolean">
8: <cfproperty name="dtCreate" type="date">
9: <cfproperty name="dtLastUpdate" type="date">
10:
11: <cfparam name="This.idItem" default="">
12: <cfparam name="This.cdItem" default="">
13: <cfparam name="This.nmItem" default="">
14: <cfparam name="This.vlItem" default="">
15: <cfparam name="This.nbWeight" default="">
16: <cfparam name="This.flFreeShipping" default="">
17: <cfparam name="This.dtCreate" default="#DateConvert('local2utc', Now())#">
18: <cfparam name="This.dtLastUpdate" default="#DateConvert('local2utc', Now())#">
19:
20: <cffunction name="getShoppingCartItem" access="public" output="no" returntype="ShoppingCartItem">
21: <cfreturn This>
22: </cffunction>
23:
24: <cffunction name="setShoppingCartItem" access="public" output="no" returntype="void">
25: <cfargument name="idItem" type="numeric" required="yes">
26:
27: <cfset This.idItem = numberFormat(arguments.idItem, "00")>
28: <cfset This.cdItem = "CI" & numberFormat(arguments.idItem, "00000000")>
29: <cfset This.nmItem = "Nome do Item " & arguments.idItem>
30: <cfset This.vlItem = randRange(5.40, 123.80)>
31: <cfset This.nbWeight = randRange(0.25, 2)>
32: <cfset This.flFreeShipping = iif(This.vlItem GT 59.20, true, false)>
33: <cfset This.dtCreate = now()>
34: <cfset This.dtLastUpdate = now()>
35: </cffunction>
36: </cfcomponent>
Juntando tudo
Para evitar dores de cabeça criei um index.cfm de teste, só para vermos as ações funcionando.
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>Untitled Document</title>
5: </head>
6: <body>
7: <cfif structKeyExists(form, "add")>
8: <cfset session.shoppingCart.addItem(form.idItem, form.qtItem)>
9: </cfif>
10: <cfif structKeyExists(form, "upd")>
11: <cfset session.shoppingCart.updateItem(form.idItemUpd, form.qtItemUpd)>
12: </cfif>
13: <cfif structKeyExists(form, "rem")>
14: <cfset session.shoppingCart.remove(form.idItemRem)>
15: </cfif>
16: <cfif structKeyExists(form, "clear")>
17: <cfset session.shoppingCart.clear()>
18: </cfif>
19: <cfif structKeyExists(form, "reset")>
20: <cfset structClear(session)>
21: <cfset session.shoppingCart = createObject("component", "ShoppingCart")>
22: </cfif>
23:
24: <form method="post">
25: <div style="float: left;">
26: <input type="submit" name="reset" value="Limpar Session" />
27: <input type="submit" name="clear" value="Limpar Carrinho" />
28: <fieldset>
29: <legend>Adicionar Itens</legend>
30: <div style="float: left;">
31: <label for="idItem">Código:</label>
32: <input type="text" id="idItem" name="idItem" size="4"/>
33: <label for="qtItem">Quantidade:</label>
34: <input type="text" id="qtItem" name="qtItem" size="4"/>
35: <input type="submit" name="add" value="Adicionar" />
36: </div>
37: </fieldset>
38:
39: <fieldset>
40: <legend>Atualizar Itens</legend>
41: <div style="float: left;">
42: <label for="idItemUpd">Código:</label>
43: <input type="text" id="idItemUpd" name="idItemUpd" size="4"/>
44: <label for="qtItemUpd">Nova Quantidade:</label>
45: <input type="text" id="qtItemUpd" name="qtItemUpd" size="4"/>
46: <input type="submit" name="upd" value="Atualizar" />
47: </div>
48: </fieldset>
49:
50: <fieldset>
51: <legend>Remover Itens</legend>
52: <div style="float: left;">
53: <label for="idItemRem">Código:</label>
54: <input type="text" id="idItemRem" name="idItemRem" size="4"/>
55: <input type="submit" name="rem" value="Remover" />
56: </div>
57: </fieldset>
58:
59: <fieldset>
60: <legend>Detalhes do Item</legend>
61: <div style="float: left;">
62: <label for="idItemDet">Código:</label>
63: <input type="text" id="idItemDet" name="idItemDet" size="4"/>
64: <input type="submit" name="det" value="Detalhes" />
65: </div>
66: <cfif structKeyExists(form,"idItemDet") and isNumeric(form.idItemDet)>
67: <cfset details = session.shoppingCart.getProductDetail(form.idItemDet)>
68: <cfdump var="#details#" label="Detalhe do Item">
69: </cfif>
70: </fieldset>
71:
72: <fieldset>
73: <legend>Detalhes do Carrinho</legend>
74: <div>
75: <strong>Total de Produtos no Carrinho:</strong>
76: <cfoutput>#session.shoppingCart.getTotalProducts()#</cfoutput>
77: </div>
78: <div>
79: <strong>Total de Itens no Carrinho:</strong>
80: <cfoutput>#session.shoppingCart.getTotalItems()#</cfoutput>
81: </div>
82: <div>
83: <strong>Valor Total do Carrinho:</strong>
84: <cfoutput>#lsCurrencyFormat(session.shoppingCart.getTotal())#</cfoutput>
85: </div>
86: <h3>Dump do Carrinho</h3>
87: <cfdump var="#session.shoppingCart.list()#">
88: </fieldset>
89: </form>
90: </body>
91: </html>
E finalmente, para quem cansou de ler, esta tudo pronto para download no link http://www.flagnet.inf.br/downloads/shoppingCart-part1.rar
Show de bola Rafael, a tempos atraz vc me deu umas dicas de shoppingCard e eu monteu meu carrinho, olhando o seu vejo que fui um bom aluno.
ResponderExcluirTo ancioso pra ver agora o retorno automatico do pagseguro.
Abraço Mestre.
É sempre bom ler de quem realmente sabe!
ResponderExcluirParabéns!
Animal... tive varias ideias, obrigado mesmo...
ResponderExcluirAssim como o Anderson, fico aguardando o retorno automárico anciosamente xD
Abraço
Parabens Rafal!
ResponderExcluirEu sempre apoio esse tipo de iniciativa. O código está muito bem feito e simples leitura e com compatibilidade entre as versões 8 e 9, embora alguns recursos facilitadores da versão 9 trouxesse maior simplicidade ao código.
Abraço,
Jeff
Esses códigos não dá para colocar no Blogspot.com que pena !!
ResponderExcluirEsses códigos são para quem utiliza a lingagem ColdFusion. no caso do BlogSpot ele não utiliza nenhuma linguagem de programação, a não ser o HTML. Se a tua intenção é ter o pagseguro em um blog, só utilizando a API de HTML que você encontra no site do pag seguro
Excluir