简体   繁体   中英

PayPal C# SDK Endpoint

I'm looking for a way to set the PayPal SOAP API endpoint in the code rather than specifying it in the web.config or app.config. I need to read & use the endpoint from an environment-specific configuration that is not the web.config/app.config.

Is this possible? I've read some of the code for the SDK on their github repo and it does not appear possible but I'm hoping I missed something.

I'm using the PayPal Merchant SDK for .Net, ver 2.1.96.0.

I needed to be able to specify all important settings outside of the .config file. I'm lazy so here's how I did it:

paypalConfig = new Dictionary<string, string>();
paypalConfig.Add("apiUsername", "xxxxx");
paypalConfig.Add("apiPassword", "xxxxx");
paypalConfig.Add("apiSignature", "xxxxx");
==>paypalConfig.Add("mode","live|sandbox");

And then you need to call your method by specifying again the credentials (haven't investigated much why this is necessary):

var service = new PayPalAPIInterfaceServiceService(paypalConfig);
var doCaptureResponse = service.DoCapture(amount, new SignatureCredential(paypalConfig["apiUsername"], paypalConfig["apiPassword"], paypalConfig["apiSignature"]));

Answer 2: BUT If you're fine with editing PayPal's code this will work fine...

(PayPal: if you're reading this PLEASE implement this feature!!)


EDIT: THIS IS NO LONGER NECESSARY - SEE ANSWER BY Simon Labrecque


So after spending HOURS on this and having written my application around the expectation that I could switch endpoint between live and sandbox (just like I used to when directly calling the SOAP service) I decided to just go and dig into the source more and figure it out.

Here's the changes I made to get it to work.

Assumptions:

Steps:

  • You will be editing PayPal's source code and compiling locally - but only 2 files:
  • Blank out the endpoint in web.config under <paypal> . Recommended safety precaution!
  • Download source for PayPalAPIInterfaceServiceService.cs from GitHub (Merchant SDK)
  • Download source for DefaultSOAPAPICallHandler.cs from GitHub (Core SDK)
  • You probably want to take a minute to make sure the version is the same as your nuGet package.

  • Make a copy of both of these in your project in a folder PayPalMerchantSDK or something like that

  • I'd recommend renaming both files to avoid confusion and conflict with the NuGet versions. I got frustrated and just called them SimonsPayPalAPIInterfaceServiceService and SimonsSOAPAPICallHandler - but call them whatever you want.

Changes to SimonsSOAPAPICallHandler.cs

Change the constructor to add a boolean useSandbox :

Note: It must be the first parameter because we're going to do some search and replace magic soon.

    public SimonsSOAPAPICallHandler(bool useSandbox, string rawPayLoad, string attributesNamespace,
        string headerString)
        : base()
    {
        this.rawPayLoad = rawPayLoad;
        this.nmespceAttributes = attributesNamespace;
        this.headElement = headerString;

        // you can get these from config if you wish but I doubt they'll ever change
        this.endpoint = useSandbox ? "https://api-3t.sandbox.paypal.com/2.0" : "https://api-3t.paypal.com/2.0";
    }

Change GetEndPoint() :

    /// <summary>
    /// Returns the endpoint for the API call
    /// </summary>
    /// <returns></returns>
    public string GetEndPoint()
    {
        return this.endpoint;
    }

Add a corresponding member:

    /// <summary>
    /// Endpoint
    /// </summary>
    private string endpoint;

Changes to SimonsPayPalAPIInterfaceServiceService.cs

Modify the constructor to add a useSandbox parameter

public SimonsPayPalAPIInterfaceServiceService(bool useSandbox) 
{ 
    this.useSandbox = useSandbox; 
}

Add a corresponding member

    private bool useSandbox;

Do two search and replaces on this file. You'll have about 100 replacements for each

  • Replace new DefaultSOAPAPICallHandler( with new SimonsSOAPAPICallHandler(useSandbox,
  • Replace DefaultSOAPAPICallHandler defaultHandler with var defaultHandler

What you just did was added useSandbox as a parameter to the constructor of SimonsSOAPAPICallHandler (which thankfully implements IAPICallPreHandler ) and you'll end up with this for each method:

 public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(DoExpressCheckoutPaymentReq doExpressCheckoutPaymentReq, string apiUserName)
{
    IAPICallPreHandler apiCallPreHandler = null;
    string portName = "PayPalAPIAA";
    setStandardParams(doExpressCheckoutPaymentReq.DoExpressCheckoutPaymentRequest);
    var defaultHandler = new SimonsSOAPAPICallHandler(useSandbox, doExpressCheckoutPaymentReq.ToXMLString(null, "DoExpressCheckoutPaymentReq"), null, null);
    apiCallPreHandler = new MerchantAPICallPreHandler(defaultHandler, apiUserName, getAccessToken(), getAccessTokenSecret());
    ((MerchantAPICallPreHandler) apiCallPreHandler).SDKName = SDKName;
    ((MerchantAPICallPreHandler) apiCallPreHandler).SDKVersion = SDKVersion;
    ((MerchantAPICallPreHandler) apiCallPreHandler).PortName = portName;
    string response = Call(apiCallPreHandler);
    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.LoadXml(response);
    XmlNode xmlNode = xmlDocument.SelectSingleNode("*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='DoExpressCheckoutPaymentResponse']");
    return new DoExpressCheckoutPaymentResponseType(xmlNode);

}

That's it!

Now when you call a method you can say ...

bool useSandbox = true; // or false
var service = new SimonsPayPalAPIInterfaceServiceService(useSandbox);

Then call the method as normal

caller.DoExpressCheckoutPayment(pp_request, config.AccountName);

Note: It will still look for the account name in your config to find the corresponding keys. Obviously be careful updating to a later version of the Merchant SDK cos you'll have to do this all over again .

I hope someone finds this useful :-)

It's entirely doable, but you have to hard-code the values by creating the bindings as objects. This is what I've worked out for my own project:

protected static PayPalAPIInterface GetService()
{
    return new PayPalAPIInterfaceClient(new BasicHttpBinding()
            {
                SendTimeout = new TimeSpan(0, 5, 0),
                MaxReceivedMessageSize = 200000,
                Security = new BasicHttpSecurity()
                {
                    Mode = BasicHttpSecurityMode.Transport,
                    Transport = new HttpTransportSecurity()
                                    {
                                        ClientCredentialType = HttpClientCredentialType.None,
                                        ProxyCredentialType = HttpProxyCredentialType.None,
                                    },
                    Message = new BasicHttpMessageSecurity()
                                {
                                    ClientCredentialType = BasicHttpMessageCredentialType.Certificate,
                                }
                }
            },
            new EndpointAddress(@"https://api-3t.paypal.com/2.0/")
        ).ChannelFactory.CreateChannel();
}

There's more parameters you can set - in theory, everything in the .config file can be reproduced here. This works for me, though, so I didn't take it any further.

It's also worth noting that this enables you to put the PayPal calls into a library and not have to copy the binding into the config file of the project that includes that library, which was why I developed it in the first place.


Edit: Here's the basic definition of PayPalAPIInterfaceClient - no guarantees that it will actually be sufficient for use.

public partial class PayPalAPIInterfaceClient : System.ServiceModel.ClientBase<PayPalAPIInterfaceServiceService>
{
    public PayPalAPIInterfaceClient(System.ServiceModel.Channels.Binding binding,
                                    System.ServiceModel.EndpointAddress remoteAddress) 
           : base(binding, remoteAddress) { }
}

You'd also modify the earlier code to have a return type of PayPalAPIInterfaceServiceService instead.

Answer 1: I agree - not possible out of the box.

It really doesn't seem to be possible without compiling the code yourself and changing quite a lot. Here's the current code from GitHub for PayPalAPIInterfaceServiceService which is actually generated - probably using a T4 template. This is a massive 2489 line file with code like this for every API method.

The key thing here is IAPICallPreHandler . You'll see it's set to null and then initialized to an instance of MerchantAPICallPreHandler . There's no way to pass it in.

    public SetExpressCheckoutResponseType SetExpressCheckout(SetExpressCheckoutReq setExpressCheckoutReq, ICredential credential)
    {
        IAPICallPreHandler apiCallPreHandler = null;
        string portName = "PayPalAPIAA";
        setStandardParams(setExpressCheckoutReq.SetExpressCheckoutRequest);
        DefaultSOAPAPICallHandler defaultHandler = new DefaultSOAPAPICallHandler(setExpressCheckoutReq.ToXMLString(null, "SetExpressCheckoutReq"), null, null);
        apiCallPreHandler = new MerchantAPICallPreHandler(defaultHandler, credential);
        ((MerchantAPICallPreHandler) apiCallPreHandler).SDKName = SDKName;
        ((MerchantAPICallPreHandler) apiCallPreHandler).SDKVersion = SDKVersion;
        ((MerchantAPICallPreHandler) apiCallPreHandler).PortName = portName;
        string response = Call(apiCallPreHandler);
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.LoadXml(response);
        XmlNode xmlNode = xmlDocument.SelectSingleNode("*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='SetExpressCheckoutResponse']");
        return new SetExpressCheckoutResponseType(xmlNode);

    }

Now let's look at the interface :

public interface IAPICallPreHandler
{
    ICredential GetCredential();
    string GetEndPoint();
    Dictionary<string, string> GetHeaderMap();
    string GetPayLoad();
}

Doh! Doesn't GetEndPoint() look kinda like what we'd want to override.

Delving deeper into the code - the instance of GetEndPoint() that is ultimately called is this one in DefaultSOAPAPICallHandler - which as you can see goes straight to the ConfigManager .

    public string GetEndPoint() 
    {
        return ConfigManager.Instance.GetProperty(BaseConstants.END_POINT);
}

ConfigManager just goes straight to web.config or app.config

  config = (SDKConfigHandler)ConfigurationManager.GetSection("paypal");

There's no hooks or anything else to change the endpoint unfortunately.

I've considered compiling it locally and fixing it, but I'd rather lose the ability to change the endpoint in code than lose the ability to one-click update the whole package.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM