I stumbled over many questions regarding the implementation of the new Paypal Subscription API.
It is difficult because there is no PHP SDK for subscriptions and the documentation is hard to read.
Plus, you find many old tutorials, refering back to the old APIs.
So I decided to create a complete tutorial of how to integrate the new Paypal subscriptions with PHP and Javascript.
Prerequisites: You must have a standard paypal account (should be "business") and a developer account.
Okay, let's start:
1. Create Subscription Button in Paypal
In SANDBOX mode:
- Go to "Billing plans" https://sandbox.paypal.com/billing/plans (Tab Subscription Plans)
- Hit the button "Create Plan": https://sandbox.paypal.com/billing/plans/plan/create/choose-product?from=plans
- add your product ... choose "fix price", submit, on next page enter the price
- Product and Plan are the same for me (name of my digital good)
- click "activate plan"
- new page opens showing the button design: click button "Copy code" (bottom right)
- save the code in your editor
Do the same procedure for LIVE buttons:
https://www.paypal.com/billing/plans
https://www.paypal.com/billing/plans/plan/create/choose-product?from=plans
RESULT:
<div id="paypal-button-container-P-82B73303CA584772TMBOW7LA"></div>
<script src="https://www.paypal.com/sdk/js?client-id=AWJSVt2lvrjDK3eyMiicht1nOCRcFEHOhxBJS9enLCw6ZvQWB7zVfne1wzCBPcdJzaiagde5YIa2ZFbN&vault=true&intent=subscription" data-sdk-integration-source="button-factory"></script>
<script>
paypal.Buttons({
style: {
shape: 'rect',
color: 'gold',
layout: 'vertical',
label: 'subscribe'
},
createSubscription: function(data, actions) {
return actions.subscription.create({
/* Creates the subscription */
plan_id: 'P-82B73303CA584772TMBOW7LA'
});
},
onApprove: function(data, actions) {
alert(data.subscriptionID); // You can add optional success message for the subscriber here
}
}).render('#paypal-button-container-P-82B73303CA584772TMBOW7LA'); // Renders the PayPal button
</script>
Congratulations, with this code a user can create a subscription with you!
2. Set up your APP (site specific)
- login at https://developer.paypal.com/home
- go to dashboard https://developer.paypal.com/developer/applications/
- click "Create App"
- name the app and also add sandbox account (merchant)
- note: later on you need a second sandbox account to simulate the buyer. Create it.
- copy "SANDBOX API CREDENTIALS" from the page
Example:
Sandbox account
service-buyer@yoursite.com
Client ID
AYkqPe4ShL6LVIOBssEe834sdfsdfsfsfsdfklsjfhudfusfusdfzzjsdkfskdf12345
Secret
EJr5Y-MoCb97RRerd0Z5xfjsfsfdkfsdklfksjkfsdjksdflsdf7Q4MJpsQqy_7OhWxMWgGzxGxPgb1jh12345
- next scroll down and click "Add Webhook"
- Enter the URL you want to be informed about paypal events (your server URL, where the PHP script will be)
- e.g. URL https://www.yoursite.com/provider/paypal-webhook
- click on "all events"
- then "Save" button
3. PHP script (on your Server) to catch the Webhook data
- Create a PHP script at the location you specified (URL above) with the following code:
// WEBHOOK simulator: https://developer.paypal.com/developer/webhooksSimulator/
// WEBHOOK events: https://developer.paypal.com/docs/api-basics/notifications/webhooks/event-names/#subscriptions
// get data from Paypal Webhook POST
$data = json_decode(file_get_contents("php://input"));
error_log('PAYPAL EVENT: '.$data->event_type);
// write all data into server log - helpful for debugging and identifying available data
error_log(print_r($data, true));
// Next an example of how you can process and interact with the webhook data on your server:
if($data->event_type == 'BILLING.SUBSCRIPTION.ACTIVATED')
{
// paypal subscription id, e.g. "I-75NW8GLJYFDX"
$subscription_id = $data->resource->id;
$payeremail = $data->resource->subscriber->email_address;
$plan_id = $data->resource->plan_id;
$payerid = $data->resource->subscriber->payer_id;
// USERID
$userid = $data->resource->custom_id;
error_log($subscription_id); // e.g. "I-F5KXHVG4X0YX"
error_log('userid: '.$userid); // our own userid (defined in button javascript as "custom_id")
// handle the subscription
// Example: save subscription_id into our database
if(!empty($userid))
{
// very important, here we store the payerid (subscriptionid in our database)
$paydata = array(
'userid' => $userid,
'payprovider' => 'paypal',
'payerid' => $subscription_id,
'status' => 'active'
);
// your custom function
payprovider_create_entry($paydata);
}
else
{
error_log('# PAYPAL PROBLEM: userid not found: '.$payeremail);
}
}
else if($data->event_type == 'PAYMENT.SALE.COMPLETED')
{
// paypal subscription id, e.g. "I-75NW8GLJYFDX"
$subscription_id = $data->resource->billing_agreement_id;
// e.g. "29.95"
$payamount = $data->resource->amount->total;
// e.g. "completed"
$status = $data->resource->state;
// USERID (not the missing "_id")
$userid = $data->resource->custom;
error_log($subscription_id); // e.g. "I-F5KXHVG4X0YS"
error_log($payamount); // e.g. "29.95"
error_log($status); // e.g. "completed"
error_log('userid: '.$userid); // our own userid
// unlock user account as premium
if($status=='completed')
{
if(empty($userid))
{
// user not found ...
}
else
{
// custom function you define yourself!
$userdata = get_customerdata($userid);
if(!empty($userdata))
{
// register payment and send email to customer
// custom function you define yourself!
register_payment($userid, $payamount, 'paypal');
}
}
}
}
else if($data->event_type == 'PAYMENT.SALE.REFUNDED')
{
error_log('REFUND WEBHOOK DATA:');
error_log(print_r($data, true));
// paypal subscription id, e.g. "I-75NW8GLJYFDX"
$subscription_id = $data->resource->id;
// USERID
$userid = $data->resource->custom_id;
// *** $userid = $data->resource->custom; // not tested yet!
$payeremail = $data->resource->subscriber->email_address;
// e.g. "-29.95"
$payamount = $data->resource->amount->total;
// e.g. "completed"
$status = $data->resource->state;
error_log($subscription_id); // e.g. "I-F5KXHVG4X0YS"
error_log('userid: '.$userid); // our own userid
if(!empty($userid))
{
$userdata = get_usercustomerdata($userid);
// does user exist in DB
if(!empty($userdata))
{
// if money is lost, then rollback last added month from endtime
// your custom code
}
else
{
error_log('# PAYPAL PROBLEM: userid not found: '.$payeremail);
}
}
return;
Webhook done!
You can trigger some events with the webhook simulator and check your error logs, if the incoming data has arrived properly.
It should show (on Paypal) after the test is sent:
Your event has been successfully queued at {date}
Note that I use a $userid
in the example code above that comes from custom_id
. This is still an empty value (not set).
We have to add the custom_id
when we use the paypal payment buttons on the sales page, with Javascript.
4. Implement paypal payment button on sales page
Note: Sandbox URL and live URL are the same: https://www.sandbox.paypal.com/sdk/js
$planid = 'P-9U5126551T923450KMBNASXX';
// if you have several plans, you want to decide here about other plans
// paypal credentials (sandbox for now)
$clientid = "AU_d-ShIOx.......";
$secret = "EObgYm5a.......";
// change paypal URL params for customization, see https://developer.paypal.com/docs/checkout/reference/customize-sdk/
// "locale=de_DE" switches to German
// "disable-funding=credit,card" hides the creditcard button
$output .= '
<div class="paypal-wrap">
<div id="paypal-button-container-'.$planid.'" class="paypal-button-wrap"></div>
</div>
<script src="https://www.sandbox.paypal.com/sdk/js?client-id='.$clientid.'&vault=true&intent=subscription&locale=de_DE&disable-funding=credit,card" data-sdk-integration-source="button-factory"></script>
<script>
paypal.Buttons({
style: {
shape: "rect",
color: "gold",
layout: "vertical",
label: "subscribe"
},
createSubscription: function(data, actions) {
return actions.subscription.create({
plan_id: "'.$planid.'",
custom_id: "'.$userid.'",
application_context: {
shipping_preference: "NO_SHIPPING" // no shipping since digital good
},
});
},
onApprove: function(data, actions)
{
// console.log(data.subscriptionID); // further process if needed
$(".paypal-wrap").hide();
// show your success confirmation
},
onCancel: function (data) {
// show cancel page, or return to cart
},
onError: function (err) {
// window.location.href = "/error-page";
alert("Payment Error: "+err);
}
}).render("#paypal-button-container-'.$planid.'");
</script>
';
Next: TRY the button on your site!
If all goes well, the paypal payment popup opens and you can do the purchase with your sandbox buyer account.