2
votes

The following PHP:

    public function processPayment($data)
{
    // Create Payer object
    $payer = new Payer();
    // Payment method is via PayPal. Take the customer to PayPal for processing.
    $payer->setPaymentMethod("paypal");
    // Create billingAddress as Address object and fill with customer's billing address.
    $billingAddress = new Address();
    $billingAddress->setLine1($data['payment_address_1'])
        ->setLine2($data['payment_address_2'])
        ->setCity($data['payment_city'])
        ->setState(/* TWO-LETTER STATE POSTAL ABBREV */)
        ->setPostalCode($data['payment_postcode'])
        ->setCountryCode(/* COUNTRY CODE */);
    // Create PayerInfo object, populate with customer's billing
    // info (name, billingAddress, phone, email)
    $payerInfo = new PayerInfo();
    $payerInfo->setFirstName($data['payment_firstname'])
        ->setLastName($data['payment_lastname'])
        ->setBillingAddress($billingAddress)
        //->setPhone($data['telephone'])
        ->setEmail($data['email']);
    // Assign payerInfo to payer.
    $payer->setPayerInfo($payerInfo);

    /**
     * List of items sold and their details
     * Add shipping address
     */
    $itemList = new ItemList();
    foreach ($data['products'] as $product)
    {
        if ($product['product_sku']) {
            $item = new Item();
            $item->setName($product['product_name'])
                ->setSku($product['product_sku'])
                ->setQuantity($product['quantity'])
                ->setPrice(number_format($product['price'], 2 , "." , "," ))
                ->setTax(number_format($product['tax'] , 2 , "." , "," ))
                ->setCurrency($data['currency_code']);
            $itemList->addItem($item);
        }
    }
    $shippingAddress = new ShippingAddress();
    $shippingAddress->setRecipientName($data['shipping_firstname'].' '.$data['shipping_lastname'])
        ->setLine1($data['shipping_address_1'])
        ->setLine2($data['shipping_address_2'])
        ->setCity($data['shipping_city'])
        ->setState(/* TWO-LETTER STATE POSTAL ABBREV */)
        ->setPostalCode($data['shipping_postcode'])
        ->setCountryCode(/* COUNTRY CODE */);
    $itemList->setShippingAddress($shippingAddress);

    $details = new Details();
    $details->setShipping(number_format($totals['shipping'] , 2 , "." , "," ))
        ->setTax(number_format($totals['tax'] , 2 , "." , "," ))
        ->setSubtotal(number_format($totals['subTotal'] , 2 , "." , "," ));

    $amount = new Amount();
    $amount->setCurrency($data['currency_code'])
        ->setTotal(number_format($data['total'] , 2 , "." , "," ))
        ->setDetails($details);

    $transaction = new Transaction();
    $transaction->setAmount($amount)
        ->setItemList($itemList)
        ->setInvoiceNumber($data['invoice_number'])
        ->setNotifyUrl(/* NOTIFY URL */);

    $redirectUrls = new RedirectUrls();
    $redirectUrls->setReturnUrl(/* RETURN URL */)
        ->setCancelUrl(/* CANCEL URL */);

    $payment = new Payment();
    $payment->setIntent("sale")
        ->setPayer($payer)
        ->setRedirectUrls($redirectUrls)
        ->addTransaction($transaction)
        ->setPayee($this->payee); // payee created and populated in _constructor

    echo '<h1>Redirecting. . . .</h1>';

    try {
        $payment->create($this->apiContext); // apiContext created and populated in _constructor
    } catch (Exception $ex) {
        echo $this->PayPalError($ex); // Print detailed error messages
    }
    echo "<pre>$payment</pre>";
    return;
}

results in

Redirecting. . . .

MALFORMED_REQUEST - Incoming JSON request does not map to API request

{
    "intent": "sale",
    "payer": {
        "payment_method": "paypal",
        "payer_info": {
            "first_name": "Sandbox",
            "last_name": "Buyer",
            "billing_address": {
                "line1": "BILLING ADDRESS LINE 1",
                "line2": "",
                "city": "CITY",
                "state": "ST",
                "postal_code": "ZIP",
                "country_code": "US"
            },
            "email": "SANDBOX BUYER EMAIL"
        }
    },
    "redirect_urls": {
        "return_url": "RETURN URL",
        "cancel_url": "CANCEL URL"
    },
    "transactions": [
        {
            "amount": {
                "currency": "USD",
                "total": "156.00",
                "details": {
                    "shipping": "23.00",
                    "tax": "0.00",
                    "subtotal": "133.00"
                }
            },
            "item_list": {
                "items": [
                    {
                        "name": "PRODUCT NAME",
                        "sku": "PRODUCT SKU",
                        "quantity": 1,
                        "price": "133.00",
                        "tax": "0.00",
                        "currency": "USD"
                    }
                ],
                "shipping_address": {
                    "recipient_name": "Sandbox Buyer",
                    "line1": "SHIPPING ADDRESS LINE 1",
                    "line2": "",
                    "city": "SHIPPING CITY",
                    "state": "ST",
                    "postal_code": "ZIP",
                    "country_code": "US"
                }
            },
            "invoice_number": 25,
            "notify_url": "NOTIFY URL"
        }
    ],
    "payee": {
        "email": "SANDBOX MERCHANT EMAIL",
        "merchant_id": "SANDBOX MERCHANT ID"
    }
}

I get the MALFORMED_REQUEST and the data dump, no additional activity. The user is supposed to be taken to PayPal (Sandbox, in this case) for payment processing (and then return to the calling site. I am using the PayPal PHP SDK (REST). Where do I go from here?

1

1 Answers

1
votes

There are a few issues I can see with the JSON that would cause potential errors. One that is causing that MALFORMED_REQUEST error, and two that you may run into once you fix that.

First, the payee object will actually go underneath the transactions object, so that node should look something like:

"transactions": [{
    "amount": {
        "currency": "USD",
        "total": "156.00",
        "details": {
            "shipping": "23.00",
            "tax": "0.00",
            "subtotal": "133.00"
        }
    },
    "payee": {
        "email": "SANDBOX MERCHANT EMAIL"
    },
    "item_list": {
        "items": [{
            "name": "PRODUCT NAME",
            "sku": "PRODUCT SKU",
            "quantity": 1,
            "price": "133.00",
            "tax": "0.00",
            "currency": "USD"
        }],
        "shipping_address": {
            "recipient_name": "Sandbox Buyer",
            "line1": "SHIPPING ADDRESS LINE 1",
            "line2": "",
            "city": "SHIPPING CITY",
            "state": "ST",
            "postal_code": "ZIP",
            "country_code": "US"
        }
    },
    "invoice_number": "25",
    "notify_url": "NOTIFY URL"
}]

Next, in your payee object, you specify both the email and merchant_id. Only one of those is required. There's a little quirk that happens when both are added and may not match up correctly where there will be an error that is produced. The specific error would look like:

{"response":{"name":"PAYEE_ACCOUNT_INVALID","message":"Payee account is invalid.","information_link":"https://developer.paypal.com/docs/api/#PAYEE_ACCOUNT_INVALID","debug_id":"feefw4543a1a5","httpStatusCode":400},"httpStatusCode":400}

Lastly, invoice_number expects a string (https://developer.paypal.com/docs/api/payments/#definition-transaction:v1). The API endpoint may auto-convert it, but it's better to have it match the requirements.

The transaction object I posted above should work for your needs, once the real values are added in.