Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

RSS iMasters

[Resolvido] Utilizando Android Cloud to Device Messaging com PayP

Recommended Posts

Olá, pessoal! No artigo "Manipulador de notificação de pagamento instantâneo", ilustramos como construir um manipulador para receber e validar as notificações instantâneas de pagamento do PayPal 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 monitor a aplicação para ver se uma notificação chegou, ou então fazer uma consulta manual, utilizando a operação GetTransactionDetails para saber se houve tal mudança de status de pagamento.

A ideia desse artigo é utilizar o manipulador descrito no artigo supracitado 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. Dessa forma, assim que uma mensagem IPN chegar, receberemos a informação em nossos dispositivos móveis.

O 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 ele, 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, utilizaremos ClientLogin. Como a implementação é feita em três linguagens diferentes, escolhi um padrão de nomenclatura de classes e pacotes para facilitar e simplificar as coisas.

A implementação do ClientLogin é 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, é preciso que:

  1. A aplicação envie a mensagem para o serviço C2DM contendo o ID de registro, mensagem e Token de autorização;
  2. Google receba a mensagem e coloque em uma fila. Se o dispositivo estiver offline, o Google armazena a mensagem;
  3. Quando o dispositivo esteja online, o Google envie a mensagem para o dispositivo;
  4. O sistema transmita 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 receba a mensagem e a processe 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 só 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 faça essa validação e deixe 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;

 

/**

 * Observador de Notificações de Pagamento Instantâneo.

 * 

 * @author João Batista Neto

 */

public class InstantPaymentNotification {

    /**

     * HOST de produção.

     */

    private static final String HOST = "https://www.paypal.com";

 

    /**

     * HOST do Sandbox

     */

    private static final String SANDBOX_HOST = "https://www.sandbox.paypal.com";

 

    /**

     * Endpoint que será utilizado na validação

     */

    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 nós 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:

AndroidManifest.xml

<?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" />

    <uses-permission android:name="android.permission.WAKE_LOCK" />

 

    <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>

Assim como o processo de registro:

// 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é a página Testando Notificações Instantâneas de Pagamento, no PayPal Sandbox

Abaixo algumas telas capturadas do dispositivo rodando o exemplo:

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

O código utilizado no exemplo está disponível no perfil do PayPal X Brasil no github: PayPal X Brasil

 

http://imasters.com.br/artigo/23512/paypal/utilizando-android-cloud-to-device-messaging-com-paypal-instant-payment-notification

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.