I'm struggling to decide between Paypal NVP and REST APIs.
The REST API seems to be newer and better but I can't find a way to get customer details after a transaction occured.
Basically I want to set up a billing plan for recurring payments and then get customer details via API so they do not have to enter them through my website.
Paypal Express checkout seems to be what I should use here.
The NVP API offers a GetExpressCheckoutDetails method to get these details.
I didn't find something similar for the REST API.
Since the REST API seems to go through Express Checkout as well there should be a solution.
How can I get customer details after activating the billing agreement?
I ran into this same problem a month ago and after reading the docs, there's currently no way to accomplish this with the REST API. If there is a way, it's not documented.
The only way I found to do this is with the NVP API and possibly the SOAP API. The NVP API will give you back most of the fields that you want but if you've stored custom fields for a transaction, it will only give you 3 of the custom fields but not all of them (weird).
I haven't tried the NVP method GetExpressCheckoutDetails but I have used the GetTransactionDetails method. It will give you back the transaction details. It returns a text block that you must parse. Each field is URL encoded and separated by an ampersand. Here's an example in PHP:
<?php
// Get cURL resource
$ch = curl_init();
// Set url
curl_setopt($ch, CURLOPT_URL, 'https://api-3t.paypal.com/nvp/');
// Set method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
// Set options
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// Set headers
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/x-www-form-urlencoded; charset=utf-8",
]
);
// Create body
$body = [
"VERSION" => "204.0",
"METHOD" => "GetTransactionDetails",
"USER" => "nvp_api_username_here",
"PWD" => "nvp_api_password_here",
"SIGNATURE" => "nvp_api_signature_here",
"TRANSACTIONID" => "some_paypal_transaction_id_here",
];
$body = http_build_query($body);
// Set body
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
// Send the request and save response to $resp
$resp = curl_exec($ch);
if(!$resp) {
die('Error: "' . curl_error($ch) . '" - Code: ' . curl_errno($ch));
} else {
parse_str($resp, $formated_response);
print_r($formated_response);
}
// Close request to clear up some resources
curl_close($ch);
Here's the formatted return body:
Array
(
[RECEIVERBUSINESS] => paypal_account_owner_email_address_here#test.com
[RECEIVEREMAIL] => paypal_account_owner_email_address_here#test.com
[RECEIVERID] => 1111111111111
[EMAIL] => buyers_email_address_here#test.com
[PAYERID] => 55551
[PAYERSTATUS] => verified
[COUNTRYCODE] => US
[BUSINESS] => buyers_business_name_here
[ADDRESSOWNER] => PayPal
[ADDRESSSTATUS] => None
[SALESTAX] => 0.00
[SHIPAMOUNT] => 0.00
[SHIPHANDLEAMOUNT] => 0.00
[SHIPDISCOUNT] => 0.00
[INSURANCEAMOUNT] => 0.00
[GIFTRECEIPT] => 0
[TIMESTAMP] => 2016-08-02T17:04:58Z
[CORRELATIONID] => 55552
[ACK] => Success
[VERSION] => 204.0
[BUILD] => 22386173
[FIRSTNAME] => Foo
[LASTNAME] => Bar
[TRANSACTIONID] => 55553
[TRANSACTIONTYPE] => webaccept
[PAYMENTTYPE] => instant
[ORDERTIME] => 2016-08-01T20:49:28Z
[AMT] => 1.00
[TAXAMT] => 0.00
[CURRENCYCODE] => USD
[PAYMENTSTATUS] => Completed
[PENDINGREASON] => None
[REASONCODE] => None
[SHIPPINGMETHOD] => Default
[PROTECTIONELIGIBILITY] => Ineligible
[PROTECTIONELIGIBILITYTYPE] => None
[L_QTY0] => 0
[L_TAXAMT0] => 0.00
[L_SHIPPINGAMT0] => 0.00
[L_HANDLINGAMT0] => 0.00
[L_CURRENCYCODE0] => USD
[L_OPTIONSNAME0] => first_custom_field_label_here
[L_OPTIONSVALUE0] => first_custom_field_value_here
[L_OPTIONSNAME1] => second_custom_field_label_here
[L_OPTIONSVALUE1] => second_custom_field_value_here
[L_OPTIONS1NAME0] => second_custom_field_label_here_duplicate
[L_OPTIONS1VALUE0] => second_custom_field_value_here_duplicate
[L_TAXABLE0] => false
[L_TAXRATE0] => 0.0
[L_AMT0] => 1.00
[INSURANCEOPTIONSELECTED] => 0
[SHIPPINGOPTIONISDEFAULT] => 0
)
Warning:
This only applies if you're storing custom fields in a PayPal transaction.
PayPal's NVP API only returns up to 3 custom fields for a transaction, even though you can store up to 7 custom fields in a transaction. Even crazier, one of the custom fields it returns is a duplicate of the second custom field. At least that's the case when I try to retrieve a custom fields from a transaction.
Related
I am using the Paypal API and I want to create a recurring payment, the first amount should be charged immediately. I need to know if it was successfully to unlock the content for the user. How in the world can I achieve that?
$fields = array(
'USER' => urlencode('email'),
'PWD' => urlencode('pass'),
'SIGNATURE' => urlencode('signature'),
'METHOD' => urlencode('GetExpressCheckoutDetails'),
'VERSION' => urlencode('86'),
'TOKEN' => urlencode($_GET['token'])
);
//url-ify the data for the POST
foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
rtrim($fields_string, '&');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"https://api-3t.sandbox.paypal.com/nvp");
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS,$fields_string); //Post Fields
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec ($ch);
curl_close ($ch);
// Get PAYERID of 1. query
$payerid = parseNVP($server_output);
$payerid = $payerid['PAYERID'];
$fields2 = array(
'USER' => urlencode('Email'),
'PWD' => urlencode('Pass'),
'SIGNATURE' => urlencode('Signature'),
'METHOD' => urlencode('CreateRecurringPaymentsProfile'),
'VERSION' => urlencode('86'),
'TOKEN' => urlencode($_GET['token']),
'PAYERID' => urlencode($payerid),
'PROFILESTARTDATE' => urlencode(date('Y-m-d').'T'.date('H:i:s', strtotime('-5 minutes')).'Z'),
'DESC' => urlencode('FitnessMembership'),
'BILLINGPERIOD' => urlencode('Day'),
'BILLINGFREQUENCY' => urlencode('1'),
'AMT' => urlencode($price),
'CURRENCYCODE' => urlencode('USD'),
'COUNTRYCODE' => urlencode('US'),
'MAXFAILEDPAYMENTS' => urlencode('3')
);
Paypal Result:
[2016-04-12 12:50 America/New_York] Verified IPN:cmd=_notify-validate&
payment_cycle=Daily&
txn_type=recurring_payment_profile_created&
last_name=Mustermann&
next_payment_date=03%3A00%3A00+Apr+12%2C+2016+PDT&
residence_country=US&
initial_payment_amount=0.00&
currency_code=USD&
time_created=09%3A49%3A25+Apr+12%2C+2016+PDT&
verify_sign=AJjtUEC2-zvkp.2Yz8a-.FGmZ2b3AoUov3DJ70yddjCmc1KidQVDeZHd&
period_type=+Regular&
payer_status=verified&
test_ipn=1&
tax=0.00&
payer_email=gut%40gut.de&
first_name=Hendrik&
receiver_email=hendrik.tewes-facilitator%40live.de&
payer_id=6AJ2HHWAPZSF6&
product_type=1&shipping=0.00&
amount_per_cycle=119.00&
profile_status=Active&
charset=windows-1252&
notify_version=3.8&
amount=119.00&
outstanding_balance=0.00&
recurring_payment_id=I-8P0534LSX2A3&
product_name=FitnessMembership&
ipn_track_id=59fede576b95c
You can charge an initial amount when creating a Recurring Payment Profile for Express Checkout.
Here is the documentation from the PayPal Developer Site
In the CreateRecurringPaymentsProfile API call, you will need to use the variable INITAMT
Here is more information from the PayPal Developer Site:
INITAMT
(Optional) Initial non-recurring payment amount due immediately upon
profile creation. Use an initial amount for enrolment or set-up fees.
Note: All amounts included in the request must have the same currency.
Character length and limitations:
Value is typically a positive number which cannot exceed 10,000.00 USD
or the per transaction limit for the currency. It includes no currency
symbol. Most currencies require two decimal places; the decimal
separator must be a period (.), and the optional thousands separator
must be a comma (,). Some currencies do not allow decimals. See the
currency codes page for details.
I have code GetRecurringPaymentsProfileDetails...where it wrong?
//--------------------------------------------------------------------------------------------------------------------------------------------------------
public function callNVP($profileId) {
$api_request = '&USER=' . urlencode('bestlifeXXXXX.gmaill.com')
. '&PWD=' . urlencode('136XXXXXXX')
. '&SIGNATURE=' . urlencode('XXXXXXXXJJJ4qi4-ASVptjmiE8Sqp4tXXXXXXCa')
. '&VERSION=76.0'
. '&METHOD =GetRecurringPaymentsProfileDetails'
. '&PROFILEID=' . urlencode($profileId);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api-3t.sandbox.paypal.com/nvp'); // For live transactions, change to 'https://api-3t.paypal.com/nvp'
curl_setopt($ch, CURLOPT_VERBOSE, 1);
//curl_setopt($ch, CURLOPT_HEADER, FALSE);
// Uncomment these to turn off server and peer verification
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
// Set the API parameters for this transaction
//curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $api_request);
// Request response from PayPal
$response = curl_exec($ch);
// If no response was received from PayPal there is no point parsing the response
if (!$response)
die('Calling PayPal to change_subscription_status failed: ' . curl_error($ch) . '(' . curl_errno($ch) . ')');
curl_close($ch);
// An associative array is more usable than a parameter string
parse_str($response, $parsed_response);
return $parsed_response;
}
But it's return result:
Array
(
[0] => Array
(
[TIMESTAMP] => 2015-12-10T03:49:09Z
[CORRELATIONID] => cb2489449e84c
[ACK] => Failure
[L_ERRORCODE0] => 10001
[L_SHORTMESSAGE0] => Internal Error
[L_LONGMESSAGE0] => Timeout processing request
)
)
What I want is:
(
[PROFILEID] => I-CL7Kxxx
[STATUS] => Active
[AUTOBILLOUTAMT] => AddToNextBilling
[DESC] => xxx
[MAXFAILEDPAYMENTS] => 2
[SUBSCRIBERNAME] => Dan
[PROFILESTARTDATE] => 2013-05-15T07:00:00Z
[PROFILEREFERENCE] => 31571
[NEXTBILLINGDATE] => 2013-05-16T10:00:00Z
[NUMCYCLESCOMPLETED] => 1
[NUMCYCLESREMAINING] => 18446744073709551615
[OUTSTANDINGBALANCE] => 0.00
[FAILEDPAYMENTCOUNT] => 0
[LASTPAYMENTDATE] => 2013-05-15T14:52:04Z
[LASTPAYMENTAMT] => 0.10
[TRIALAMTPAID] => 0.00
[REGULARAMTPAID] => 0.10
[AGGREGATEAMT] => 0.10
[AGGREGATEOPTIONALAMT] => 0.00
[FINALPAYMENTDUEDATE] => 1970-01-01T00:00:00Z
[TIMESTAMP] => 2013-05-15T14:55:58Z
[CORRELATIONID] => 225681xxx
[ACK] => Success
[VERSION] => 64
[BUILD] => 5908853
[SHIPTOSTREET] => xxx
[SHIPTOCITY] => xxx
[SHIPTOSTATE] => CA
[SHIPTOZIP] => xxx
[SHIPTOCOUNTRYCODE] => US
[SHIPTOCOUNTRY] => US
[SHIPTOCOUNTRYNAME] => United States
[SHIPADDRESSOWNER] => PayPal
[SHIPADDRESSSTATUS] => Unconfirmed
[BILLINGPERIOD] => Day
[BILLINGFREQUENCY] => 1
[TOTALBILLINGCYCLES] => 0
[CURRENCYCODE] => USD
[AMT] => 0.10
[SHIPPINGAMT] => 0.00
[TAXAMT] => 0.00
[REGULARBILLINGPERIOD] => Day
[REGULARBILLINGFREQUENCY] => 1
[REGULARTOTALBILLINGCYCLES] => 0
[REGULARCURRENCYCODE] => USD
[REGULARAMT] => 0.10
[REGULARSHIPPINGAMT] => 0.00
[REGULARTAXAMT] => 0.00
)
Who can help me? Please...
You've got this:
'&METHOD =GetRecurringPaymentsProfileDetails'
I think that space in there is causing an invalid request. Try this:
'&METHOD=GetRecurringPaymentsProfileDetails'
Its definitely because of your API request(I am guessing its Incorrect Profile ID)
Can you try(click) the following link in your browser(substitute API credentials,Profile ID)
https://api-3t.sandbox.paypal.com/nvp?&user=xxxxxxxxxx&pwd=xxxxxxxxxx&signature=xxxxxxxxxx&VERSION=109.0&METHOD=GetRecurringPaymentsProfileDetails&PROFILEID=I-XXXXXXXXXXXX
And see what response you get.
I get Internal Error, when I used "I-XXXXXXXXXXXX" as profile id.
Im currently using PayPals rest api. When executing payment it is returning as 'Pending' and the reason its stating is 'Multi Currency'.
The reason behind this is because the default 'facilitators' account is set to US i need it to all be in GB. The problem is I can login to user-facilitators#email.co.uk's account on sandbox.paypal.co.uk. I'm entering the email and password (which is 100% correct) and It wont let me login to sandbox so I can change the currency preferences back to GBP.
Does anybody no why?
In order to log into a Sandbox Paypal account, you have to be logged into your Paypal Developer Account
as the default 'facilitators' account is set to US and you want to change it to other country. but this edit is not possible in sandbox.
so an alternate solution is to create an another sandbox account and set it to business account. set your desired country and then currency will be your country currency, automatically.
after doing this, use this business account for testing instead of that default 'facilitators' account.
like this you can create any number of business accounts for any country/currency.
hope this will help to you and understood.
Yes, once we register on Paypal developer site, as default, Paypal will issue us two default test account, one is merchant account and another is user test account. All there are fine.
But the problem is their default currency is US, and We can't change them.
When your Magento store's base currency has been set others, like AU, this issue definitely will come out with following system log:
[CURRENCYCODE] => AUD
[PAYMENTSTATUS] => Pending
[PENDINGREASON] => multicurrency
Create another merchant account and set the same currency with your Magento store, it will be fixed.
payment_paypal_express.log
2014-12-12T01:51:34+00:00 DEBUG (7): Array
(
[url] => https://api-3t.sandbox.paypal.com/nvp
[GetTransactionDetails] => Array
(
[TRANSACTIONID] => 15Y48062WM0910211
[METHOD] => GetTransactionDetails
[VERSION] => 72.0
[USER] => ****
[PWD] => ****
[SIGNATURE] => ****
[BUTTONSOURCE] => OneStepCheckout_SI_MagentoCE
)
[response] => Array
(
[RECEIVEREMAIL] => john.yin.au-facilitator#gmail.com
[RECEIVERID] => VQ37QYGD3SFPW
[EMAIL] => john.yin.au-buyer#gmail.com
[PAYERID] => LXDH2RT89NRAS
[PAYERSTATUS] => verified
[COUNTRYCODE] => US
[SHIPTONAME] => john h
[SHIPTOSTREET] => 33
[SHIPTOSTREET2] => esdfd re
[SHIPTOCITY] => ddw
[SHIPTOSTATE] => AL
[SHIPTOCOUNTRYCODE] => AU
[SHIPTOCOUNTRYNAME] => Australia
[SHIPTOZIP] => 34343
[ADDRESSOWNER] => PayPal
[ADDRESSSTATUS] => Confirmed
[INVNUM] => 100000036
[SALESTAX] => 0.00
[TIMESTAMP] => 2014-12-12T01:51:34Z
[CORRELATIONID] => 2ef746f5f1a77
[ACK] => Success
[VERSION] => 72.0
[BUILD] => 14077178
[FIRSTNAME] => Test
[LASTNAME] => Buyer
[TRANSACTIONID] => 15Y48062WM0910211
[TRANSACTIONTYPE] => cart
[PAYMENTTYPE] => instant
[ORDERTIME] => 2014-12-12T01:50:39Z
[AMT] => 44.00
[TAXAMT] => 0.00
[SHIPPINGAMT] => 0.00
[HANDLINGAMT] => 0.00
[CURRENCYCODE] => AUD
[PAYMENTSTATUS] => Pending
[PENDINGREASON] => multicurrency
[REASONCODE] => None
[PROTECTIONELIGIBILITY] => Eligible
[PROTECTIONELIGIBILITYTYPE] => ItemNotReceivedEligible,UnauthorizedPaymentEligible
[L_NAME0] => color swatch te
[L_NUMBER0] => ZP-D3241-WHITE-L
[L_QTY0] => 1
[L_TAXAMT0] => 0.00
[L_SHIPPINGAMT0] => 0.00
[L_HANDLINGAMT0] => 0.00
[L_CURRENCYCODE0] => AUD
[L_AMT0] => 44.00
)
[__pid] => 30696
)
Sure it's just something I'm missing. Fairly new to the Paypal API, especially the classic one! Basically, I'm using the following code to generate a subscription to my site at £2.50 per month. I can get the subscription to appear in the Recurring Payments Dashboard on Paypal Sandbox, but it doesn't change the balance and seems to be missing things to do with the initial payment. I tried INITAMT too, which filled in some fields, but still doesn't change the Sandbox balance of my account. Any ideas guys? Here's the code:
<?php
// Set PayPal API version and credentials.
$api_version = '85.0';
$api_endpoint = 'https://api-3t.sandbox.paypal.com/nvp';
$api_username = 'MY_SANDBOX_USERNAME';
$api_password = 'MY_SANDBOX_PASSWORD';
$api_signature = 'MY_SANDBOX_SIGNATURE';
$startdate = gmdate('Y-m-d')."T00:00:00Z";
$request_params = array
(
'USER' => $api_username,
'PWD' => $api_password,
'SIGNATURE' => $api_signature,
'VERSION' => $api_version,
'METHOD' => 'CreateRecurringPaymentsProfile',
'PROFILESTARTDATE' => $startdate,
'DESC' => 'Membership',
'BILLINGPERIOD' => 'Month',
'BILLINGFREQUENCY' => '1',
'TOTALBILLINGCYCLES' => '0',
'AMT' => '2.50',
'MAXFAILEDPAYMENTS' => '12',
'ACCT' => '4641631486853053',
'CREDITCARDTYPE' => 'VISA',
'CVV2' => '123',
'FIRSTNAME' => 'James',
'LASTNAME' => 'Smith',
'STREET' => 'FirstStreet',
'CITY' => 'London',
'STATE' => 'G London',
'ZIP' => 'W2 1NE',
'COUNTRYCODE' => 'GB',
'CURRENCYCODE' => 'GBP',
'EXPDATE' => '052015'
);
// Loop through $request_params array to generate the NVP string.
$nvp_string = '';
foreach($request_params as $var=>$val)
{
$nvp_string .= '&'.$var.'='.urlencode($val);
}
// Send NVP string to PayPal and store response
$curl = curl_init();
curl_setopt($curl, CURLOPT_VERBOSE, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_URL, $api_endpoint);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $nvp_string);
$result = curl_exec($curl);
curl_close($curl);
$nvp_response_array = parse_str($result);
print_r($result);
?>
I took your code and used my own sandbox credentials. It seems to have worked just fine for me.
It came through as unclaimed since my account is USD, but you can see it did work just fine. Are you sure you're checking the correct sandbox account after you run it yourself?
I am using ci-merchant and I want to do my cart checkout with PayPal Express.
This is the code I am using:
$this->load->library('merchant');
$this->merchant->load('paypal_express');
$settings = $this->merchant->default_settings();
$settings = array(
'username' => 'bla bla bla.gmail.com',
'password' => 'bla bla bla',
'signature' => 'bla bla bla',
'test_mode' => true);
$this->merchant->initialize($settings);
$params =array(
'amount' => $this->cart->format_number($this->cart->total()),
'currency' => 'EUR',
'return_url' => 'https://www.example.com/checkout/payment_return/123',
'cancel_url' => 'https://www.example.com/checkout',
'description' => 'Esto es desc'
);
$response = $this->merchant->purchase($params);
With the above code I am being redirected to PayPal just fine, but at the items column I only see one unique item.
Now I would like to add per-item description, qty and price. But I don't know the where to include my items array, and wich format should I use. I can't find it on ci-merchant docs. Can anybody explain me?
You just need to setup your $params to include all of the fields you would need to make it work, and you need to make sure you're using version 63.0 or later.
It can be a little tricky, though, because any Express Checkout flow could include more than 1 payment, and each payment included could include more than 1 item, so you'll end up adding 0,1,2, etc. to params.
Here's a sample of SetExpressCheckout with 1 payment that has 2 items on it.
[REQUESTDATA] => Array
(
[USER] => ***
[PWD] => ***
[VERSION] => 97.0
[BUTTONSOURCE] => AngellEYE_PHPClass
[SIGNATURE] => ***
[METHOD] => SetExpressCheckout
[MAXAMT] => 200.00
[RETURNURL] => http://paypal.angelleye.com/standard/samples/DoExpressCheckoutPayment.php
[CANCELURL] => http://paypal.angelleye.com/paypal/class/cancel.php
[ALLOWNOTE] => 1
[HDRIMG] => http://paypal.angelleye.com/images/hdrimg.jpg
[SOLUTIONTYPE] => Sole
[LANDINGPAGE] => Billing
[BRANDNAME] => Angell EYE
[CUSTOMERSERVICENUMBER] => 555-555-5555
[BUYEREMAILOPTIONENABLE] => 1
[PAYMENTREQUEST_0_AMT] => 100.00
[PAYMENTREQUEST_0_CURRENCYCODE] => USD
[PAYMENTREQUEST_0_ITEMAMT] => 80.00
[PAYMENTREQUEST_0_SHIPPINGAMT] => 15.00
[PAYMENTREQUEST_0_TAXAMT] => 5.00
[PAYMENTREQUEST_0_DESC] => This is a test order.
[PAYMENTREQUEST_0_NOTETEXT] => This is a test note before ever having left the web site.
[PAYMENTREQUEST_0_PAYMENTACTION] => Sale
[L_PAYMENTREQUEST_0_NAME0] => Widget 123
[L_PAYMENTREQUEST_0_DESC0] => Widget 123
[L_PAYMENTREQUEST_0_AMT0] => 40.00
[L_PAYMENTREQUEST_0_NUMBER0] => 123
[L_PAYMENTREQUEST_0_QTY0] => 1
[L_PAYMENTREQUEST_0_ITEMURL0] => http://www.angelleye.com/products/123.php
[L_PAYMENTREQUEST_0_ITEMCATEGORY0] => Digital
[L_PAYMENTREQUEST_0_NAME1] => Widget 456
[L_PAYMENTREQUEST_0_DESC1] => Widget 456
[L_PAYMENTREQUEST_0_AMT1] => 40.00
[L_PAYMENTREQUEST_0_NUMBER1] => 456
[L_PAYMENTREQUEST_0_QTY1] => 1
[L_PAYMENTREQUEST_0_ITEMURL1] => http://www.angelleye.com/products/456.php
[L_PAYMENTREQUEST_0_ITEMCATEGORY1] => Digital
)
That results in the following when redirected to PayPal..
CI Merchant doesn't support listing order items in PayPal out of the box. There is a pull request which supports this, but it won't be merged into master until we work around some tax calculation and rounding issues.
Until it's supported officially, you may want to try using the forked version to achieve this, or simply edit the PayPal driver yourself to pass through the item data.