diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 7932cb3a56af..f862caf91f7a 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -194,6 +194,7 @@ class Gateway extends StaticModel GatewayType::PAYPAL => ['refund' => false, 'token_billing' => false], GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false], GatewayType::VENMO => ['refund' => false, 'token_billing' => false], + GatewayType::PAYPAL_ADVANCED_CARDS => ['refund' => false, 'token_billing' => true], // GatewayType::SEPA => ['refund' => false, 'token_billing' => false], // GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false], // GatewayType::EPS => ['refund' => false, 'token_billing' => false], @@ -207,6 +208,7 @@ class Gateway extends StaticModel GatewayType::PAYPAL => ['refund' => false, 'token_billing' => false], GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false], GatewayType::VENMO => ['refund' => false, 'token_billing' => false], + GatewayType::PAYPAL_ADVANCED_CARDS => ['refund' => false, 'token_billing' => true], // GatewayType::SEPA => ['refund' => false, 'token_billing' => false], // GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false], // GatewayType::EPS => ['refund' => false, 'token_billing' => false], diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 473838f4e2d7..f30d5c97cb14 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -91,6 +91,8 @@ class GatewayType extends StaticModel public const PAYLATER = 28; + public const PAYPAL_ADVANCED_CARDS = 29; + public function gateway() { return $this->belongsTo(Gateway::class); @@ -158,6 +160,9 @@ class GatewayType extends StaticModel return ctrans('texts.mybank'); case self::PAYLATER: return ctrans('texts.paypal_paylater'); + case self::PAYPAL_ADVANCED_CARDS: + return ctrans('texts.credit_card'); + default: return ' '; } diff --git a/app/PaymentDrivers/PayPal/PayPalWebhook.php b/app/PaymentDrivers/PayPal/PayPalWebhook.php index 0d800d826de6..fa08542af0e1 100644 --- a/app/PaymentDrivers/PayPal/PayPalWebhook.php +++ b/app/PaymentDrivers/PayPal/PayPalWebhook.php @@ -399,3 +399,64 @@ class PayPalWebhook implements ShouldQueue } } */ + + +/** token created + * { + "id":"WH-1KN88282901968003-82E75604WM969463F", + "event_version":"1.0", + "create_time":"2022-08-15T14:13:48.978Z", + "resource_type":"payment_token", + "resource_version":"3.0", + "event_type":"VAULT.PAYMENT-TOKEN.CREATED", + "summary":"A payment token has been created.", + "resource":{ + "time_created":"2022-08-15T07:13:48.964PDT", + "links":[ + { + "href":"https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/9n6724m", + "rel":"self", + "method":"GET", + "encType":"application/json" + }, + { + "href":"https://api-m.sandbox.paypal.com/v3/vault/payment-tokens/9n6724m", + "rel":"delete", + "method":"DELETE", + "encType":"application/json" + } + ], + "id":"9n6724m", + "payment_source":{ + "card":{ + "last_digits":"1111", + "brand":"VISA", + "expiry":"2027-02", + "billing_address":{ + "address_line_1":"2211 N First Street", + "address_line_2":"17.3.160", + "admin_area_2":"San Jose", + "admin_area_1":"CA", + "postal_code":"95131", + "country_code":"US" + } + } + }, + "customer":{ + "id":"695922590" + } + }, + "links":[ + { + "href":"https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/WH-1KN88282901968003-82E75604WM969463F", + "rel":"self", + "method":"GET" + }, + { + "href":"https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/WH-1KN88282901968003-82E75604WM969463F/resend", + "rel":"resend", + "method":"POST" + } + ] + } + */ \ No newline at end of file diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index cdad6a880ea6..047f02df1578 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -52,6 +52,7 @@ class PayPalRestPaymentDriver extends BaseDriver 3 => 'paypal', 1 => 'card', 25 => 'venmo', + 29 => 'paypal_advanced_cards', // 9 => 'sepa', // 12 => 'bancontact', // 17 => 'eps', @@ -117,6 +118,7 @@ class PayPalRestPaymentDriver extends BaseDriver "3" => $method = PaymentType::PAYPAL, "25" => $method = PaymentType::VENMO, "28" => $method = PaymentType::PAY_LATER, + "29" => $method = PaymentType::CREDIT_CARD_OTHER, }; return $method; @@ -208,6 +210,10 @@ return render('gateways.paypal.pay', $data); } + public function processTokenPayment(array $response) { + + } + public function processPaymentResponse($request) { @@ -216,6 +222,9 @@ return render('gateways.paypal.pay', $data); $request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']); $response = json_decode($request['gateway_response'], true); + if(isset($response['gateway_response']['token']) && strlen($response['gateway_response']['token']) > 2) + return $this->processTokenPayment($response); + // nlog($response); //capture $orderID = $response['orderID']; @@ -272,6 +281,8 @@ return render('gateways.paypal.pay', $data); if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) { + nlog($response->json()); + $data = [ 'payment_type' => $this->getPaymentMethod($request->gateway_type_id), 'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'], @@ -281,8 +292,40 @@ return render('gateways.paypal.pay', $data); $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); + if ($request->has('store_card') && $request->input('store_card') === true) { + $payment_source = $response->json()['payment_source']; + + if(isset($payment_source['card']) && ($payment_source['card']['attributes']['vault']['status'] ?? false) && $payment_source['card']['attributes']['vault']['status'] == 'VAULTED'){ + + $last4 = $payment_source['card']['last_digits']; + $expiry = $payment_source['card']['expiry']; //'2025-01' + $expiry_meta = explode('-', $expiry); + $brand = $payment_source['card']['brand']; + + $payment_meta = new \stdClass(); + $payment_meta->exp_month = $expiry_meta[1] ?? ''; + $payment_meta->exp_year = $expiry_meta[0] ?? $expiry; + $payment_meta->brand = $brand; + $payment_meta->last4 = $last4; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $token = $payment_source['card']['attributes']['vault']['id']; // 09f28652d01257021 + $gateway_customer_reference = $payment_source['card']['attributes']['vault']['customer']['id']; //rbTHnLsZqE; + + $data['token'] = $token; + $data['payment_method_id'] = GatewayType::PAYPAL_ADVANCED_CARDS; + $data['payment_meta'] = $payment_meta; + $data['payment_method_id'] = GatewayType::CREDIT_CARD; + + $additional['gateway_customer_reference'] = $gateway_customer_reference; + + $this->storeGatewayToken($data, $additional); + + } + } + SystemLogger::dispatch( - ['response' => $response, 'data' => $data], + ['response' => $response->json(), 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_PAYPAL, @@ -329,32 +372,40 @@ return render('gateways.paypal.pay', $data); private function getPaymentSource(): array { - - if($this->gateway_type_id == 1) { + //@todo - roll back here for advanced payments vs hosted card fields. + if($this->gateway_type_id == GatewayType::PAYPAL_ADVANCED_CARDS) { return [ "card" => [ "attributes" => [ "verification" => [ "method" => "SCA_WHEN_REQUIRED", //SCA_ALWAYS + // "method" => "SCA_ALWAYS", //SCA_ALWAYS + ], + "vault" => [ + "store_in_vault" => "ON_SUCCESS", //must listen to this webhook - VAULT.PAYMENT-TOKEN.CREATED webhook. ], ], - "name" => $this->client->present()->primary_contact_name(), - "email_address" => $this->client->present()->email(), - "address" => [ - "address_line_1" => $this->client->address1, - "address_line_2" => $this->client->address2, - "admin_area_2" => $this->client->city, - "admin_area_1" => $this->client->state, - "postal_code" => $this->client->postal_code, - "country_code" => $this->client->country->iso_3166_2, - ], - "experience_context" => [ - "user_action" => "PAY_NOW" - ], + "experience_context" => [ + "shipping_preference" => "SET_PROVIDED_ADDRESS" + ], + // "name" => $this->client->present()->primary_contact_name(), + // "email_address" => $this->client->present()->email(), + // "address" => [ + // "address_line_1" => $this->client->address1, + // "address_line_2" => $this->client->address2, + // "admin_area_2" => $this->client->city, + // "admin_area_1" => $this->client->state, + // "postal_code" => $this->client->postal_code, + // "country_code" => $this->client->country->iso_3166_2, + // ], + // "experience_context" => [ + // "user_action" => "PAY_NOW" + // ], "stored_credential" => [ - "payment_initiator" => "MERCHANT", //"CUSTOMER" who initiated the transaction? - "payment_type" => "UNSCHEDULED", + // "payment_initiator" => "MERCHANT", //"CUSTOMER" who initiated the transaction? + "payment_initiator" => "CUSTOMER", //"" who initiated the transaction? + "payment_type" => "UNSCHEDULED", //UNSCHEDULED "usage"=> "DERIVED", ], ], @@ -406,9 +457,9 @@ return render('gateways.paypal.pay', $data); "custom_id" => $this->payment_hash->hash, "description" => ctrans('texts.invoice_number') . '# ' . $invoice->number, "invoice_id" => $invoice->number, - "payment_instruction" => [ - "disbursement_mode" => "INSTANT", - ], + // "payment_instruction" => [ + // "disbursement_mode" => "INSTANT", + // ], $this->getShippingAddress(), "amount" => [ "value" => (string) $data['amount_with_fee'], @@ -440,6 +491,8 @@ return render('gateways.paypal.pay', $data); $order['purchase_units'][0]["shipping"] = $shipping; } + nlog($order); + $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); // nlog($r->json()); @@ -463,7 +516,11 @@ return render('gateways.paypal.pay', $data); ], ] - : null; + : [ + "name" => [ + "full_name" => $this->client->present()->name() + ] + ]; } diff --git a/lang/en/texts.php b/lang/en/texts.php index e542e667e9d4..970d5f6c832a 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5296,6 +5296,7 @@ $lang = array( 'rappen_rounding' => 'Rappen Rounding', 'rappen_rounding_help' => 'Round amount to 5 cents', 'assign_group' => 'Assign group', + 'paypal_advanced_cards' => 'Advanced Card Payments', ); return $lang; diff --git a/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php b/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php index aea037e36670..dc40aa436bd8 100644 --- a/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php +++ b/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php @@ -12,26 +12,48 @@ + + + @include('portal.ninja2020.gateways.includes.payment_details') +
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')]) + @if (count($tokens) > 0) + @foreach ($tokens as $token) + + @endforeach + @endisset + + + + @endcomponent +