APIs e Microsserviços

13 dez, 2012

Implementação de Market Place em C#

Publicidade

No tutorial Implementação de Market Place com Adaptive Payments falamos sobre a operação Pay para definição de pagamentos paralelos ou encadeados.

Nesse tutorial veremos como implementar a operação Pay utilizando C#, criando uma biblioteca para ser utilizada por nossas aplicações.

Como vamos utilizar a inteface NVP, vamos trabalhar com a classe System.Collections.Specialized.NameValueCollection que oferece todos os recursos necessários para o que precisamos:

namespace PayPal {

    using System;
    using System.Collections.Specialized;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Web;
    public class AdaptivePayments {
        public const string HOST = "svcs.paypal.com";
        public const string SANDBOX_APPID = "APP-80W284485P519543T";
        public const string SANDBOX_HOST = "svcs.sandbox.paypal.com";
        public string UserId { get; set; }
        public string Password { get; set; }
        public string Signature { get; set; }
        public string AppID { get; set; }
        public bool Sandbox { get; set; }
        public AdaptivePayments( string appID , string userId , string password , string signature ) {
            AppID = appID;
            UserId = userId;
            Password = password;
            Signature = signature;
            Sandbox = appID.Equals( SANDBOX_APPID );
        }
        public AdaptivePayments( string userId , string password , string signature ):
        this(SANDBOX_APPID,userId,password,signature) {}
        public NameValueCollection execute( AdaptivePaymentsOperation o ) {
            StringBuilder sb = new StringBuilder( "https://" );
            sb.Append( Sandbox ? SANDBOX_HOST : HOST );
            sb.Append( "/AdaptivePayments/" );
            sb.Append( o.getOperation() );
            Console.WriteLine( sb.ToString() );
            HttpWebRequest request = (HttpWebRequest) WebRequest.Create( sb.ToString() );
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            /**
             * Definindo as credenciais da API
             */
            request.Headers.Add( "X-PAYPAL-SECURITY-USERID" , UserId );
            request.Headers.Add( "X-PAYPAL-SECURITY-PASSWORD" , Password );
            request.Headers.Add( "X-PAYPAL-SECURITY-SIGNATURE" , Signature );
            request.Headers.Add( "X-PAYPAL-APPLICATION-ID" , AppID );
            /**
             * Definindo o formato da requisição e resposta
             */
            request.Headers.Add( "X-PAYPAL-REQUEST-DATA-FORMAT" , "NV" );
            request.Headers.Add( "X-PAYPAL-RESPONSE-DATA-FORMAT" , "NV" );
            /**
             * Montando e enviando a requisição
             */
            NameValueCollection requestNvp = o.getNvp();
            using ( Stream stream = request.GetRequestStream() ) {
                sb = new StringBuilder();
                for ( int i = 0 , t = requestNvp.Count ; i < t ; ++i ) {
                    string key = requestNvp.GetKey( i );
                    string value = requestNvp.Get( key );
                    Console.WriteLine( key + "=" + value );
                    sb.Append( key + "=" );
                    sb.Append( HttpUtility.UrlEncode( value ) + "&" );
                }
                sb.Append( "requestEnvelope.errorLanguage=en_US" );
                UTF8Encoding encoding = new UTF8Encoding();
                byte[] bytes = encoding.GetBytes( sb.ToString() );
                stream.Write( bytes , 0 , bytes.Length );
            }
            /**
             * Recuperando a resposta e obtendo os pares nvp
             */
            HttpWebResponse response = (HttpWebResponse) request.GetResponse();
            NameValueCollection responseNvp;
            using ( Stream stream = response.GetResponseStream() ) {
                using ( StreamReader reader = new StreamReader( stream , Encoding.UTF8 ) ) {
                    string result = reader.ReadToEnd();
                    responseNvp = HttpUtility.ParseQueryString( result );
                }
            }
            return responseNvp;
        }
        public PayOperation Pay() {
            return Pay( ActionType.PAY );
        }
        public PayOperation Pay( ActionType actionType ) {
            PayOperation payOperation = new PayOperation( this );
            payOperation.actionType = actionType;
            return payOperation;
        }
    }
}

A definição da classe AdaptivePayments além de fazer a requisição ao PayPal, também oferece a criação da instância da classe PayOperation, que será utilizada para configurar a requisição.

namespace PayPal {

    using System;
    using System.Collections.Specialized;
    public abstract class AdaptivePaymentsOperation {
        private AdaptivePayments ap;
        protected NameValueCollection nvp;
        public AdaptivePaymentsOperation ( AdaptivePayments ap ) {
            nvp = new NameValueCollection();
            this.ap = ap;
        }
        public NameValueCollection execute() {
            return ap.execute( this );
        }
        internal NameValueCollection getNvp() {
            return nvp;
        }
        internal abstract string getOperation();
    }
}
A classe AdaptivePaymentsOperation oferece uma interface comum para as operações da API Adaptive Payments, qualquer classe de operação será derivada da AdaptivePaymentsOperation, como a PayOperation:
namespace PayPal {

    using System;
    using System.Collections.Generic;
    public class PayOperation : AdaptivePaymentsOperation {
        private List<Receiver> receivers;
        public PayOperation ( AdaptivePayments ap ) :base( ap ) {
            receivers = new List<Receiver>();
            currencyCode = CurrencyCode.DEFAULT;
        }
        override internal string getOperation() {
            return "Pay";
        }
        public ActionType actionType {
            get { return (ActionType) getNvp().Get( "actionType" ); }
            set { getNvp().Set( "actionType" , value.ToString() ); }
        }
        public string cancelUrl {
            get { return getNvp().Get( "cancelUrl" ); }
            set { getNvp().Set( "cancelUrl" , value ); }
        }
        public CurrencyCode currencyCode {
            get { return (CurrencyCode) getNvp().Get( "currencyCode" ); }
            set { getNvp().Set( "currencyCode" , value.ToString() ); }
        }  
        public Receiver receiverList( int n ) {
            if ( receivers.Count == n ) {
                receivers.Add( new Receiver(this,n) );
            }
            return receivers[n];
        }
        public string returnUrl {
            get { return getNvp().Get( "returnUrl" ); }
            set { getNvp().Set( "returnUrl" , value ); }
        }
    }
}

Como podemos ver, a classe PayOperation é bastante simples, ela apenas define uma interface para que possamos definir os valores dos campos da requisição. Ela também utiliza 3 outros participantes CurrencyCode, ActionType e Receiver.

ActionType e CurrencyCode foram criados apenas para garantir que os valores informados nesses campos sejam o que é esperado pela documentação da API, facilitando a depuração e manutenção do código.

namespace PayPal {

    using System;
    using System.Collections.Generic;
    public class ActionType {
        private static readonly Dictionary<string, ActionType> actionType = new Dictionary<string, ActionType>();
        public static readonly ActionType PAY = new ActionType( "PAY" );
        public static readonly ActionType CREATE = new ActionType( "CREATE" );
        public static readonly ActionType PAY_PRIMARY = new ActionType( "PAY_PRIMARY" );
        private string value;
        private ActionType( string type ) {
            actionType["PAY"] = PAY;
            actionType["CREATE"] = CREATE;
            actionType["PAY_PRIMARY"] = PAY_PRIMARY;
            value = type;
        }
        public static explicit operator ActionType( string value ) {
            ActionType result;
            if ( actionType.TryGetValue( value , out result ) ) {
                return result;
            } else {
                throw new InvalidCastException();
            }
        }
        public override string ToString () {
             return value;
        }
    }
}
namespace PayPal {

    using System;
    using System.Collections.Generic;
    public sealed class CurrencyCode {
        private static readonly Dictionary<string, CurrencyCode> currencyCodes = new Dictionary<string, CurrencyCode>();
        public static readonly CurrencyCode AUSTRALIAN_DOLLAR = new CurrencyCode( "AUD");
        public static readonly CurrencyCode BRAZILIAN_REAL = new CurrencyCode( "BRL" );
        public static readonly CurrencyCode CANADIAN_DOLLAR = new CurrencyCode( "CAD" );
        public static readonly CurrencyCode CZECH_KORUNA = new CurrencyCode( "CZK" );
        public static readonly CurrencyCode DANISH_KRONE = new CurrencyCode( "DKK" );
        public static readonly CurrencyCode EURO = new CurrencyCode( "EUR" );
        public static readonly CurrencyCode HONG_KONG_DOLLAR = new CurrencyCode( "HKD" );
        public static readonly CurrencyCode HUNGARIAN_FORINT = new CurrencyCode( "HUF" );
        public static readonly CurrencyCode ISRAELI_NEW_SHEQEL = new CurrencyCode( "ILS" );
        public static readonly CurrencyCode JAPAN_YEN = new CurrencyCode( "JPY" );
        public static readonly CurrencyCode MALAYSIAN_RINGGIT = new CurrencyCode( "MYR" );
        public static readonly CurrencyCode MEXICAN_PESO = new CurrencyCode( "MXN" );
        public static readonly CurrencyCode NORWEGIAN_KRONE = new CurrencyCode( "NOK" );
        public static readonly CurrencyCode NEW_ZEALAND_DOLLAR = new CurrencyCode( "NZD" );
        public static readonly CurrencyCode PHILIPPINE_PESO = new CurrencyCode( "PHP" );
        public static readonly CurrencyCode POLISH_ZLOTY = new CurrencyCode( "PLN" );
        public static readonly CurrencyCode POUND_STERLING = new CurrencyCode( "GBP" );
        public static readonly CurrencyCode SINGAPORE_DOLLAR = new CurrencyCode( "SGD" );
        public static readonly CurrencyCode SWEDISH_KRONA = new CurrencyCode( "SEK" );
        public static readonly CurrencyCode SWISS_FRANC = new CurrencyCode( "CHF" );
        public static readonly CurrencyCode TAIWAN_NEW_DOLLAR = new CurrencyCode( "TWD" );
        public static readonly CurrencyCode TAI_BAHT = new CurrencyCode( "THB" );
        public static readonly CurrencyCode TURKISH_LIRA = new CurrencyCode( "TRY" );
        public static readonly CurrencyCode US_DOLLAR = new CurrencyCode( "USD" );
        public static readonly CurrencyCode DEFAULT = CurrencyCode.US_DOLLAR;
        private string value;
        private CurrencyCode ( string value ) {
            currencyCodes["AUD"] = AUSTRALIAN_DOLLAR;
            currencyCodes["BRL"] = BRAZILIAN_REAL;
            currencyCodes["CAD"] = CANADIAN_DOLLAR;
            currencyCodes["CZK"] = CZECH_KORUNA;
            currencyCodes["DKK"] = DANISH_KRONE;
            currencyCodes["EUR"] = EURO;
            currencyCodes["HKD"] = HONG_KONG_DOLLAR;
            currencyCodes["USD"] = US_DOLLAR;
            this.value = value;
        }
        public static explicit operator CurrencyCode( string code ) {
            CurrencyCode currencyCode;
            if ( currencyCodes.TryGetValue( code , out currencyCode ) ) {
                return currencyCode;
            } else {
                throw new InvalidCastException();
            }
        }
        public override string ToString() {
            return  value;
        }
    }
}

Por fim, temos o Receiver, que representa um recebedor do pagamento:

namespace PayPal {

    using System;
    using System.Globalization;
    public class Receiver {
        private int n;
        private AdaptivePaymentsOperation o;
        public Receiver( AdaptivePaymentsOperation o , int n ) {
            this.o = o;
            this.n = n;
        }
        public double Amount {
            get {
                string amount = o.getNvp().Get( "receiverList.receiver(" + n + ").amount" );
                return Double.Parse( amount );
            }
            set {
                o.getNvp().Set(
                    "receiverList.receiver(" + n + ").amount",
                    value.ToString( CultureInfo.InvariantCulture )
                );
            }
        }
        public string Email {
            get { return o.getNvp().Get( "receiverList.receiver(" + n + ").email" ); }
            set { o.getNvp().Set( "receiverList.receiver(" + n + ").email" , value ); }
        }
        public bool Primary {
            get {
                string v = o.getNvp().Get( "receiverList.receiver(" + n + ").primary" );
                return v == "true";
            }
            set {
                o.getNvp().Set( "receiverList.receiver(" + n + ").primary" , value ? "true" : "false" );
            }
        }
    }
}

Utilizar a biblioteca é bastante simples:

/**

 * Cria a instância do wrapper da API Adaptive Payments
 */
AdaptivePayments adaptivePayments = new AdaptivePayments(
    AdaptivePayments.SANDBOX_APPID,
    "usuario",
    "senha",
    "assinatura"
);
/**
 * Cria a instância da operação Pay
 */
PayOperation payOperation = adaptivePayments.Pay();
/**
 * Configura um pagamento paralelo
 */
payOperation.cancelUrl = "http://127.0.0.1/cancel";
payOperation.returnUrl = "http://127.0.0.1/return";
payOperation.receiverList(0).Email = "neto_1306507007_biz@gmail.com";
payOperation.receiverList(0).Amount = 100;
payOperation.receiverList(1).Email = "neto.j_1324471857_biz@gmail.com";
payOperation.receiverList(1).Amount = 100;
/**
 * Executa a operação e obtem o retorno
 */
NameValueCollection nvp = payOperation.execute();
/**
 * Verifica se a operação foi bem sucedida e obtem a chave de pagamento
 */
if ( nvp.Get("responseEnvelope.ack") == "Success" ) {
    string payKey = nvp.Get("payKey");
    return payKey;
} else {
    throw new ApplicationException();
}

Se separarmos essa utilização em um participante específico, teremos:

namespace MarketPlace.Models {

    using System;
    using System.Collections.Specialized;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using PayPal;
    public class Payment {
        private PayOperation payOperation;
        private int currentReceiver = 0;
        private string payKey;
        public Payment () {
            ServicePointManager.ServerCertificateValidationCallback = Validator;
            /**
             * Cria a instância do wrapper da API Adaptive Payments
             */
            AdaptivePayments adaptivePayments = new AdaptivePayments(
                AdaptivePayments.SANDBOX_APPID,
                "usuario",
                "senha",
                "assinatura"
            );
            /**
             * Cria a instância da operação Pay
             */
            payOperation = adaptivePayments.Pay();
        }
        public Receiver AddReceiver( string Email , double Amount ) {
            return AddReceiver( Email , Amount , false );
        }
        public Receiver AddReceiver( string Email , double Amount , bool Primary ) {
            Receiver receiver = payOperation.receiverList( currentReceiver );
            receiver.Amount = Amount;
            receiver.Email = Email;
            receiver.Primary = Primary;
            return receiver;
        }
        public string CancelUrl {
            get { return payOperation.cancelUrl; }
            set { payOperation.cancelUrl = value; }
        }
        public string Execute() {
            /**
             * Executa a operação e obtem o retorno
             */
            NameValueCollection nvp = payOperation.execute();
            /**
             * Verifica se a operação foi bem sucedida e obtem a chave de pagamento
             */
            if ( nvp.Get("responseEnvelope.ack") == "Success" ) {
                payKey = nvp.Get("payKey");
                return payKey;
            } else {
                throw new ApplicationException();
            }
        }
        public string RedirectUrl {
            get {
                return "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=" + payKey;
            }
        }
        public string ReturnUrl {
            get { return payOperation.returnUrl; }
            set { payOperation.returnUrl = value; }
        }
        bool Validator(
            object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors ) {
            return true;
        }
    }
}

Com isso, o controlador pode receber a requisição de checkout de um produto, criar a transação e direcionar o cliente para a página de pagamento do PayPal:

namespace MarketPlace.Controllers {

    using System;
    using System.Collections.Specialized;
    using System.Web;
    using System.Web.Mvc;
    using MarketPlace.Models;
    [HandleError]
    public class HomeController : Controller {
        public ActionResult Index () {
            Payment payment = new Payment();
            payment.AddReceiver( "neto_1306507007_biz@gmail.com" , 200 , true );
            payment.AddReceiver( "neto.j_1324471857_biz@gmail.com" , 100 );
            payment.CancelUrl = "http://127.0.0.1:8080/Cancel";
            payment.ReturnUrl = "http://127.0.0.1:8080/Cancel";
            payment.Execute();
            return Redirect( payment.RedirectUrl );
        }
    }
}

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.