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(); }}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.



