· 5 years ago · Jun 27, 2020, 06:36 PM
1<?php
2
3
4namespace App\Http\Controllers;
5
6use DB;
7
8
9class PayPalHandler extends PaymentAPIHandler
10{
11
12 /*
13 * Algunas notas sobre las tablas y los modelos
14 *
15 * Subscription.php
16 *
17 * paypal_id -> represeta el id del objeto de subscription de la api de paypal
18 * subscription_type -> el tipo de SubscriptionPlan
19 * is_canceled -> es true si el pago fallo o si se suspendio manualmente
20 * created_date -> fecha en la q se creo
21 * last_renewed_date -> fecha del ultimo pago exitoso
22 *
23 * SubscriptionPlan.php
24 *
25 * plan_type -> el id del plan segun la api de paypal
26 *
27 *
28 * */
29
30 /*
31 * Debe realizar un cargo de $amount por PayPal
32 *
33 * @param $params es un array que contiene los parametros necesarios para identificar al usuario (token? mail de paypal?) es a eleccion.
34 *
35 * @returns un array del estilo ['error' => false|true, 'msg' => 'mensaje de error'].
36 * Si no hay error, no hay necesidad de agregar msg
37 */
38 public static function charge($amount, $params){
39 $data = [];
40 $item = new \stdClass();
41 $item->currency_code = "USD";
42 $item->value = strval($amount);
43 array_push($data, ["amount" => $item]);
44
45 $application_context = new \stdClass();
46 /* if you want a return url
47 * $application_context->return_url = "https://www.google.com/";
48 $application_context->cancel_url = "https://www.google.com/";*/
49
50 $response = self::doApiRequest("Bearer " . self::getAccessToken(), "/v2/checkout/orders",
51 array("intent" => "CAPTURE", "purchase_units" => $data, "application_context" => $application_context), "application/json");
52
53 if(isset($response["error"]) && $response["error"] == true){
54 return array("error" => true, "msg" => $response["message"]);
55 }
56
57 $id = $response["id"];
58 $href = $response["links"][1]["href"];
59
60
61 DB::table('paypal_references')->insert(
62 ['paypal_id' => $id, 'params' => $params["user_id"]]
63 );
64
65 return array("error" => false, "href" => $href, "id" => $id);
66 }
67
68 /*
69 * @param $params es un array que contiene los parametros necesarios para identificar al usuario (token? mail de paypal?) es a eleccion.
70 *
71 * @returns un array del estilo ['error' => false|true, 'msg' => 'mensaje de error'].
72 * Si no hay error, no hay necesidad de agregar msg
73 */
74 public static function subscribe($planId, $params){
75 $response = self::doApiRequest("Bearer " . self::getAccessToken(), "/v1/billing/subscriptions",
76 array("plan_id" => $planId), "application/json");
77
78 if(isset($response["error"]) && $response["error"] == true){
79 return array("error" => true, "msg" => $response["message"]);
80 }
81
82 $id = $response["id"];
83 $href = $response["links"][0]["href"];
84
85 DB::table('paypal_references')->insert(
86 ['paypal_id' => $id, 'params' => $params["user_id"]]
87 );
88
89 return array("error" => false, "href" => $href, "id" => $id);
90 }
91
92 /*
93 * Esta es una funcion para poder cambiar de plan de subscripcion
94 * Recibe el id de la subscription actual y el id del plan nuevo,
95 * la idea es usar /v1/billing/subscriptions/{id} para cambiar al plan
96 * nuevo.
97 */
98 public static function upgradeSubscription($subscriptionId, $newPlanId){
99 $response = self::doApiRequest("Bearer " . self::getAccessToken(), "/v1/billing/subscriptions/".$subscriptionId."/revise",
100 array("plan_id" => $newPlanId), "application/json");
101
102 if(isset($response["error"]) && $response["error"] == true){
103 return array("error" => true, "msg" => $response["message"]);
104 }
105
106 $href = $response["links"][0]["href"];
107
108 return array("error" => false, "href" => $href);
109 }
110
111 /*
112 * Suspende la subscripcion dada (no la cancela) ya que despues se puede reactivar
113 */
114 public static function suspendSubscription($subscriptionId){
115 $response = self::doApiRequest("Bearer " . self::getAccessToken(), "/v1/billing/subscriptions/".$subscriptionId."/suspend",
116 array("reason" => "No longer interested"), "application/json");
117
118 if(isset($response["error"]) && $response["error"] == true){
119 return array("error" => true, "msg" => $response["message"]);
120 }
121
122 return array("error" => false);
123 }
124
125 /*
126 * Reactiva la subscripcion dada.
127 */
128 public static function renewSubscription($subscriptionId){
129 $response = self::doApiRequest("Bearer " . self::getAccessToken(), "/v1/billing/subscriptions/".$subscriptionId."/activate",
130 [], "application/json");
131
132 if(isset($response["error"]) && $response["error"] == true){
133 return array("error" => true, "msg" => $response["message"]);
134 }
135
136 return array("error" => false);
137 }
138
139 private static function subscriptionGetPlan($subscriptionId){
140 $response = self::doApiRequest("Bearer " . self::getAccessToken(), "/v1/billing/subscriptions/".$subscriptionId, null,
141 "application/json", false);
142
143 if(isset($response["error"]) && $response["error"] == true){
144 return "";
145 }
146
147 return $response["plan_id"];
148 }
149
150 /*
151 * Procesa las requests que son del IPN de paypal o de los webhooks. Actualiza la db con la ultima fecha de pago o con el estado de cancelado si es necesario (si el pago fallo)
152 */
153 public static function handleIPN(){
154 // STEP 1: read POST data
155 // Reading POSTed data directly from $_POST causes serialization issues with array data in the POST.
156 // Instead, read raw POST data from the input stream.
157 $raw_post_data = file_get_contents('php://input');
158 $raw_post_array = explode('&', $raw_post_data);
159 $myPost = array();
160 foreach ($raw_post_array as $keyval) {
161 $keyval = explode ('=', $keyval);
162 if (count($keyval) == 2)
163 $myPost[$keyval[0]] = urldecode($keyval[1]);
164 }
165 // read the IPN message sent from PayPal and prepend 'cmd=_notify-validate'
166 $req = 'cmd=_notify-validate';
167 if (function_exists('get_magic_quotes_gpc')) {
168 $get_magic_quotes_exists = true;
169 }
170 foreach ($myPost as $key => $value) {
171 if ($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
172 $value = urlencode(stripslashes($value));
173 } else {
174 $value = urlencode($value);
175 }
176 $req .= "&$key=$value";
177 }
178
179 // Step 2: POST IPN data back to PayPal to validate
180 if(self::isTestMode()) {
181 $ch = curl_init('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr');
182 }else{
183 $ch = curl_init('https://ipnpb.paypal.com/cgi-bin/webscr');
184 }
185 curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
186 curl_setopt($ch, CURLOPT_POST, 1);
187 curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
188 curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
189 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
190 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
191 curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
192 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
193 // In wamp-like environments that do not come bundled with root authority certificates,
194 // please download 'cacert.pem' from "https://curl.haxx.se/docs/caextract.html" and set
195 // the directory path of the certificate as shown below:
196 // curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
197 if ( !($res = curl_exec($ch)) ) {
198 error_log("Got " . curl_error($ch) . " when processing IPN data");
199 curl_close($ch);
200 exit;
201 }
202 curl_close($ch);
203
204 // inspect IPN validation result and act accordingly
205 if (strcmp ($res, "VERIFIED") == 0) {
206 // The IPN is verified, process it
207 $id = $_POST['recurring_payment_id'];
208 $txn_type = $_POST["txn_type"];
209
210 $cancelTypes = array("recurring_payment_expired", "recurring_payment_expired", "recurring_payment_profile_cancel", "recurring_payment_suspended",
211 "recurring_payment_suspended_due_to_max_failed_payment");
212
213 $reference = DB::table('paypal_references')->where(
214 ['paypal_id' => $id]
215 )->first();
216
217 $user_id = $reference->params;
218 $now = DB::raw('NOW()');
219
220
221 if(isset($_POST["payment_cycle"])){
222 if(in_array($txn_type, $cancelTypes)){
223 DB::table('subscriptions')->where([
224 "user_id" => $id
225 ])->update([
226 'is_canceled'=> 1,
227 'updated_at'=>$now,
228 ]
229 );
230 }else if($txn_type == "recurring_payment_profile_created") {
231 //this is also for upgrades
232
233 $plan = self::subscriptionGetPlan($id);
234 $plan_internal_id = DB::table('subscription_plans')->where(
235 ['plan_type' => $plan]
236 )->first()->id;
237
238 $reference = DB::table('subscriptions')->where(
239 ['user_id' => $user_id]
240 )->first();
241
242
243 if ($reference != null) {
244 error_log("!= null");
245 DB::table('subscriptions')->where([
246 "user_id" => $user_id
247 ])->update([
248 'subscription_type' => $plan_internal_id,
249 'is_canceled' => 0,
250 'last_renewed_date' => $now,
251 'updated_at' => $now,
252 ]
253 );
254 } else {
255 DB::table('subscriptions')->insert([
256 'user_id' => $user_id,
257 'paypal_id' => $id,
258 'subscription_type' => $plan_internal_id,
259 'is_canceled' => 0,
260 'created_date' => $now,
261 'last_renewed_date' => $now,
262 'created_at' => $now,
263 'updated_at' => $now,
264 ]
265 );
266 }
267 }
268 }else{
269 //TODO WHAT TO DO WITH A CHARGE?
270
271 $amount = $_POST["mc_gross"];
272 }
273
274
275 } else if (strcmp ($res, "INVALID") == 0) {
276 // IPN invalid, log for manual investigation
277 error_log($res);
278 }
279 }
280
281 private static function getAccessToken(){
282 $response = self::doApiRequest("Basic " . base64_encode( env('CLIENT_ID').":". env('SECRET')),
283 "/v1/oauth2/token?grant_type=client_credentials");
284
285 return $response["access_token"];
286 }
287
288 private static function doApiRequest($token, $endpoint, $payload = [], $type = "application/x-www-form-urlencoded", $post = true)
289 {
290 if(self::isTestMode()){
291 $endpoint = "https://api.sandbox.paypal.com" . $endpoint;
292 }else{
293 $endpoint = "https://api.paypal.com" . $endpoint;
294 }
295 $ch = curl_init();
296
297 if($post) {
298 curl_setopt($ch, CURLOPT_POST, 1);
299 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
300 }
301
302 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
303 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
304 curl_setopt($ch, CURLOPT_URL, $endpoint);
305 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
306 curl_setopt($ch, CURLOPT_HTTPHEADER, [
307 'Content-Type: ' . $type,
308 'Authorization: ' . $token
309 ]);
310 $response = json_decode(curl_exec($ch), true);
311 $response_code = curl_getinfo ($ch)["http_code"];
312
313 if (curl_errno($ch) || $response_code > 299 || $response_code < 200)
314 {
315 $response["error_msg"] = curl_errno($ch);
316 $response["error"] = true;
317 $response["error_code"] = $response_code;
318 }
319 curl_close($ch);
320 return $response;
321 }
322
323
324}