Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payment.Create not working #105

Closed
Zartexx opened this issue Jun 11, 2015 · 25 comments
Closed

Payment.Create not working #105

Zartexx opened this issue Jun 11, 2015 · 25 comments
Labels

Comments

@Zartexx
Copy link

Zartexx commented Jun 11, 2015

I am attempting implementation of the REST API.
but can't get past the line:
Payment ZeroChargedPayment = pymnt.Create(PAC)
all it tells me is UNKNOWN_ERROR
paypalerror2

some debug_ids
9916dd16fade6
ebca6a2b07079

I also tried with the Details() in the Amount() and adding the ItemList() to the Transaction()
with the same result.

the json looks like this

{
  "intent":"sale",
  "payer":{
    "payment_method":"credit_card",
    "funding_instruments":[
      {
        "credit_card_token":{
          "credit_card_id":"CARD-9V730261823609037KV462DY",
          "expire_month":11,
          "expire_year":2018
        }
      }
    ]
  },
  "transactions":[
    {
      "amount":{
        "currency":"USD",
        "total":"1.00"
      },
      "description":"Validating a Credit Card"
    }
  ]
}

the account has direct enable for sandbox
paypalerror4

so I don't know what is wrong, I am using the fake credit card from the sample C# REST API

@jziaja
Copy link
Contributor

jziaja commented Jun 11, 2015

@jaypatel512
Copy link
Contributor

Just to confirm, are you trying to create payment using credit card, or vault ?

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

The plan is:
User enters CC info on our site, I vault it and store the Vault # in our database.
then when the "Event" occurs, we will charge their CC by using the Vault id.
(the "Event" can be hours later or weeks later)

@jziaja
Copy link
Contributor

jziaja commented Jun 11, 2015

@Zartexx can you try using the sandbox credentials used by the .NET SDK sample project in your project and see if it's able to create the payment successfully? That will help to rule out whether or not it's an issue with your account. Thanks!

@jaypatel512
Copy link
Contributor

Make sure to create a new Vault Token, as vaults are specific for each merchant (clientId).

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

I haven't been saving the vault tokens, If the CC Validation fails, no save occurs.
ok, changed ClientID and Secret to sample, but only got back:
The remote server returned an error: (401) Unauthorized.

@jziaja
Copy link
Contributor

jziaja commented Jun 11, 2015

Are you authenticating and vaulting using those sample project credentials prior to making the Payment.Create() call? If so, can you share the code you're calling?

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

"PAC" is the local instance of the internal static class Configuration (copied from sample app)

First, when user is on web site we add the CC to the Vault:

        /// <summary>Add a Credit Card to the Paypal vault and Return the MemberCreditCard record</summary>
        /// <param name="member">Member Record</param>
        /// <param name="NewCard">New Credit card to Vault then Verify if bad then Delete, good then Insert into our db</param>
        /// <param name="CCType">Credit card type. Valid types are: visa, mastercard, discover, amex</param>
        /// <param name="CCCVV2">3-4 digit card validation code. Passed thru, never save this!</param>
        /// <param name="BillingAddress">Billing address associated with card.</param>
        public static MemberCreditCard AddVaultedCreditCardForMember(int MemberId, MemberCreditCard NewCard, int CCCVV2)
        {
            if (MemberId == 0) { throw new Exception("Member not specified"); }
            if (String.IsNullOrWhiteSpace(NewCard.BillingLine1) || String.IsNullOrWhiteSpace(NewCard.BillingPostalCode)) { throw new Exception("Billing Address not specified"); }
            MemberCreditCard MembersCard = new MemberCreditCard();

            //TODO: Address this when we get clients outside the US: (country_code = "US")

            CreditCard card = new CreditCard()
            {
                billing_address = new Address()
                {
                    city = NewCard.BillingCity,
                    country_code = "US",
                    line1 = NewCard.BillingLine1,
                    postal_code = NewCard.BillingPostalCode,
                    state = NewCard.BillingState
                },
                expire_month = NewCard.CardMonth,
                expire_year = NewCard.CardYear,
                number = NewCard.CardNumber,
                type = NewCard.CardType,
                cvv2 = CCCVV2.ToString()
            };

            CreditCard NewCC = card.Create(PAC);
            MembersCard.CardMonth = NewCC.expire_month;
            MembersCard.CardNumber = NewCC.number; // we will only save the last 4
            MembersCard.CardType = NewCC.type;
            MembersCard.CardYear = NewCC.expire_year;
            MembersCard.CreatedOn = Strings.SafeDate(NewCC.create_time);
            MembersCard.IsVerified = (NewCC.state.ToLower() == "ok"); // expired or ok
            MembersCard.MemberId = MemberId;
            MembersCard.PayPalVaultId = NewCC.id;
            MembersCard.UpdatedOn = Strings.SafeDate(NewCC.update_time);

            // Cards is now Vaulted

            //Lets Verify it
            if (ValidateMembersCreditCard(MembersCard) == false)
            {
                // Throw Error
                DeleteVaultedCreditCard(MembersCard);
            }

            return MembersCard;
        }

The Validate method:

        private static bool ValidateMembersCreditCard(MemberCreditCard MembersCard)
        {
            //          ____   _       ______  _____    ____   ______  ______ 
            // /\  /\  / __ \ | |     |__  __||  __ \  / __ \ |__  __||  ____|
            /// /  \ || |  | || |        ||   | |  | || |  | |   ||   | |____ 
            //| \  / || |__| || |        ||   | |  | || |__| |   ||   |  ____|
            // \ \/ / |  __  || |____  __||__ | |__| ||  __  |   ||   | |____ 
            //  \__/  |_|  |_||______||______||_____/ |_|  |_|   ||   |______|

            decimal AmountToChargeInDollars = 1M;
            string ItemName = "Validation";
            string FullDescription = "Validating a Credit Card";
            Payment pymnt = new Payment()
            {
                intent = "sale",
                payer = new Payer()
                {
                    funding_instruments = new List<FundingInstrument>() { GetFundingInstrument(MembersCard) },
                    payment_method = "credit_card"
                },
                transactions = new List<Transaction>() { GetSaleTransaction(MembersCard, AmountToChargeInDollars, ItemName, FullDescription) }
            };
            bool IsAuthorized = false;
            try
            {
                // Create a payment using a valid APIContext
                Payment ZeroChargedPayment = pymnt.Create(PAC); 
                string authorizationId = ZeroChargedPayment.transactions[0].related_resources[0].authorization.id;
                Authorization AuthStatus = Authorization.Get(PAC, authorizationId);
                IsAuthorized = (AuthStatus.state == AuthorizationState.Authorized.ToDescription()); // "authorized"

                //AuthStatus.Void(PAC); //DRY: PayPal Question: IDK if i'm supposed to do this?
            }
            catch (PayPal.PaymentsException ex)
            {
                string errDetails = "";
                if (ex.Details != null) { errDetails = ex.Details.debug_id.ToString() + " " + ex.Details.name + ' ' + ex.Details.message + Environment.NewLine + ("" + ex.Details.information_link) + Environment.NewLine; }
                if (ex.Details != null && ex.Details.details != null) { ex.Details.details.ForEach(d => errDetails += d.code + " " + d.field + " " + d.issue + Environment.NewLine); }
                errDetails += pymnt.ConvertToJson() + Environment.NewLine;
                System.Diagnostics.Debug.WriteLine("ERROR: " + ex.Message + Environment.NewLine + errDetails);
                throw new Exception("ERROR: " + ex.Message + Environment.NewLine + errDetails,ex);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("ERROR: " + ex.Message);
                throw;
            }
            return IsAuthorized;

        } // End Function ValidateMembersCreditCard

        private static FundingInstrument GetFundingInstrument(MemberCreditCard MembersCard)
        {
            // A resource representing a credit card that can be used to fund a payment.
            FundingInstrument FI = new FundingInstrument() { credit_card_token = GetCreditCardToken(MembersCard) };
            return FI;
        }
        private static CreditCardToken GetCreditCardToken(MemberCreditCard MembersCard)
        {
            // A resource representing a credit card that can be used to fund a payment.
            CreditCardToken JRRToken = new CreditCardToken() { credit_card_id = MembersCard.PayPalVaultId, expire_month = MembersCard.CardMonth, expire_year = MembersCard.CardYear };
            return JRRToken;
        }
        private static Transaction GetSaleTransaction(MemberCreditCard MembersCard, decimal AmountToChargeInDollars, string ItemName, string FullDescription)
        {
            string strSku = Guid.NewGuid().ToString(); // Just to look official?
            string strCost = Strings.FormatNumber(AmountToChargeInDollars, 2).Replace(",", "");
            Amount SaleCost = new Amount()
            {
                currency = "USD",
                total = strCost
                //,details = new Details() { subtotal = strCost }
            };
            // Items within a transaction.
            var ThingWeSold = new Item() { name = ItemName, price = strCost, currency = "USD", quantity = "1", sku = strSku };

            // A transaction defines the contract of a payment - what is the payment for and who is fulfilling it. 
            Transaction SaleTransaction = new Transaction()
            {
                amount = SaleCost,
                description = FullDescription
                //,item_list = new ItemList() { items = new List<Item>() { ThingWeSold } }
            };

            return SaleTransaction;
        }

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

Then, later when the event occurs:

        /// <summary>Be Carefull with this method, as it !!WILL CHARGE A CREDIT CARD!!</summary>
        private static Payment ChargeMembersCreditCard(MemberCreditCard MembersCard, decimal AmountToChargeInDollars, string ItemName, string FullDescription)
        {
            //  ____   _    _   ____   _____    ____   ______ 
            // / ___\ | |  | | / __ \ |  __ \  / ___| |  ____|
            //| |     | |__| || |  | || |__| )| |     | |____ 
            //| |     |  __  || |__| ||    _/ | | ____|  ____|
            //| |____ | |  | ||  __  || |\ \  | |__| || |____ 
            // \____/ |_|  |_||_|  |_||_| \_)  \____/ |______|
            Payment pymnt = new Payment()
            {
                intent = "sale",
                payer = new Payer()
                {
                    funding_instruments = new List<FundingInstrument>() { GetFundingInstrument(MembersCard) },
                    payment_method = "credit_card"
                },
                transactions = new List<Transaction>() { GetSaleTransaction(MembersCard, AmountToChargeInDollars, ItemName, FullDescription) }
            };

            // Create a payment using a valid APIContext
            Payment ChargedPayment = pymnt.Create(PAC); 

            Amount PaymentAmount = ChargedPayment.transactions[0].amount;
            Capture PaymentCapture = new Capture() { amount = PaymentAmount, is_final_capture = true };
            Authorization PaymentAuthorization = ChargedPayment.transactions[0].related_resources[0].authorization;

            Capture ActualPayment = PaymentAuthorization.Capture(PAC, PaymentCapture);
            return ChargedPayment;

        } // End Function ChargeMembersCreditCard

@jziaja
Copy link
Contributor

jziaja commented Jun 11, 2015

Can you share how PAC is being setup?

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

PAC:

internal static class Configuration
    {
        public readonly static string ClientId;
        public readonly static string ClientSecret;
        private readonly static string PayPalSandBoxCID = "<==redacted==>";
        private readonly static string PayPalSandBoxSEC = "<==redacted==>";
        private readonly static string PayPalLiveCID = "<==redacted==>";
        private readonly static string PayPalLiveSEC = "<==redacted==>";
        private readonly static string ConfigSetting_Timeout = "360000";
        private readonly static string ConfigSetting_RetryCount = "1";

        #region "    Implementation    "

        // Static constructor for setting the readonly static members.
        static Configuration()
        {
            Dictionary<string, string> config = GetConfig(); // Hash Table
            ClientId = config[PayPal.Api.BaseConstants.ClientId];
            ClientSecret = config[PayPal.Api.BaseConstants.ClientSecret];
        }

        // Create the configuration map that contains mode and other optional configuration details.
        public static Dictionary<string, string> GetConfig()
        {

            #region "    Normally in Web.Config    "
            /*
              <!-- PayPal SDK settings -->
              <paypal>
                <settings>
                  <add name="mode" value="sandbox"/>
                  <add name="connectionTimeout" value="360000"/>
                  <add name="requestRetries" value="1"/>
                  <add name="clientId" value="<==redacted==>"/>
                  <add name="clientSecret" value="<==redacted==>"/>
                </settings>
              </paypal>             
            */
            #endregion


            Dictionary<string, string> ManualOverRide = new Dictionary<string, string>();
            ManualOverRide.Add(PayPal.Api.BaseConstants.HttpConnectionTimeoutConfig, ConfigSetting_Timeout);
            ManualOverRide.Add(PayPal.Api.BaseConstants.HttpConnectionRetryConfig, ConfigSetting_RetryCount);
            bool ynDebug = true;
            if (ynDebug)
            { // DEVELOPMENT
                ManualOverRide.Add(PayPal.Api.BaseConstants.ApplicationModeConfig, PayPal.Api.BaseConstants.SandboxMode);
                ManualOverRide.Add(PayPal.Api.BaseConstants.EndpointConfig, PayPal.Api.BaseConstants.RESTSandboxEndpoint);
                ManualOverRide.Add(PayPal.Api.BaseConstants.ClientId, PayPalSandBoxCID);
                ManualOverRide.Add(PayPal.Api.BaseConstants.ClientSecret, PayPalSandBoxSEC);
            }
            else
            { // LIVE
                ManualOverRide.Add(PayPal.Api.BaseConstants.ApplicationModeConfig, PayPal.Api.BaseConstants.LiveMode); 
                ManualOverRide.Add(PayPal.Api.BaseConstants.EndpointConfig, PayPal.Api.BaseConstants.RESTLiveEndpoint);
                ManualOverRide.Add(PayPal.Api.BaseConstants.ClientId, PayPalLiveCID);
                ManualOverRide.Add(PayPal.Api.BaseConstants.ClientSecret, PayPalLiveSEC);
            }

            return ManualOverRide;
            //return ConfigManager.Instance.GetProperties();
        }

        // Create accessToken
        private static string GetAccessToken()
        {
            // ###AccessToken
            // Retrieve the access token from OAuthTokenCredential by passing in ClientID and ClientSecret
            // It is not mandatory to generate Access Token on a per call basis.
            // Typically the access token can be generated once and reused within the expiry window                
            string accessToken = new OAuthTokenCredential( ClientId, ClientSecret, GetConfig()).GetAccessToken();
            return accessToken;
        }

        // Returns APIContext object
        public static APIContext GetAPIContext(string accessToken = "")
        {
            // ### Api Context
            // Pass in a `APIContext` object to authenticate the call and to send a unique request id 
            // (that ensures idempotency). The SDK generates a request id if you do not pass one explicitly. 
            APIContext apiContext = new APIContext(string.IsNullOrEmpty(accessToken) ? GetAccessToken() : accessToken);
            apiContext.Config = GetConfig();
            return apiContext;
        }
        #endregion
    }

@jziaja
Copy link
Contributor

jziaja commented Jun 11, 2015

Thanks for sharing your code @Zartexx. I was able to successfully authenticate, vault, and create a payment using your code with the sample sandbox credentials. To do so, I set the following in the Configuration class:

private readonly static string PayPalSandBoxCID = "AUASNhD7YM7dc5Wmc5YE9pEsC0o4eVOyYWO9ezXWBu2XTc63d3Au_s9c-v-U";
private readonly static string PayPalSandBoxSEC = "EBq0TRAE-4R9kgCDKzVh09sm1TeNcuY-xJirid7LNtheUh5t5vlOhR0XSHt3";

I am also setting PAC by doing the following:

static APIContext PAC = Configuration.GetAPIContext();

Can you confirm if that's what you're doing in your code?

Regarding your question from the code about calling Authorization.Void(), because you're making a sale payment, the returned payment resource will not contain an authorization related resource; only a sale related resource. The authorization related resource is only created when you create a payment with intent set to order. If the sale completes successfully, its state will be completed.

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

Yes on the PAC:

        /// <summary>Optional Override Paypal Api Context</summary>
        public static PayPal.Api.APIContext ConnectionOverride;

        /// <summary>Paypal Api Context</summary>
        private static PayPal.Api.APIContext PAC
        {
            [System.Diagnostics.DebuggerStepThrough()]
            get
            {
                if (ConnectionOverride == null)
                    return Configuration.GetAPIContext();
                else
                    return ConnectionOverride;
            }
        }

About the validate, if I change intent to order will I need to void it after?
is the create payment correct then? It got confusing how to complete a charge.

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

Tried the Order (debud_id 642be276f4cdb )
it looks like it wants me to use "authorize"?

642be276f4cdb VALIDATION_ERROR Invalid request - see details
intent For DCC transactions, allowed payment intent is Sale or Authorize

{"intent":"order","payer":{"payment_method":"credit_card","funding_instruments":[{"credit_card_token":{"credit_card_id":"CARD-1NX46819DV6935036KV5A5NQ","expire_month":11,"expire_year":2018}}]},"transactions":[{"amount":{"currency":"USD","total":"1.00"},"description":"Validating a Credit Card"}]}

tried authorize ("9468286e9e803") got unknown error
tried authorize on above client/secret and got authorized back
paypalerror7

So, I guess that does prove something is not right with our account.

@Zartexx
Copy link
Author

Zartexx commented Jun 11, 2015

I just noticed the ClientId you posted:
AUASNhD7YM7dc5Wmc5YE9pEsC0o4eVOyYWO9ezXWBu2XTc63d3Au_s9c-v-U

sample:
AQKvrsvZmSlgnMvpb6wTGaL3pgvF-AWcfSwZsFNMT67ynr-44BnbJY6E9_xpff3eukRwSdK1PoVOpUBD

is different from the one i pulled from PayPal Sample, am I grabbing the wrong id? or are they variable in length?

the only success, so far, has been with the 60 character CID/SEC you provided, all of the CID/SEC's I have are 80 characters in length, and none of them are working. (or is this a red herring?)

@jziaja
Copy link
Contributor

jziaja commented Jun 12, 2015

The current sample project credentials were created sometime last year when PayPal was generating 60-character credentials for REST apps. I believe current REST apps have 80-character credentials. However, both sets of credentials should work. Do you mind sharing where you're getting the 80-character sample credentials from? I just want to check and make sure there aren't out-of-date credentials floating around somewhere (it could be we deleted the associated REST app when we were cleaning up some of our dev accounts).

Also, the flow I should've pointed you towards was the authorization flow - not the order flow, which is designed for payment_method="paypal"-only payments. The flow is explained on PayPal Developer, and it boils down to the following:

  1. Create a payment authorization (intent = "authorize") using the vaulted card as the funding instrument
  2. The response will then contain the authorization related resource your code is expecting, where you can verify its state is authorized
  3. Once you're ready to capture the payment, simply call Authorization.Capture(...) using the authorization resource

The sample project includes an example for creating an authorization and then capturing the payment (I'd recommend running the sample project so you can see the steps involved in the flow a bit better, along with what the request & response looks like at each point). The sample is currently setup to use a new credit card, but can be easily updated to use a vaulted card instead:

// First vault a credit card.
var card = new CreditCard
{
    expire_month = 11,
    expire_year = 2018,
    number = "4877274905927862",
    type = "visa",
    cvv2 = "874"
};
var createdCard = card.Create(apiContext);

// Next, create the payment authorization using the vaulted card as the funding instrument.
var payment = new Payment
{
    intent = "authorize",
    payer = new Payer
    {
        payment_method = "credit_card",
        funding_instruments = new List<FundingInstrument>
        {
            new FundingInstrument
            {
                credit_card_token = new CreditCardToken
                {
                    credit_card_id = createdCard.id,
                    expire_month = createdCard.expire_month,
                    expire_year = createdCard.expire_year
                }
            }
        }
    },
    transactions = new List<Transaction>
    {
        new Transaction
        {
            amount = new Amount
            {
                currency = "USD",
                total = "1.00"
            },
            description = "This is the payment transaction description."
        }
    }
};
var createdPayment = payment.Create(apiContext);

// Get the authorization resource.
var authorization = createdPayment.transactions[0].related_resources[0].authorization;

// Check and make sure the card has been authorized for the payment.
if(authorization.state != "authorized")
{
    return;
}

// Specify an amount to capture.  By setting 'is_final_capture' to true, all remaining funds held by the authorization will be released from the funding instrument.
var capture = new Capture
{
    amount = new Amount
    {
        currency = "USD",
        total = "1.00"
    },
    is_final_capture = true
};

// Capture the payment.
var responseCapture = authorization.Capture(apiContext, capture);

@Zartexx
Copy link
Author

Zartexx commented Jun 12, 2015

I got the ID's from this page:
paypalerror9

and the sample ID's from the web.config in the project: PayPal.SDK.Sample.VS.2013.sln

So when the "Event" occurs and a charge is made, I need to authorize it first?

@jziaja
Copy link
Contributor

jziaja commented Jun 12, 2015

Yes, that's the correct page. Something else to try - if you're still getting the 400 error when making the payment, try creating a new REST app and see if the credentials from there work.

So when the "Event" occurs and a charge is made, I need to authorize it first?

By "Event", do you mean the user on your site trying to make a purchase? I was going by the code you've written where it looks like you're trying to verify the card is authorized to make a charge.

@Zartexx
Copy link
Author

Zartexx commented Jun 12, 2015

The business model is: someone creates an account, we vault their credit card, then weeks or months later when the business event occurs, they will be charged. They wont be present (on the site) at the time of the charge, which is why we needed direct payment.

We only authorized the payment upon vaulting to make sure the credit card is real and valid.
The charge method I provided, do I need to add authorization to it?

@jziaja
Copy link
Contributor

jziaja commented Jun 12, 2015

Your charge method shouldn't need authorization since it was already done when the card was vaulted. All you'll need to do for the charge is do a sale like you're doing with the vaulted card ID.

The one step missing from the code above is to void the authorization (by calling Authorization.Void(), which you have commented out in your code) after you've verified the card can be charged successfully.

@Zartexx
Copy link
Author

Zartexx commented Jun 17, 2015

Spoke with PayPal today, they fixed it, some sort of back end issue.

Thank you guys for all your assistance.

@jziaja
Copy link
Contributor

jziaja commented Jun 17, 2015

Glad to hear you got it fixed! 👍

@jziaja jziaja closed this as completed Jun 17, 2015
@jeffgrove
Copy link

I'm hitting the same issue, with basically identical code. I'm saving a card to the vault (client side) and then trying to use the card token on the server side to authorize. I'm not setting the token expire year and month as according to the docs it isn't required but it fails for me even when I do. I guess I'll contact support.

@jaypatel512
Copy link
Contributor

Hey @jeffgrove !

Please contact merchant support. It seems like few older accounts failed to migrate successfully in one of the migrations. They would be able to reset the configuration to enable you to use your app.

@jeffgrove
Copy link

@jaypatel512
Good suggestion, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants