Dev (Back & Front)ARTIGO

Utilizando Android Cloud to Device Messaging com PayPal Instant Payment Notification

A ideia desse tutorial é utilizar o manipulador descrito no outro tutorial em conjunto com o serviço Android Cloud to Device Messaging (C2DM) para notificar dispositivos móveis sobre qualquer mudança de status de pagamento, ou seja, assim que uma mensagem IPN chegar, receberemos a informação em nossos dispositivos móveis!

Olá pessoal,

No artigo Manipulador de Notificação de Pagamento Instantâneo ilustramos como construir um manipulador para receber as notificações instantâneas de pagamento do PayPal, validar essas notificações e como construir as regras de negócio para utilizar essa informação.

Já sabemos que as notificações de pagamento são assíncronas, ou seja, elas podem ocorrer a qualquer momento. Isso significa que, se precisarmos da confirmação da mudança de status de um determinado pagamento para tomar alguma decisão relacionada ao negócio, precisaremos estar monitorando a aplicação para ver se uma notificação chegou, ou então fazer uma consulta manualmente utilizando a operação GetTransactionDetails para saber se houve tal mudança de status de pagamento.

A ideia desse tutorial é utilizar o manipulador descrito no outro tutorial em conjunto com o serviço Android Cloud to Device Messaging (C2DM) para notificar dispositivos móveis sobre qualquer mudança de status de pagamento, ou seja, assim que uma mensagem IPN chegar, receberemos a informação em nossos dispositivos móveis! Legal né?

Android Cloud to Device Messaging está disponível para todos os desenvolvedores, mas para utilizá-lo é preciso ir até Sign Up for Android Cloud to Device Messaging e solicitar acesso.

Bom, o processo para utilizar IPN com C2DM é bem simples:

  1. O usuário acessa a aplicação em seu dispositivo.
  2. A aplicação faz a chamada ao método de registro do C2DM.
  3. Após o registro do usuário, o Google fornece um ID único para esse usuário, que a aplicação deverá enviar para a aplicação no lado do servidor.
  4. A aplicação do lado do servidor recebe o identificador e armazena. Esse ID será utilizado nas comunicações entre servidor e dispositivo.
  5. Quando o PayPal enviar uma mensagem IPN, utilizamos o ID de registro do usuário e fazemos um POST para o serviço C2DM com o ID da transação.
  6. O usuário recebe a notificação em seu dispositivo Android.
  7. A aplicação Android exibe as informações do pagamento.

Como podemos ver, precisaremos de uma aplicação no servidor para receber o identificador único do usuário, as notificações IPN do PayPal e despachá-las para o serviço C2DM.

Google ClientLogin

A aplicação que fará o POST ao serviço C2DM precisa estar autorizada e, para isso, utilizaremosClientLogin. O processo é bem simples e o código abaixo dará conta do recado:

Em PHP
com/paypal/ipn/google/auth/ClientLogin.php

<?php

namespace com\paypal\ipn\google\auth; /**  * Faz a requisição POST ao ClientLogin do Google para obter a autorização  * de acesso para contas Google.  * @author João Batista Neto  */ class ClientLogin {     /**      * URL do serviço de autorização ClientLogin do Google.      * @var string      */     const URL = 'https://www.google.com/accounts/ClientLogin';     /**      * Token de autorização.      * @var string      */     private $auth;     /**      * Obtém o token de autorização do Google utilizando ClientLogin      *      * @param   string $accountType Tipo da conta que está solicitando a      *          autorização, os valores possíveis são:      *          <ul>      *          <li>GOOGLE</li>      *          <li>HOSTED</li>      *          <li>HOSTED_OR_GOOGLE</li>      *          </ul>      * @param   string $Email Email completo do usuário, incluindo o domínio.      * @param   string $Passwd Senha do usuário.      * @param   string $source Uma string identificando a aplicação.      * @param   string $service Nome do serviço que será solicitada a      *          autorização.      * @return  string O Token de autorização.      */     public function getAuth(                             $accountType,                             $Email,                             $Passwd,                             $source,                             $service ) {         if ( $this->auth === null ) {             $curl = curl_init();             curl_setopt( $curl , CURLOPT_URL , ClientLogin::URL );             curl_setopt( $curl , CURLOPT_RETURNTRANSFER , 1 );             curl_setopt( $curl , CURLOPT_POST , 1 );             curl_setopt( $curl , CURLOPT_POSTFIELDS , http_build_query(                 array(                     'accountType' => $accountType,                     'Email' => $Email,                     'Passwd' => $Passwd,                     'source' => $source,                     'service' => $service                 )             ) );             $responseStr = curl_exec( $curl );             $responseArr = array();             $matches = array();             curl_close( $curl );             if ( preg_match_all(                                 "/\n?(?<field>\\w+)\\=(?<value>[^\n]+)/",                                 $responseStr,                                 $matches ) ) {                 foreach ( $matches[ 'field' ] as $offset => $field ) {                     $responseArr[ $field ] = $matches[ 'value' ][ $offset ];                 }                 if ( isset( $responseArr[ 'Auth' ] ) ) {                     $this->auth = $responseArr[ 'Auth' ];                 }             }         }         return $this->auth;     } } Em Java
com/paypal/ipn/google/auth/ClientLogin.java package com.paypal.ipn.google.auth;

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLConnection; import java.util.logging.Level; import java.util.logging.Logger; /**  * Faz a requisição POST ao ClientLogin do Google para obter a autorização de  * acesso para contas Google.  *  * @author João Batista Neto  */ public class ClientLogin {     /**      * URL do serviço de autorização ClientLogin do Google.      */     public static final String URL = "https://www.google.com/accounts/ClientLogin";     /**      * Token de autorização.      */     private String auth;     /**      * Obtém o token de autorização do Google utilizando ClientLogin      *      * @param accountType      *            {@link String} Tipo da conta que está solicitando a      *            autorização, os valores possíveis são:      *            <ul>      *            <li>GOOGLE</li>      *            <li>HOSTED</li>      *            <li>HOSTED_OR_GOOGLE</li>      *            </ul>      * @param Email      *            {@link String} Email completo do usuário, incluindo o domínio.      * @param Passwd      *            {@link String} Senha do usuário.      * @param source      *            {@link String} Uma string identificando a aplicação.      * @param service      *            {@link String} Nome do serviço que será solicitada a      *            autorização.      * @return {@link String} O Token de autorização.      */     public String getAuth(String accountType, String Email, String Passwd,             String source, String service) {         if (auth == null) {             try {                 URL url = new URL(URL);                 URLConnection conn = url.openConnection();                 StringBuilder sb = new StringBuilder();                 sb.append("accountType=" + accountType);                 sb.append("&Email=" + Email);                 sb.append("&Passwd=" + Passwd);                 sb.append("&source=" + source);                 sb.append("&service=" + service);                 conn.setDoOutput(true);                 OutputStreamWriter writer = new OutputStreamWriter(                         conn.getOutputStream());                 writer.write(sb.toString());                 writer.flush();                 writer.close();                 InputStreamReader in = new InputStreamReader(                         conn.getInputStream());                 BufferedReader reader = new BufferedReader(in);                 sb = new StringBuilder();                 String data = null;                 while ((data = reader.readLine()) != null) {                     String nv[] = data.split("=");                     if (nv.length == 2 && nv[0].equals("Auth")) {                         auth = nv[0];                         break;                     }                 }                 reader.close();             } catch (IOException e) {                 Logger.getLogger(ClientLogin.class.getName()).log(Level.SEVERE,                         null, e);             }         }         return auth;     } } Em C#
com/paypal/ipn/google/auth/ClientLogin.cs namespace com.paypal.ipn.google.auth {

    using System;     using System.IO;     using System.Text;     using System.Net;     /// <summary>     /// Faz a requisição POST ao ClientLogin do Google para obter a autorização de     /// acesso para contas Google.     /// </summary>     public class ClientLogin {         /// <summary>         /// URL do serviço de autorização ClientLogin do Google .         /// </summary>         const string URL = "https://www.google.com/accounts/ClientLogin";         /// <summary>         /// Token de autorização.         /// </summary>         private string Auth;         /// <summary>         /// Obtém o token de autorização do Google utilizando ClientLogin.         /// </summary>         /// <param name="accountType">         /// <see cref="System.String"/> Tipo da conta que está solicitando a         /// autorização, os valores possíveis são:         /// <ul>         /// <li>GOOGLE</li>         /// <li>HOSTED</li>         /// <li>HOSTED_OR_GOOGLE</li>         /// </ul>         /// </param>         /// <param name="Email">         /// <see cref="System.String"/> Email completo do usuário, incluindo o domínio.         /// </param>         /// <param name="Passwd">         /// <see cref="System.String"/> Senha do usuário.         /// </param>         /// <param name="source">         /// Uma <see cref="System.String"/> identificando a aplicação.         /// </param>         /// <param name="service">         /// <see cref="System.String"/> Nome do serviço que será solicitada a autorização.         /// </param>         /// <returns>         /// <see cref="System.String"/> O Token de autorização.         /// </returns>         public string getAuth(string accountType,                               string Email,                               string Passwd,                               string source,                               string service) {             if ( Auth == null ) {                 HttpWebRequest request = (HttpWebRequest) WebRequest.Create( URL );                 StringBuilder sb = new StringBuilder();                 sb.Append( "accountType=" + accountType );                 sb.Append( "&Email=" + Email );                 sb.Append( "&Passwd=" + Passwd );                 sb.Append( "&source=" + source );                 sb.Append( "&service=" + service );                 request.Method = "POST";                 request.ContentType = "application/x-www-form-urlencoded";                              using ( Stream stream = request.GetRequestStream() ) {                     UTF8Encoding encoding = new UTF8Encoding();                     byte[] bytes = encoding.GetBytes( sb.ToString() );                     stream.Write( bytes , 0 , bytes.Length );                 }                 HttpWebResponse response = (HttpWebResponse) request.GetResponse();                 using ( Stream stream = response.GetResponseStream() ) {                     string data;                     using ( StreamReader reader = new StreamReader( stream , Encoding.UTF8 ) ) {                         while ( (data = reader.ReadLine()) != null ) {                             string[] nv = data.Split( '=' );                             if ( nv.Length == 2 && nv[0].Equals( "Auth" ) ) {                                 Auth = nv[1];                                 break;                             }                         }                         reader.Close();                     }                 }             }             return Auth;         }     } }

A requisição ao ClientLogin para obter o token não precisa ser feita todas as vezes. A recomendação é que o token seja armazenado pela aplicação server side, mas é importante ter uma política de atualização constante.

Como disse anteriormente, o cliente abrirá a aplicação no dispositivo Android, fará o registro no C2DM e receberá um ID único do Google. Esse ID deverá ser enviado para a aplicação server side, que o utilizará para enviar mensagens para o dispositivo.

Android Cloud to Device Messaging

Para a aplicação no dispositivo Android receber uma mensagem do servidor, primeiro precisamos que aplicação envie essa mensagem para o serviço C2DM. Para isso precisamos:

  1. A aplicação envia a mensagem para o serviço C2DM contendo o ID de registro, mensagem e Token de autorização.
  2. Google recebe a mensagem e coloca em uma fila. Se o dispositivo estiver offline o Google armazena a mensagem.
  3. Quando o dispositivo estiver online o Google enviará a mensagem para o dispositivo.
  4. O sistema transmite a mensagem via, Intent Broadcast, para a aplicação. A aplicação não precisa estar em execução para receber a mensagem.
  5. A aplicação recebe a mensagem e a processa adequadamente.

Vamos precisar de dois participantes, o primeiro é o responsável por enviar as mensagens para o serviço C2DM:

Em PHP
com/paypal/ipn/google/ac2dm/AndroidCloud2DeviceMessaging.php

<?php

namespace com\paypal\ipn\google\ac2dm; /**  * A classe AndroidCloud2DeviceMessaging faz integração com o serviço  * Android Cloud to Device Messaging (C2DM) para enviar dados para dispositivos  * Android.  *  * @author  João Batista Neto  */ class AndroidCloud2DeviceMessaging {     /**      * URL do serviço C2DM.      * @var string      */     const URL = 'https://android.apis.google.com/c2dm/send';     /**      * Conjunto de pares key=value que serão enviados para o dispositivo.      * @var array      */     private $data = array();     /**      * Adiciona um par key=value que será enviado para o dispositivo android.      *      * @param   string $key A chave que será enviada para o dispositivo.      * @param   string $value O valor da chave.      * @param   string $collapseKey Chave de agrupamento que será utilizado pelo      *          Google para evitar que várias mensagens do mesmo tipo sejam      *          enviadas para o usuário de uma vez quando o dispositivo fique      *          online.      */     public function addData( $key , $value , $collapseKey ) {         if ( !isset( $this->data[ $collapseKey ] ) ) {             $this->data[ $collapseKey ] = array();         }         $this->data[ $collapseKey ][ 'data.' . $key ] = $value;     }     /**      * Remove todas os pares key=value.      */     public function clear() {         $this->data = array();     }     /**      * Envia a mensagem para o servidor C2DM.      *      * @param   string $registrationId ID de registro do dispositivo android.      * @param   string $auth Token de autorização      * @see     com\google\auth\ClientLogin::getAuth()      */     public function send( $registrationId , $auth ) {         $curl = curl_init();         curl_setopt( $curl , CURLOPT_URL , AndroidCloud2DeviceMessaging::URL );         curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER , false );         curl_setopt( $curl , CURLOPT_RETURNTRANSFER , 1 );         curl_setopt( $curl , CURLOPT_POST , 1 );         curl_setopt( $curl , CURLOPT_HTTPHEADER , array(             'Authorization: GoogleLogin auth=' . $auth         ) );         foreach ( $this->data as $collapseKey => $data ) {             $data[ 'registration_id' ] = $registrationId;             $data[ 'collapse_key' ] = $collapseKey;             curl_setopt( $curl , CURLOPT_POSTFIELDS , http_build_query($data));             var_dump( curl_exec( $curl ) );         }         curl_close( $curl );     } } Em Java
com/paypal/ipn/google/ac2dm/AndroidCloud2DeviceMessaging.java package com.paypal.ipn.google.ac2dm;

import java.io.IOException; import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; /**  * A classe AndroidCloud2DeviceMessaging faz integração com o serviço Android  * Cloud to Device Messaging (C2DM) para enviar dados para dispositivos Android.  *  * @author João Batista Neto  */ public class AndroidCloud2DeviceMessaging {     /**      * O certificado do Google não cobre android.apis.google.com, então será      * preciso verificar e fazer a validação do certificado manualmente.      */     private static final String HOSTNAME = "android.apis.google.com";     /**      * URL do serviço C2DM.      */     public static final String URL = "https://android.apis.google.com/c2dm/send";     /**      * Conjunto de pares key=value que serão enviados para o dispositivo.      */     private Map<String, Map<String, String>> data;     public AndroidCloud2DeviceMessaging() {         data = new HashMap<String, Map<String, String>>();     }     /**      * Adiciona um par key=value que será enviado para o dispositivo android.      *      * @param key {@link String}      *            A chave que será enviada para o dispositivo.      * @param value {@link String}      *            O valor da chave.      * @param collapseKey {@link String}      *            Chave de agrupamento que será utilizado pelo      *            Google para evitar que várias mensagens do mesmo tipo sejam      *            enviadas para o usuário de uma vez quando o dispositivo fique      *            online.      */     public void addData(String key, String value, String collapseKey) {         Map<String, String> map;         if ((map = data.get(collapseKey)) == null) {             map = new HashMap<String, String>();             data.put(collapseKey, map);         }         map.put(key, value);     }     /**      * Remove todas os pares key=value.      */     public void clear() {         data.clear();     }     /**      * Envia a mensagem para o servidor C2DM.      *      * @param registrationId {@link String} ID de registro do dispositivo      *        android.      * @param auth {@link String} Token de autorização.      * @see {@link com.paypal.ipn.google.auth.ClientLogin#getAuth}      */     public void send(String registrationId, String auth) {         try {             URL url = new URL(URL);             HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();             conn.setDoOutput(true);             conn.setHostnameVerifier(new HostnameVerifier() {                 @Override                 public boolean verify(String hostname, SSLSession session) {                     return hostname.equals(HOSTNAME);                 }             });             conn.setRequestMethod("POST");             conn.setRequestProperty("Content-Type",                     "application/x-www-form-urlencoded");             conn.setRequestProperty("Authorization", "GoogleLogin auth=" + auth);             for (String collapseKey : data.keySet()) {                 StringBuilder sb = new StringBuilder();                 Map<String, String> nv = data.get(collapseKey);                 sb.append("registration_id=" + registrationId);                 sb.append("&collapse_key=" + collapseKey);                 for (Entry<String, String> entry : nv.entrySet()) {                     sb.append("&data." + entry.getKey());                     sb.append("=");                     sb.append(URLEncoder.encode(entry.getValue(), "UTF-8"));                 }                 OutputStreamWriter writer = new OutputStreamWriter(                         conn.getOutputStream());                 writer.write(sb.toString());                 writer.flush();                 writer.close();                 if (conn.getResponseCode() != 200) {                     Logger.getLogger(                             AndroidCloud2DeviceMessaging.class.getName()).log(                             Level.SEVERE, conn.getResponseMessage());                 }             }         } catch (IOException e) {             Logger.getLogger(AndroidCloud2DeviceMessaging.class.getName()).log(                     Level.SEVERE, null, e);         }     } } Em C#
com/google/c2dm/AndroidCloud2DeviceMessaging.cs namespace com.paypal.ipn.google.ac2dm {

    using System;     using System.Collections.Generic;     using System.Collections.Specialized;     using System.IO;     using System.Net;     using System.Net.Security;     using System.Security.Cryptography.X509Certificates;     using System.Text;     using System.Web;     /// <summary>     /// A classe AndroidCloud2DeviceMessaging faz integração com o serviço Android     /// Cloud to Device Messaging (C2DM) para enviar dados para dispositivos Android.     ///     /// Author: João Batista Neto     /// </summary>     public class AndroidCloud2DeviceMessaging {         /// <summary>         /// URL do serviço C2DM.         /// </summary>         public const String URL = "https://android.apis.google.com/c2dm/send";         /// <summary>         /// Conjunto de pares key=value que serão enviados para o dispositivo.         /// </summary>         private Dictionary<string,NameValueCollection> data;         public AndroidCloud2DeviceMessaging () {             data = new Dictionary<string, NameValueCollection>();         }         /// <summary>         /// Adiciona um par key=value que será enviado para o dispositivo android.         /// </summary>         /// <param name="key">         /// <see cref="System.String"/> A chave que será enviada para o dispositivo.         /// </param>         /// <param name="value">         /// <see cref="System.String"/> O valor da chave.         /// </param>         /// <param name="collapseKey">         /// <see cref="System.String"/> Chave de agrupamento que será utilizado pelo         /// Google para evitar que várias mensagens do mesmo tipo sejam         /// enviadas para o usuário de uma vez quando o dispositivo fique         /// online.         /// </param>         public void addData(string key,string value,string collapseKey) {             NameValueCollection nv;             if ( !data.ContainsKey(collapseKey) ) {                 nv = new NameValueCollection();                 data.Add( collapseKey , nv );             } else {                 nv = data[collapseKey];             }             nv.Set(key,value);         }         /// <summary>         /// Remove todas os pares key=value.         /// </summary>         public void clear() {             data.Clear();         }         /// <summary>         /// Envia a mensagem para o servidor C2DM.         /// </summary>         /// <param name="registrationId">         /// <see cref="System.String"/> ID de registro do dispositivo android.         /// </param>         /// <param name="auth">         /// <see cref="System.String"/> Token de autorização.         /// </param>         /// <seealso cref="com.paypal.ipn.google.auth.ClientLogin#getAuth"/>         public void send( string registrationId,string auth ) {             ServicePointManager.ServerCertificateValidationCallback = Validator;             HttpWebRequest request = (HttpWebRequest) WebRequest.Create( URL );             request.Method = "POST";             request.ContentType = "application/x-www-form-urlencoded";             request.Headers.Add( "Authorization" , "GoogleLogin auth=" + auth );             foreach ( string collapseKey in data.Keys ) {                 StringBuilder sb = new StringBuilder();                 NameValueCollection nv = data[collapseKey];                 sb.Append("registration_id=" + registrationId);                 sb.Append("&collapse_key=" + collapseKey);                 foreach ( string key in nv.AllKeys ) {                     sb.Append("&data." + key );                     sb.Append("=");                     sb.Append(HttpUtility.UrlEncode(nv[key]));                 }                 using ( Stream stream = request.GetRequestStream() ) {                     UTF8Encoding encoding = new UTF8Encoding();                     byte[] bytes = encoding.GetBytes( sb.ToString() );                     stream.Write( bytes , 0 , bytes.Length );                 }             }         }         /// <summary>         /// O certificado do Google não cobre android.apis.google.com, então será         /// preciso verificar e fazer a validação do certificado manualmente.         /// </summary>         /// <param name="sender">         /// <see cref="System.Object"/>         /// </param>         /// <param name="certificate">         /// <see cref="X509Certificate"/>         /// </param>         /// <param name="chain">         /// <see cref="X509Chain"/>         /// </param>         /// <param name="sslPolicyErrors">         /// <see cref="SslPolicyErrors"/>         /// </param>         /// <returns>         /// <see cref="System.Boolean"/> Retornamos         /// </returns>         bool Validator(             object sender,             X509Certificate certificate,             X509Chain chain,             SslPolicyErrors sslPolicyErrors ) {             return true;         }     } }

Com isso, definimos o manipulador de notificação instantânea de pagamento para receber as mensagens do PayPal, verificá-las e, então, despachá-las para o dispositivo Android:

Instant Payment Notification

Como a validação da mensagem é recorrente para qualquer manipulador, podemos ter um participante que faz essa validação e deixar a parte de regras específicas de negócio para um manipulador abstrato:

Em PHP
com/paypal/ipn/InstantPaymentNotification.php

<?php

namespace com\paypal\ipn; use \BadMethodCallException; /**  * Observador de Notificações de Pagamento Instantâneo.  *  * @author  João Batista Neto  */ class InstantPaymentNotification {     /**      * Endpoint de produção.      * @var string      */     const HOST = 'https://www.paypal.com';     /**      * Endpoint de produção.      * @var string      */     const SANDBOX_HOST = 'https://www.sandbox.paypal.com';     /**      * @var string      */     private $endpoint = InstantPaymentNotification::HOST;     /**      * @var com\paypal\ipn\IPNHandler      */     private $ipnHandler;     /**      * Constroi o observador no notificação instantânea de pagamento informando      * o ambiente que será utilizado para validação.      *      * @param   boolean $sandbox Define se será utilizado o Sandbox      * @throws  InvalidArgumentException      */     public function __construct( $sandbox = false ) {         if ( !!$sandbox ) {             $this->endpoint = InstantPaymentNotification::SANDBOX_HOST;         }         $this->endpoint .= '/cgi-bin/webscr?cmd=_notify-validate';     }     /**      * Aguarda por notificações de pagamento instantânea; Caso uma nova      * notificação seja recebida, faz a verificação e notifica um manipulador      * com o status (verificada ou não) e a mensagem recebida.      *      * @param   array $post Dados postatos pelo PayPal.      * @see     InstantPaymentNotification::setIPNHandler()      * @throws  BadMethodCallException Caso o método seja chamado antes de um      *          manipulador ter sido definido ou nenhum email de recebedor      *          tenha sido informado.      */     public function listen( array $post ) {         if ( $this->ipnHandler !== null && isset( $post[ 'receiver_email' ] ) ) {             $curl = curl_init ();             curl_setopt( $curl, CURLOPT_URL, $this->endpoint );             curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, false );             curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );             curl_setopt( $curl, CURLOPT_POST, 1 );             curl_setopt( $curl, CURLOPT_POSTFIELDS, http_build_query(                 $post             ) );             $response = curl_exec( $curl );             $error = curl_error( $curl );             $errno = curl_errno( $curl );             curl_close ( $curl );             if ( empty( $error ) && $errno == 0 ) {                 $this->ipnHandler->handle(                     $response == 'VERIFIED', $post                 );             }         }     }     /**      * Define o objeto que irá manipular as notificações de pagamento      * instantâneas enviadas pelo PayPal.      *      * @param   com\paypal\ipn\IPNHandler $ipnHandler      */     public function setIPNHandler( IPNHandler $ipnHandler ) {         $this->ipnHandler = $ipnHandler;     } } com/paypal/ipn/IPNHandler.php <?php

namespace com\paypal\ipn; /**  * Interface para definição de um manipulador de notificação  * de pagamento instantânea.  *  * @author João Batista Neto  */ interface IPNHandler {     /**      * Manipula uma notificação de pagamento instantânea recebida pelo PayPal.      *      * @param   boolean $isVerified Identifica que a mensagem foi verificada      *          como tendo sido enviada pelo PayPal.      * @param   array $message Mensagem completa enviada pelo PayPal.      */     public function handle( $isVerified, array $message ); }

E a implementação que fará o trabalho:

com/paypal/ipn/sample/IPNToAC2DMHandler.php

<?php

namespace com\paypal\ipn\sample; use com\paypal\ipn\google\ac2dm\AndroidCloud2DeviceMessaging; use com\paypal\ipn\IPNHandler; /**  * Manipulador de notificação instantânea de pagamento que envia a  * mensagem para dispositivos Android.  *  * @author  João Batista Neto  */ class IPNToAC2DMHandler implements IPNHandler {     /**      * @var com\paypal\ipn\sample\SampleModel      */     private $model;     public function __construct( SampleModel $model ) {         $this->model = $model;     }     /* (non-PHPdoc)      * @see com\paypal\ipn\IPNHandler#handle()      */     public function handle( $isVerified, array $message ) {         if ( $isVerified && isset( $message[ 'receiver_email' ] ) ) {             $ac2dm = new AndroidCloud2DeviceMessaging();             foreach ( $message as $field => $value ) {                 $ac2dm->addData( $field , $value, 'ipn' );             }             $ac2dm->send(                 $this->model->getRegistrationId( $message['receiver_email' ] ),                 $this->model->getAuth() );         }     } } Em Java
com/paypal/ipn/InstantPaymentNotification.java package com.paypal.ipn;

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLConnection; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; public class InstantPaymentNotification {     private static final String HOST = "https://www.paypal.com";     private static final String SANDBOX_HOST = "https://www.sandbox.paypal.com";     private String endpoint = InstantPaymentNotification.HOST;     private IPNHandler ipnHandler;     public InstantPaymentNotification() {         this(false);     }     /**      * Constroi o observador no notificação instantânea de pagamento informando      * o ambiente que será utilizado para validação.      *      * @param sandbox      *            {@link Boolean} Define se será utilizado o ambiente de      *            produção ou o Sandbox.      */     public InstantPaymentNotification(Boolean sandbox) {         if (sandbox) {             endpoint = InstantPaymentNotification.SANDBOX_HOST;         }         endpoint += "/cgi-bin/webscr?cmd=_notify-validate";     }     /**      * Aguarda por notificações de pagamento instantânea; Caso uma nova      * notificação seja recebida, faz a verificação e notifica um manipulador      * com o status (verificada ou não) e a mensagem recebida.      *      * @param post      *            {@link HttpServletRequest} Dados postatos pelo PayPal.      * @see {@link InstantPaymentNotification#setIPNHandler()}      */     public void listen(HttpServletRequest post) {         if (ipnHandler != null && post.getParameter("receiver_email") != null) {             try {                 BufferedReader reader = post.getReader();                 StringBuffer sb = new StringBuffer();                 String data;                 while ((data = reader.readLine()) != null) {                     sb.append(data);                 }                 URL url = new URL(endpoint);                 URLConnection conn = url.openConnection();                 conn.setDoOutput(true);                 OutputStreamWriter writer = new OutputStreamWriter(                         conn.getOutputStream());                 writer.write(sb.toString());                 writer.flush();                 writer.close();                 InputStreamReader in = new InputStreamReader(                         conn.getInputStream());                 reader = new BufferedReader(in);                 sb = new StringBuffer();                 data = null;                 while ((data = reader.readLine()) != null) {                     sb.append(data);                 }                 ipnHandler.handle(sb.toString().equals("VERIFIED"), post);             } catch (IOException e) {                 Logger.getLogger(InstantPaymentNotification.class.getName())                         .log(Level.SEVERE, null, e);             }         }     }     /**      * Define o objeto que irá manipular as notificações de pagamento      * instantâneas enviadas pelo PayPal.      *      * @param ipnHandler      *            {@link IPNHandler}      */     public void setIPNHandler(IPNHandler ipnHandler) {         this.ipnHandler = ipnHandler;     } } com/paypal/ipn/IPNHandler.java package com.paypal.ipn;

import javax.servlet.http.HttpServletRequest; /**  * Interface para definição de um manipulador de notificação de pagamento  * instantânea.  *  * @author João Batista Neto  */ public interface IPNHandler {     /**      * Manipula uma notificação de pagamento instantânea recebida pelo PayPal.      *      * @param isVerified      *            {@link Boolean} Identifica que a mensagem foi verificada como      *            tendo sido enviada pelo PayPal.      * @param message      *            {@link HttpServletRequest} Mensagem completa enviada pelo      *            PayPal.      */     public void handle(Boolean isVerified, HttpServletRequest message); } com/paypal/ipn/sample/IPNToAC2DMHandler.java package com.paypal.ipn.sample;

import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import com.paypal.ipn.IPNHandler; import com.paypal.ipn.google.ac2dm.AndroidCloud2DeviceMessaging; /**  * Manipulador de notificação instantânea de pagamento que envia a mensagem para  * dispositivos Android.  *  * @author João Batista Neto  */ public class IPNToAC2DMHandler implements IPNHandler {     private SampleModel model;     public IPNToAC2DMHandler(SampleModel model) {         this.model = model;     }     /*      * (non-Javadoc)      *      * @see com.paypal.ipn.IPNHandler#handle()      */     @SuppressWarnings("rawtypes")     @Override     public void handle(Boolean isVerified, HttpServletRequest message) {         if (isVerified) {             AndroidCloud2DeviceMessaging ac2dm = new AndroidCloud2DeviceMessaging();             Enumeration fields = message.getParameterNames();             while (fields.hasMoreElements()) {                 String field = (String) fields.nextElement();                 ac2dm.addData(field, message.getParameter(field), "ipn");             }             ac2dm.send(model.getRegistrationId(message                     .getParameter("receiver_email")), model.getAuth());         }     } } Em C#
com/paypal/ipn/InstantPaymentNotification.cs namespace com.paypal.ipn {

    using System;     using System.IO;     using System.Net;     using System.Text;     using System.Web;     using System.Web.Mvc;     /// <summary>     /// Observador de Notificações de Pagamento Instantâneo.     ///     /// Author: João Batista Neto     /// </summary>     public class InstantPaymentNotification {         /// <summary>         /// Endpoint de produção.         /// </summary>         public const string HOST = "https://www.paypal.com";         /// <summary>         /// Endpoint do Sandbox         /// </summary>         public const string SANDBOX_HOST = "https://www.sandbox.paypal.com";         /// <summary>         /// Endpoint que será utilizado na verificação         /// </summary>         private string endpoint = InstantPaymentNotification.HOST;         /// <summary>         /// Manipulador de notificação instantânea de pagamento.         /// </summary>         private IPNHandler handler;         public InstantPaymentNotification() :this(false) {}         /// <summary>         /// Constroi o observador no notificação instantânea de pagamento informando         /// o ambiente que será utilizado para validação.         /// </summary>         /// <param name="sandbox">         /// <see cref="System.Boolean"/> Define se será utilizado o ambiente de         /// produção ou o Sandbox.         /// </param>         public InstantPaymentNotification ( bool sandbox ) {             if ( sandbox ) {                 endpoint = InstantPaymentNotification.SANDBOX_HOST;             }             endpoint += "/cgi-bin/webscr?cmd=_notify-validate";         }         /// <summary>         /// Aguarda por notificações de pagamento instantânea; Caso uma nova         /// notificação seja recebida, faz a verificação e notifica um manipulador         /// com o status (verificada ou não) e a mensagem recebida.         /// </summary>         /// <param name="post">         /// A <see cref="FormCollection"/> com os dados postados pelo PayPal         /// </param>         /// <seealso cref="InstantPaymentNotification#setIPNHandler()"/>         public void listem( FormCollection post ) {             if ( handler != null && post[ "receiver_email" ] != null ) {                 HttpWebRequest request = (HttpWebRequest) WebRequest.Create( endpoint );                 request.Method = "POST";                 request.ContentType = "application/x-www-form-urlencoded";                 StringBuilder sb = new StringBuilder();                 foreach ( string field in post ) {                     if ( sb.Length != 0 ) {                         sb.Append( "&" );                     }                     sb.Append( field );                     sb.Append( "=" );                     sb.Append( post[ field ] );                 }                 using ( Stream stream = request.GetRequestStream() ) {                     UTF8Encoding encoding = new UTF8Encoding();                     byte[] bytes = encoding.GetBytes( sb.ToString() );                     stream.Write( bytes , 0 , bytes.Length );                 }                 HttpWebResponse response = (HttpWebResponse) request.GetResponse();                 using ( Stream stream = response.GetResponseStream() ) {                     using ( StreamReader reader = new StreamReader( stream , Encoding.UTF8 ) ) {                         string data = reader.ReadToEnd();                         reader.Close();                         handler.handle( data.Equals( "VERIFIED" ) , post );                     }                 }             }         }         /// <summary>         /// Define o objeto que irá manipular as notificações de pagamento         /// instantâneas enviadas pelo PayPal.         /// </summary>         /// <param name="handler">         /// <see cref="IPNHandler"/>         /// </param>         public void setIPNHandler( IPNHandler handler ) {             this.handler = handler;         }     } } com/paypal/ipn/IPNHandler.cs namespace com.paypal.ipn {

    using System;     using System.Web.Mvc;     /// <summary>     /// Interface para definição de um manipulador de notificação     /// de pagamento instantânea.     ///     /// Author: João Batista Neto     /// </summary>     public interface IPNHandler {         /// <summary>         /// Manipula uma notificação de pagamento instantânea recebida pelo PayPal.         /// </summary>         /// <param name="isVerified">         /// <see cref="System.Boolean"/> isVerified Identifica que a mensagem foi         /// verificada como tendo sido enviada pelo PayPal.         /// </param>         /// <param name="form">         /// <see cref="FormCollection"/> Mensagem completa enviada pelo PayPal.         /// </param>         void handle( bool isVerified , FormCollection message );     } } com/paypal/ipn/sample/IPNToAC2DMHandler.cs namespace com.paypal.ipn.sample {

    using System;     using System.Web.Mvc;     using com.paypal.ipn;     using com.paypal.ipn.google.ac2dm;     /// <summary>     /// Manipulador de notificação instantânea de pagamento que envia a     /// mensagem para dispositivos Android.     /// </summary>     public class IPNToAC2DMHandler :IPNHandler {         private SampleModel model;         public IPNToAC2DMHandler ( SampleModel model ) {             this.model = model;         }         /// <summary>         /// Envia as notificações instantânea de pagamento para dispositivos         /// Android caso a mensagem tenha sido verificada pelo PayPal.         /// </summary>         /// <param name="isVerified">         /// <see cref="System.Boolean"/> TRUE caso o PayPal tenha verificado         /// a mensagem.         /// </param>         /// <param name="message">         /// <see cref="FormCollection"/> A mensagem enviada pelo PayPal         /// </param>         public void handle( bool isVerified , FormCollection message ) {             if ( isVerified ) {                 AndroidCloud2DeviceMessaging ac2dm = new AndroidCloud2DeviceMessaging();                 foreach ( string field in message ) {                     ac2dm.addData( field , message[field] , "ipn" );                 }                 ac2dm.send(model.getRegistrationId(message["receiver_email"]),                            model.getAuth() );             }         }     } }

Aplicação Android

No Eclipse, criamos um novo “Projeto Android”:

new project

O Android Cloud to Device Messaging está disponível desde a versão 2.2, então utilizem a que for mais adequada para o projeto.

target

Em seguida algumas configurações sobre o projeto:

application

E pronto, temos o esqueleto do projeto Android:

project

Para poder utilizar o serviço C2DM, precisaremos adicionar algumas permissões no AndroidManifest.xml, ele ficará assim:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"     package="com.paypal.ipn"     android:versionCode="1"     android:versionName="1.0" >     <uses-sdk android:minSdkVersion="11" />     <permission         android:name="com.paypal.ipn.permission.C2D_MESSAGE"         android:protectionLevel="signature" />     <uses-permission android:name="com.paypal.ipn.permission.C2D_MESSAGE" />     <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />     <uses-permission android:name="android.permission.INTERNET" />     <application         android:icon="@drawable/ic_launcher"         android:label="@string/app_name" >         <activity             android:name=".RegisterActivity"             android:label="@string/app_name" >             <intent-filter>                 <action android:name="android.intent.action.MAIN" />                 <category android:name="android.intent.category.LAUNCHER" />             </intent-filter>         </activity>         <service android:name=".C2DMReceiver" />         <!--              Apenas servidores C2DM podem enviar mensagens para a aplicação.              Se a permissão não estiver definida, qualquer outra aplicação pode              gerar a mensagem.         -->         <receiver             android:name="com.google.android.c2dm.C2DMBroadcastReceiver"             android:permission="com.google.android.c2dm.permission.SEND" >             <!--                  Recebe a mensagem             -->             <intent-filter>                 <action android:name="com.google.android.c2dm.intent.RECEIVE" />                 <category android:name="com.paypal.ipn" />             </intent-filter>             <!--                  Recebe o ID de registro             -->             <intent-filter>                 <action android:name="com.google.android.c2dm.intent.REGISTRATION" />                 <category android:name="com.paypal.ipn" />             </intent-filter>         </receiver>     </application> </manifest>

Nossa interface de usuário é bastante simples:

layout/register.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="fill_parent"     android:layout_height="fill_parent"     android:gravity="center"     android:orientation="vertical" >     <ImageView         android:id="@+id/imageViewBrand"         android:contentDescription="@string/app_name"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:paddingBottom="30dp"         android:src="@drawable/ic_brand" />     <EditText         android:id="@+id/textEmailAddress"         android:layout_width="676dp"         android:layout_height="wrap_content"         android:hint="@string/register_email"         android:inputType="textEmailAddress" >         <requestFocus />     </EditText>     <Button         android:id="@+id/registerButton"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:onClick="register"         android:text="@string/register_button" /> </LinearLayout>

O processo de registro é muito simples:

// Usamos a API Intent para para obter o ID de registro

Intent regIntent = new Intent(  "com.google.android.c2dm.intent.REGISTER" ); // Identificamos a aplicação registrationIntent.putExtra("app",                 PendingIntent.getBroadcast(context, 0, new Intent(), 0)); // Identificamos a conta que o servidor usará para enviar as notificações registrationIntent.putExtra("sender", senderId); context.startService(registrationIntent);

Como a Google já providenciou um framework para esse trabalho, precisamos apenas utilizar o código disponibilizado:

com/paypal/ipn/RegisterActivity.java

package com.paypal.ipn;

import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.google.android.c2dm.C2DMessaging; public class RegisterActivity extends Activity {     /** Called when the activity is first created. */     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.register);         ((EditText) findViewById(R.id.textEmailAddress))                 .setOnKeyListener(new OnKeyListener() {                     @Override                     public boolean onKey(View v, int keyCode, KeyEvent e) {                         if (e.getAction() == KeyEvent.ACTION_DOWN                                 && keyCode == KeyEvent.KEYCODE_ENTER) {                             register(v);                         }                         return false;                     }                 });     }     public void register(View view) {         Toast.makeText(this, "Starting", Toast.LENGTH_LONG).show();         ((EditText) findViewById(R.id.textEmailAddress)).setEnabled(false);         ((Button) findViewById(R.id.registerButton)).setEnabled(false);         String email = ((EditText) findViewById(R.id.textEmailAddress))                 .getText().toString();         SharedPreferences prefs = getSharedPreferences(getResources()                 .getString(R.string.app_id), Context.MODE_PRIVATE);         Editor editor = prefs.edit();         editor.putString("receiverEmail", email);         editor.commit();         C2DMessaging.register(this, email);     } } E o responsável por receber as notificações:
com/paypal/ipn/C2DMReceiver.java package com.paypal.ipn;

import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLConnection; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import com.google.android.c2dm.C2DMBaseReceiver; public class C2DMReceiver extends C2DMBaseReceiver {     public C2DMReceiver() {         super("brasil@x.com");     }     /**      * Recebe o id de registro e envia para a nossa aplicação no servidor, que      * manipulará as notificações instantânea de pagamento.      */     @Override     public void onRegistered(Context context, String registrationId)             throws java.io.IOException {         SharedPreferences prefs = context.getSharedPreferences(                 context.getString(R.string.app_id), Context.MODE_PRIVATE);         URL url = new URL(context.getString(R.string.register_server));         URLConnection conn = url.openConnection();         StringBuilder sb = new StringBuilder();         sb.append("registrationId=" + registrationId);         sb.append("&receiverEmail=" + prefs.getString("receiverEmail", ""));         conn.setDoOutput(true);         OutputStreamWriter writer = new OutputStreamWriter(                 conn.getOutputStream());         writer.write(sb.toString());         writer.flush();         writer.close();         Editor editor = prefs.edit();         editor.putString("registrationId", registrationId);         editor.commit();     };     /**      * Mensagem recebida!      */     @Override     protected void onMessage(Context context, Intent intent) {         String ns = Context.NOTIFICATION_SERVICE;         // Pegamos o gerenciados de notificação         NotificationManager notificationManager = (NotificationManager) getSystemService(ns);         // Ícone que aparecerá na notificação         int icon = R.drawable.ic_launcher;         // Mensagem que aparecerá quando a notificação aparecer.         CharSequence tickerText = context                 .getString(R.string.ipn_notification_ticker);         long when = System.currentTimeMillis();         // Criamos a notificação, definindo o título, texto, som, etc         Notification notification = new Notification(icon, tickerText, when);         CharSequence contentTitle = context                 .getString(R.string.ipn_notification_title);         CharSequence contentText = context                 .getString(R.string.ipn_notification_text);         Intent notificationIntent = new Intent(this, RegisterActivity.class);         PendingIntent contentIntent = PendingIntent.getActivity(this, 0,                 notificationIntent, 0);         notification.defaults |= Notification.DEFAULT_SOUND;         notification.setLatestEventInfo(context, contentTitle, contentText,                 contentIntent);         // Exibimos a notificação         notificationManager.notify(1, notification);     }     @Override     public void onError(Context context, String errorId) {         // opz     } }

É isso, para testar o recebimento das notificações no dispositivo Android basta ir até Test Tools no PayPal Sandbox: Testando Notificações Instantâneas de Pagamento no PayPal Sandbox

Abaixo algumas telas capturadas do dispositivo rodando o exemplo:

ipn c2dm application 1
ipn c2dm application 2
ipn c2dm application 3
ipn c2dm application 4

O código utilizado no exemplo pode ser encontrado em Code Sample

Nota: Os códigos de exemplo são meramente ilustrativos, com o intuito de demonstrar a facilidade de integração com a API. Apesar de funcionarem segundo a proposta do tutorial, devem ser tratados como exemplos e não como código de produção.

é engenheiro de aplicações e trabalha com ambiente web desde 2000 em diversas linguagens, como Java e PHP, dedicando esforços ao desenvolvimento de bibliotecas reutilizáveis para a comunidade. Especialista em integração de sistemas, possui várias bibliotecas reutilizáveis publicadas como open-source para a comunidade, como biblioteca Cielo, PayPal, ECT (Correios), BuscaPé, Lomadee, Twitter, Facebook entre várias outras. É administrador do fórum iMasters e iMasters Code, onde compartilha conhecimento com a comunidade de desenvolvedores. Também é autor de cursos no iMasters PRO.

Ver perfil