PayPal recurring payments misuse - paypal

I have used PayPal REST api to implement subscriptions(recurring payment) in my website. I have to set one notify url to be notified of the payment made(e.g. monthly plan) by subscriptions plan through PayPal. PayPal call it as Instant Payment Notification.
My question is, can developer misuse that call to simulate call from PayPal? Because developer is aware of the notify url and parameters being passed to that method. Developer just needs to know the recurring_payment_id.
If it can be misused then what are the steps should I follow to prevent it. Please guide me.

Instant Payment Notifications does not work with Rest API, however, Rest API does have its own notifications that are sent back.
In order to use the Notifications for Rest API a developer is required to create Webhooks.
WebHooks and Notifications with PayPal Rest API
With the billing agreement Profile ID a developer can cancel an agreement, or suspend an agreement. A developer cannot obtain credit card data from PayPal.
Here is some of the information from the PayPal Developer site about recurring payments and billing agreement creation:
Even if someone were able to get a response back from the server it would simply be the status of the billing agreement. No account holder information is returned.
This is a sample response from the PayPal Developer Site:
Retrieve an Agreement Rest API
{
"id": "I-0LN988D3JACS",
"state": "Pending",
"description": "New Description",
"plan": {
"payment_definitions": [
{
"type": "REGULAR",
"frequency": "Month",
"amount": {
"currency": "USD",
"value": "100.00"
},
"charge_models": [
{
"type": "TAX",
"amount": {
"currency": "USD",
"value": "12.00"
}
},
{
"type": "SHIPPING",
"amount": {
"currency": "USD",
"value": "10.00"
}
}
],
"cycles": "12",
"frequency_interval": "2"
}
],
"merchant_preferences": {
"setup_fee": {
"currency": "USD",
"value": "0.00"
},
"max_fail_attempts": "0",
"auto_bill_amount": "YES"
}
},
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements /I-0LN988D3JACS/suspend",
"rel": "suspend",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/re-activate",
"rel": "re_activate",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/cancel",
"rel": "cancel",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/bill-balance",
"rel": "self",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/billing-agreements/I-0LN988D3JACS/set-balance",
"rel": "self",
"method": "POST"
}
],
"start_date": "2015-02-19T08:00:00Z",
"agreement_details": {
"outstanding_balance": {
"currency": "USD",
"value": "0.00"
},
"cycles_remaining": "12",
"cycles_completed": "0",
"final_payment_date": "2016-12-19T10:00:00Z",
"failed_payment_count": "0"
}
}

Related

Paypal API v2-API response missing seller_receivable_breakdown

How could I find the seller_receivable_breakdown field from Paypal v2 API?
When I capture the payment, I don't get a seller_receivable_breakdown back in the API response, which should contain the net_amount.
Is there a reason why?
API Response:
{
"create_time": "2020-05-08T18:06:08Z",
"id": "35W12417YE077383Y",
"intent": "CAPTURE",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/35W12417YE077383Y",
"method": "GET",
"rel": "self",
"title": "GET"
}
],
"payer": {
"address": {
"country_code": "SG"
},
"email_address": "sb-vnmn01690912#personal.example.com",
"name": {
"given_name": "John",
"surname": "Doe"
},
"payer_id": "ZMVMLDB2Q3RNS"
},
"purchase_units": [
{
"amount": {
"currency_code": "USD",
"value": "76.00"
},
"payee": {
"email_address": "sb-dtlz4548886#business.example.com",
"merchant_id": "9D3W7QWY9MULE"
},
"payments": {
"captures": [
{
"amount": {
"currency_code": "USD",
"value": "76.00"
},
"create_time": "2020-05-08T18:06:37Z",
"final_capture": true,
"id": "1DU784512L090023U",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/payments/captures/1DU784512L090023U",
"method": "GET",
"rel": "self",
"title": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v2/payments/captures/1DU784512L090023U/refund",
"method": "POST",
"rel": "refund",
"title": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/35W12417YE077383Y",
"method": "GET",
"rel": "up",
"title": "GET"
}
],
"seller_protection": {
"dispute_categories": [
"ITEM_NOT_RECEIVED",
"UNAUTHORIZED_TRANSACTION"
],
"status": "ELIGIBLE"
},
"status": "COMPLETED",
"update_time": "2020-05-08T18:06:37Z"
}
]
},
"reference_id": "default",
"shipping": {
"address": {
"address_line_1": "123 Thomson Rd.",
"admin_area_1": "SG_zip = 308123",
"admin_area_2": "Singapore",
"country_code": "SG",
"postal_code": "308123"
},
"name": {
"full_name": "Doe John"
}
}
}
],
"status": "COMPLETED",
"update_time": "2020-05-08T18:06:37Z"
}
Either it may not be there in sandbox mode, or you may need to do a get operation on the capture id.
https://developer.paypal.com/docs/api/payments/v2/#captures_get
I contacted PayPal support about this and this is their response:
Unfortunately, it looks like the capture response will not include the "seller_receivable_breakdown" if the capture is still pending. You can see that mentioned under the "seller_receivable_breakdown" variable in this documentation about the captures object. If the capture is pending or processing still, (this happens often when a customer opts for a card transaction rather than a PayPal account balance transaction), then the capture response won't show the seller_receivable_breakdown but you can call for details on the capture right after it has finished to then see that object.
PayPal may hold a "suspicious" transaction for few days and the transaction is in a "Pending" state during the hold. Some transaction might even require your personal approval. While the transaction is on hold you cannot get the seller_receivable_breakdown via API (though this data is available in the dashboard).
You can setup a WebHook to fire on "Payment capture completed" and retrieve the information there. Be ready that the WebHook might fire in couple of days after the order.

Paypal subscription webhook for recurring payment

I had set up Paypal webhooks to track the recurring payments. I had set up webhook for
BILLING.SUBSCRIPTION.UPDATED
BILLING.SUBSCRIPTION.RE-ACTIVATED
BILLING.SUBSCRIPTION.RENEWED
BILLING.SUBSCRIPTION.CANCELLED
BILLING.SUBSCRIPTION.EXPIRED
BILLING.SUBSCRIPTION.SUSPENDED
BILLING.SUBSCRIPTION.PAYMENT.FAILED
BILLING.SUBSCRIPTION.ACTIVATED
I am not getting any event apart from Activate. can anyone help me to find out the hook that I need to set up to track transactions for the recurring subscription billing?
Thanks
In the webhook simulator PAYMENT.SALE.COMPLETED is not fully shown, but it is correct to use when payment for a subscription is made. It also has a subscription_id (it called billing_agreement_id for some reason).
{
"id": "WH-528005538C798144N-7H391362AP602264K",
"create_time": "2021-04-17T22:45:35.950Z",
"resource_type": "sale",
"event_type": "PAYMENT.SALE.COMPLETED",
"summary": "Payment completed for CAD 14.55 CAD",
"resource": {
"billing_agreement_id": "I-37B4TLL0FAJJ",
"amount": {
"total": "14.55",
"currency": "CAD",
"details": {
"subtotal": "14.55"
}
},
"payment_mode": "INSTANT_TRANSFER",
"update_time": "2021-04-17T22:44:02Z",
"create_time": "2021-04-17T22:44:02Z",
"protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE",
"transaction_fee": {
"currency": "CAD",
"value": "0.72"
},
"protection_eligibility": "ELIGIBLE",
"links": [
{
"method": "GET",
"rel": "self",
"href": "https://api.sandbox.paypal.com/v1/payments/sale/44P499886K055384U"
},
{
"method": "POST",
"rel": "refund",
"href": "https://api.sandbox.paypal.com/v1/payments/sale/44P499886K055384U/refund"
}
],
"id": "44P499886K055384U",
"state": "completed",
"invoice_number": ""
},
"status": "PENDING",
"transmissions": [
{
"webhook_url": "https://webhook.site/7ece4e68-de87-46df-a341-5d3dc580efe6",
"transmission_id": "b02ef980-9fce-11eb-b0ec-5bc241fbaaf3",
"status": "PENDING"
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-528005538C798144N-7H391362AP602264K",
"rel": "self",
"method": "GET",
"encType": "application/json"
},
{
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-528005538C798144N-7H391362AP602264K/resend",
"rel": "resend",
"method": "POST",
"encType": "application/json"
}
],
"event_version": "1.0"
}
See https://developer.paypal.com/docs/integration/direct/webhooks/event-names/#subscriptions
It looks like you want PAYMENT.SALE.COMPLETED
There is also one for refunded and reversed.

PayPal REST API - State doesn't change after purchase

So I have noticed that when checking on the status of a payment
GET /v1/payments/payment/<Payment-Id>
the "state" of a PayPal response does not change from "created" even after the PayPal user has purchased the item. It isn't until I execute the payment that the state changes to "approved".
This makes it difficult to tell the difference between a payment that has actually been approved by a client and one that is still in process as they are both in the "created" state. The only difference in the json response is that it seems to include shipping_address once the payment has actually been approved. I'm not sure that is a standard way to denote the difference though.
What is the standard way to tell if a client has actually approved a PayPal transaction when using the above call to the REST API?
Note: I already have the PHP callback scripts setup and working. I am working on a fail safe system that constantly checks database entries to make sure an approved payment was executed (in case of some system failure).
I thought I could use the IPN callback script I have setup from CLASSIC API but it seems that the REST API and CLASSIC API are not compatible since the IPN callback will not contain a transaction id or any necessary information to be useful.
It seems like when using the REST API, if you miss the redirect call to your webserver then that transaction is lost forever.
It is correct that you won't tell an approved payment from the state field in the lookup API response, instead, you would look for the payer object in the JSON body, and that piece of info will indicate an approved payment resource for you.
Here're the JSON responses of the same PAY-ID before/after the customer redirection (user approval)
Lookup payment /v1/payments/payment/PAY-9J02491382988403BK3BMC6I (before user approval):
{
"id": "PAY-9J02491382988403BK3BMC6I",
"intent": "sale",
"state": "created",
"cart": "07U14103P0008801U",
"transactions": [ {
"amount": {
"total": "80.00",
"currency": "USD"
},
"payee": {"email": "USM#email.com"},
"invoice_number": "55a460ff696br",
"item_list": {
"items": [
{
"name": "Test Ticket 1",
"sku": "55a460ff65f13",
"price": "10.00",
"currency": "USD",
"quantity": 1
},
{
"name": "Test Ticket 2",
"sku": "55a460ff66c7a",
"price": "20.00",
"currency": "USD",
"quantity": 2
},
{
"name": "Test Ticket 3",
"sku": "55a460ff66ce2",
"price": "10.00",
"currency": "USD",
"quantity": 3
}
],
"shipping_address": {
"recipient_name": "Test Name",
"line1": "Main St 1",
"city": "San Jose",
"state": "CA",
"postal_code": "95131",
"country_code": "US"
}
},
"related_resources": [],
"notify_url": "https://bt-pduan-1.c9.io/ipn.php"
}],
"redirect_urls": {
"return_url": "http://localhost:80/getpaypal?paymentId=PAY-9J02491382988403BK3BMC6I",
"cancel_url": "http://localhost:80/cancel"
},
"create_time": "2016-02-16T06:28:08Z",
"update_time": "2016-02-16T06:28:08Z",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-9J02491382988403BK3BMC6I",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-9J02491382988403BK3BMC6I/execute",
"rel": "execute",
"method": "POST"
},
{
"href": "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-07U14103P0008801U",
"rel": "approval_url",
"method": "REDIRECT"
}
]
}
Lookup the payment after user approval (I'm removing some JSON contents e.g. transaction/url arrays for readablity):
{
"id": "PAY-9J02491382988403BK3BMC6I",
"intent": "sale",
"state": "created",
"cart": "07U14103P0008801U",
"payer": {
"payment_method": "paypal",
"status": "VERIFIED",
"payer_info": {
"email": "USP#email.com",
"first_name": "Payer",
"last_name": "US",
"payer_id": "8FMFQ2KVYYHTY",
"shipping_address": {
"recipient_name": "Test Name",
"line1": "Main St 1",
"city": "San Jose",
"state": "CA",
"postal_code": "95131",
"country_code": "US"
},
"phone": "408-743-9795",
"country_code": "US",
"billing_address": {
"line1": "1 Main St",
"line2": "",
"city": "San Jose",
"state": "CA",
"postal_code": "95131",
"country_code": "US"
}
}
},
"transactions": [],
"redirect_urls": {},
"create_time": "2016-02-16T06:28:08Z",
"update_time": "2016-02-16T06:28:08Z",
"links": []
}
By checking the recorded PAY-ID looking for payer object in the API response, your will be able to save the orders and proceed the execute call in case it was missed in the customer return redirection.
Additionaly, neither IPN or webhooks would help in this case as they are async meesages triggered by transactional level events, which means there will be no notification until a payment is executed.

paypal REST API transaction approved

I making an integration with paypal. I'm using REST API for java. Currently, I have a problem with pending payments. When I look up a payment (https://api.paypal.com/v1/payments/payment/{paymentId}) to sandbox environment to check the payment status the responses are different.
When I approved payment as a seller the payment is in status
approved
and sale is in status
completed
example response:
{
"id": "PAY-5YK922393D847794YKER7MUI",
"create_time": "2013-02-19T22:01:53Z",
"update_time": "2013-02-19T22:01:55Z",
"state": "approved",
"intent": "sale",
"payer": {
"payment_method": "credit_card",
"funding_instruments": [
{
"credit_card": {
"type": "mastercard",
"number": "xxxxxxxxxxxx5559",
"expire_month": "2",
"expire_year": "2018",
"first_name": "Betsy",
"last_name": "Buyer"
}
}
]
},
"transactions": [
{
"amount": {
"total": "7.47",
"currency": "USD",
"details": {
"subtotal": "7.47"
}
},
"description": "This is the payment transaction description.",
"related_resources": [
{
"sale": {
"id": "36C38912MN9658832",
"create_time": "2013-02-19T22:01:53Z",
"update_time": "2013-02-19T22:01:55Z",
"state": "completed",
"amount": {
"total": "7.47",
"currency": "USD"
},
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/36C38912MN9658832",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/36C38912MN9658832/refund",
"rel": "refund",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
"rel": "parent_payment",
"method": "GET"
}
]
}
}
]
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
"rel": "self",
"method": "GET"
}
]
}
and sometimes payment is
pending
and the sale status in
completed
example respose:
{
"id": "PAY-5YK922393D847794YKER7MUI",
"create_time": "2013-02-19T22:01:53Z",
"update_time": "2013-02-19T22:01:55Z",
"state": "pending",
"intent": "sale",
"payer": {
"payment_method": "credit_card",
"funding_instruments": [
{
"credit_card": {
"type": "mastercard",
"number": "xxxxxxxxxxxx5559",
"expire_month": "2",
"expire_year": "2018",
"first_name": "Betsy",
"last_name": "Buyer"
}
}
]
},
"transactions": [
{
"amount": {
"total": "7.47",
"currency": "USD",
"details": {
"subtotal": "7.47"
}
},
"description": "This is the payment transaction description.",
"related_resources": [
{
"sale": {
"id": "36C38912MN9658832",
"create_time": "2013-02-19T22:01:53Z",
"update_time": "2013-02-19T22:01:55Z",
"state": "completed",
"amount": {
"total": "7.47",
"currency": "USD"
},
"parent_payment": "PAY-5YK922393D847794YKER7MUI",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/36C38912MN9658832",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/36C38912MN9658832/refund",
"rel": "refund",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
"rel": "parent_payment",
"method": "GET"
}
]
}
}
]
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-5YK922393D847794YKER7MUI",
"rel": "self",
"method": "GET"
}
]
}
Similarly, Sanbox works when I reject the transaction. Once the payment status is failed and sale status is reversed and another time is pending and reserved.
My question is when can be 100% sure about the transaction is completed or failed?
Should I check the payment status or the sale status?
Maybe this this problem is related only with sandbox not with production.
PS. This code is only the sample, do NOT analyze it.
You should ensure that that even if the state of the sale shows completed, the payment has been approved. Even after the sale status shows completed, there might be something related to the specific buyer's account which keeps the payment in a pending state. You should ensure that payment has been approved before shipping your product.

PayPal REST API - List of Refund Transactions

A few months ago I asked the following for a List of Sale Transactions:
PayPal Restful API - Pull List of Sale Transactions
In that question I got an answer referencing the List of Payment Resources call, which is great, but now I need to do something similar for refunds. However it's unclear to me from the documentation whether this includes Refunds.
Does the List of Payment Resources include Refund transactions (which would be a reverse payment of sorts), or is there some other way of accessing a list of Refund transactions?
Thanks in advance.
You currently cannot specifically get a list of refunded transactions. However, as you mention, you can get a list of payments and within that you will see the state of the sale object as refunded and a refund object with the state of completed:
Example:
{
"id": "PAY-ABCDEFGHIJKLMNO1234567890",
"create_time": "2013-09-20T15:44:04Z",
"update_time": "2013-09-20T16:10:05Z",
"state": "approved",
"intent": "sale",
"payer": {
"payment_method": "paypal",
"payer_info": {
"email": "bob-facilitator#example.com",
"first_name": "Bob",
"last_name": "Example",
"payer_id": "ABCDEFG123456"
}
},
"transactions": [
{
"amount": {
"total": "1.00",
"currency": "USD",
"details": {
"subtotal": "1.00"
}
},
"description": "example description",
"related_resources": [
{
"sale": {
"id": "ABCDEFGH123456789",
"create_time": "2013-09-20T15:44:04Z",
"update_time": "2013-09-20T16:10:05Z",
"state": "refunded",
"amount": {
"total": "1.00",
"currency": "USD"
},
"parent_payment": "PAY-ABCDEFGHIJKLMNO1234567890",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/12345678ABCDEFGIJ",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/12345678ABCDEFGIJ/refund",
"rel": "refund",
"method": "POST"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-ABCDEFGHIJKLMNO1234567890",
"rel": "parent_payment",
"method": "GET"
}
]
}
},
{
"refund": {
"id": "ABCDEFGH123456789",
"create_time": "2013-09-20T16:10:05Z",
"update_time": "2013-09-20T16:10:05Z",
"state": "completed",
"amount": {
"total": "1.00",
"currency": "USD"
},
"sale_id": "12345678ABCDEFGIJ",
"parent_payment": "PAY-57E15446PJ712294VKI6G2RA",
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/refund/ABCDEFGH123456789",
"rel": "self",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-ABCDEFGHIJKLMNO1234567890",
"rel": "parent_payment",
"method": "GET"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/sale/12345678ABCDEFGIJ",
"rel": "sale",
"method": "GET"
}
]
}
}
]
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-ABCDEFGHIJKLMNO1234567890",
"rel": "self",
"method": "GET"
}
]
}