1
votes

Here's what I'm designing a site where multiple shops my be able to offer their producs to sale. Every seller will have a virtual store on my site. I'm using paypal for purchase operations. I've considered to allow customers to use credit card without having a paypal account, and I'm trying to use Adaptive payments flow to allow "buy as guest" flow. I'm trying to use paypal default flow (not the rest api) since I don't want to be worried about handling credit card data and having to design my site as PCI compliant.

So with this escenario here's what I'm using:

Setting Up Web Pages to Invoke the Embedded Payment Flow Using a Lightbox

  • Since this payment flow requires a pay key to be generated, I'm using the code found on this link:

https://github.com/paypal/rest-api-sdk-dotnet/tree/master/Samples/RestApiSample

-So on my MVC I have a page that generates the order, and it calls a Helper methods to get the paykey. Here's the most relevant one:

public static string GetPayKey(DummyPurchase purchase)
    {
        ReceiverList receiverList = new ReceiverList();
        receiverList.receiver = new List<Receiver>();
        //(Required) Amount to be paid to the receiver
        string[] amt = new string[1] { purchase.TotalPrice.ToString() };
        // Receiver's email address. This address can be unregistered with paypal.com.
        // If so, a receiver cannot claim the payment until a PayPal account is linked
        // to the email address. The PayRequest must pass either an email address or a phone number. 
        // Maximum length: 127 characters 
        string[] receiverEmail = new string[1] { purchase.StoreId.ToString() };
        string cancelUrl = ConfigurationHelper<string>.GetKeyValue(Constants.PAYPAL_CANCEL_URL);
        string returnUrl = ConfigurationHelper<string>.GetKeyValue(Constants.PAYPAL_RETURN_URL);
        string currency = ConfigurationHelper<string>.GetKeyValue(Constants.PAYPAL_CURRENCY_CODE);

        //Generate Receivers list
        for (int i = 0; i < amt.Length; i++)
        {
            Receiver rec = new Receiver(Convert.ToDecimal(amt[i]));
            if (receiverEmail[i] != string.Empty)
            {
                rec.email = receiverEmail[i];
            }

            receiverList.receiver.Add(rec);
        }

        PayRequest request = new PayRequest(new RequestEnvelope("en_US"), "PAY",
                            cancelUrl, currency,
                            receiverList, returnUrl);




        //call the service
        AdaptivePaymentsService service = null;
        PayResponse response = null;
        try
        {
            // (https://github.com/paypal/sdk-core-dotnet/wiki/SDK-Configuration-Parameters)
            Dictionary<string, string> configurationMap = GetAcctAndConfig();

            // Creating service wrapper object to make an API call and loading
            // configuration map for your credentials and endpoint
            service = new AdaptivePaymentsService(configurationMap);

            response = service.Pay(request);
        }
        catch (Exception ex)
        {
            Elmah.ErrorLog.GetDefault(null).Log(new Elmah.Error(ex));
            return "";
        }

        Dictionary<string, string> responseValues = new Dictionary<string, string>();
        string redirectUrl = null;
        if (!(response.responseEnvelope.ack == AckCode.FAILURE) &&
            !(response.responseEnvelope.ack == AckCode.FAILUREWITHWARNING))
        {

            return response.payKey;
        }
        return "";
    }

-After I get this key, I get the html from another view that has the form as the API guide specifies, having the paykey string as the model for this view.

@model string
<h2>ConfirmCheckout</h2>
<script src="https://www.paypalobjects.com/js/external/dg.js">
</script>
<form action="https://www.paypal.com/webapps/adaptivepayment/flow/pay"
target="PPDGFrame">
<input id="type" type="hidden" name="expType" value="light">
<input id="paykey" type="hidden" name="paykey" value="@Model">
<input type="submit" id="submitBtn" value="Pay with PayPal">
</form>

-After the view is rendered, I call the javascript code to start the flow:

var dgFlow = new PAYPAL.apps.DGFlow({ trigger: 'submitBtn' });

-The flow works perfectly and I get a valid pay key rendered on this form. But when I click this button (submit button on form with paykey) I get 2 different errors. This is the most frequent one:

This transaction has already been approved. Please visit your PayPal Account Overview to see the details.

-But sometimes I get a "Your payment session has expired" error.

I have 2 questions:

  • Does someone know how to fix those errors?
  • I'm using clasic API since guest payment flow for adaptive payments require a PayKey to start the flow (in order to avoid security and PCI complience matters). I did not found a method on the Paypal REST API that could get the same PayKey. Is there any method to get those keys?

Thanks a lot

2
You can check the example here: highlightsolutions.com/shoppingcart The first option of the Store dropdown is the one that is working right now to generate the pay key.David Espino

2 Answers

3
votes

Well this is really embarrasing... but the real issue was the url on the post action of the form. I had

<form action="https://www.paypal.com/webapps/adaptivepayment/flow/pay" target="PPDGFrame">

Which is production link. And I'm not going live yet, I'm using paypal api credentials for a sanbox account, so the real form action should be:

<form action="https://www.sandbox.paypal.com/webapps/adaptivepayment/flow/pay" target="PPDGFrame">  

Duh!. Well hope that this could help another person with same kind of errors.

Thanks a lot @Andrew Angell

0
votes

It sounds like you're sending a Preapproval key with your Pay request. When you do that, there's no redirect to PayPal required. That's the whole point...you've already got approval via the Preapproval Key, so when you submit a Pay request with a preapproval key included the payment will be made immediatly (as long as the preapproval profile is still valid.)

As such, when you're doing the redirect, it's telling you exactly what happened...the transaction was already processed. Again, no need for a redirect.

The whole point is to be able to trigger payments using the preapproval profile within your application at any time you need to with any redirect or further approval. Once you have that preapproval key you can trigger payments any way you want to. For example, you might want to charge somebody per use and trigger a payment when they log in to your website, but you don't want them to have to approve the payment every time. The payment could happen in the background via the Pay API with the preapproval key included but the user would not be interrupted at all.

Hope that helps.