jenkins-bot has submitted this change and it was merged.

Change subject: smashpig paypal listener
......................................................................


smashpig paypal listener

Bug: T141654
Change-Id: Ic4654eda51d860293b9177d101454a2d9efd0460
---
M Core/Http/Request.php
M Core/QueueConsumers/JobQueueConsumer.php
A PaymentProviders/PayPal/Job.php
A PaymentProviders/PayPal/Listener.php
A PaymentProviders/PayPal/Message.php
A PaymentProviders/PayPal/Tests/Data/subscr_payment.json
A PaymentProviders/PayPal/Tests/Data/web_accept.json
A PaymentProviders/PayPal/Tests/PayPalTestConfiguration.php
A PaymentProviders/PayPal/Tests/config_test.yaml
A PaymentProviders/PayPal/Tests/phpunit/CaptureIncomingMessageTest.php
M SmashPig.yaml
M Tests/MockDataStore.php
M phpunit.xml
13 files changed, 392 insertions(+), 4 deletions(-)

Approvals:
  Awight: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Core/Http/Request.php b/Core/Http/Request.php
index 70285e2..8c2011b 100644
--- a/Core/Http/Request.php
+++ b/Core/Http/Request.php
@@ -7,6 +7,8 @@
                return file_get_contents( 'php://input' );
        }
 
+       // XXX It's weird that we wrap the symfony helper just to export the
+       // request as an array. Worth the extra layer of indirection?
        public function getValues() {
                return $this->query->all() +
                        $this->attributes->all() +
diff --git a/Core/QueueConsumers/JobQueueConsumer.php 
b/Core/QueueConsumers/JobQueueConsumer.php
index d056167..530a719 100644
--- a/Core/QueueConsumers/JobQueueConsumer.php
+++ b/Core/QueueConsumers/JobQueueConsumer.php
@@ -23,6 +23,9 @@
                }
 
                // TODO: encapsulate the reconstitution step elsewhere.
+               // FIXME It seems bad that these objects indiscriminately store
+               // things as properties. The message is mingled with stuff like
+               // php-message-class. Could collide.
                $className = $jobMessage['php-message-class'];
                $jsonMessage = json_encode( $jobMessage );
                $jobObj = KeyedOpaqueStorableObject::fromJsonProxy( $className, 
$jsonMessage );
diff --git a/PaymentProviders/PayPal/Job.php b/PaymentProviders/PayPal/Job.php
new file mode 100644
index 0000000..fd38d57
--- /dev/null
+++ b/PaymentProviders/PayPal/Job.php
@@ -0,0 +1,84 @@
+<?php namespace SmashPig\PaymentProviders\PayPal;
+
+use SmashPig\Core\Configuration;
+use SmashPig\Core\Jobs\RunnableJob;
+
+class Job extends RunnableJob {
+
+       public $payload;
+
+       public function execute() {
+               $this->config = Configuration::getDefaultConfig();
+
+               // TODO some pending-merge stuff?
+
+               // Verify message with paypal.
+
+               $url = $this->config->val( 'endpoints/listener/postback-url' );
+
+               // XXX Why does everything get made into objects?
+               $request = (array)$this->payload;
+               $request['cmd'] = '_notify-validate';
+
+               $ch = curl_init();
+               curl_setopt( $ch, CURLOPT_URL,
+                       $this->config->val( 'endpoints/listener/postback-url' ) 
);
+               curl_setopt( $ch, CURLOPT_HEADER, 0 );
+               curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 0 );
+               curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
+               curl_setopt( $ch, CURLOPT_POST, 1 );
+               curl_setopt( $ch, CURLOPT_POSTFIELDS, $request );
+
+               $data = curl_exec( $ch );
+
+               // FIXME test server keeps returning INVALID
+               // if ( $data !== 'VERIFIED' ) {
+               //      throw new \Exception( 'PayPal message verify fail: ' . 
$data );
+               // }
+
+               // Determine message type.
+
+               $txn_type = $request['txn_type'];
+
+               $msg_type = null;
+               foreach ( $this->config->val( 'messages' ) as $type => $conf ) {
+                       if ( in_array( $txn_type, $conf['txn_types'] ) ) {
+                               $msg_type = $type;
+                       }
+               }
+
+               if ( ! $msg_type ) {
+                       throw new \Exception( 'Invalid PayPal message type: ' . 
$txn_type );
+               }
+
+               // Transform into new message.
+
+               // FIXME this could just be an array, but we need compat with
+               // keyedopaque* until activemq goes away
+               $new_msg = new Message;
+               // FIXME hack because the recurring consumer doesn't want
+               // a normalized message
+               if ( $msg_type === 'recurring' ) {
+                       foreach ( $request as $key => $val ) {
+                               $new_msg->$key = $val;
+                       }
+               } else {
+                       $map = $this->config->val( 'var_map' );
+                       foreach ( $map as $rx => $tx ) {
+                               if ( array_key_exists( $rx, $request ) ) {
+                                       $new_msg->$tx = $request[$rx];
+                               }
+                       }
+               }
+
+               // hax
+               $new_msg->date = strtotime( $new_msg->date );
+               $new_msg->gateway = 'paypal';
+
+               // Save to appropriate queue.
+
+               $this->config->object( 'data-store/' . $msg_type )
+                       ->push( $new_msg );
+
+       }
+}
diff --git a/PaymentProviders/PayPal/Listener.php 
b/PaymentProviders/PayPal/Listener.php
new file mode 100644
index 0000000..62cece9
--- /dev/null
+++ b/PaymentProviders/PayPal/Listener.php
@@ -0,0 +1,19 @@
+<?php namespace SmashPig\PaymentProviders\PayPal;
+
+use SmashPig\Core\Configuration;
+use SmashPig\Core\Http\IHttpActionHandler;
+use SmashPig\Core\Http\Response;
+use SmashPig\Core\Http\Request;
+
+class Listener implements IHttpActionHandler {
+
+       public function execute( Request $request, Response $response ) {
+               $this->config = Configuration::getDefaultConfig();
+               // Dump the request right into the queue with no validation.
+               $job = new Job;
+               $job->payload = $request->getValues();
+               $job->{'php-message-class'} = 
'SmashPig\PaymentProviders\PayPal\Job';
+               $this->config->object( 'data-store/jobs-paypal' )->push( $job );
+       }
+
+}
diff --git a/PaymentProviders/PayPal/Message.php 
b/PaymentProviders/PayPal/Message.php
new file mode 100644
index 0000000..1787733
--- /dev/null
+++ b/PaymentProviders/PayPal/Message.php
@@ -0,0 +1,11 @@
+<?php namespace SmashPig\PaymentProviders\PayPal;
+
+use SmashPig\Core\DataStores\KeyedOpaqueStorableObject;
+
+class Message extends KeyedOpaqueStorableObject {
+#  _ __   ___         ___  _ __
+# | '_ \ / _ \ _____ / _ \| '_ \
+# | | | | (_) |_____| (_) | |_) |
+# |_| |_|\___/       \___/| .__/
+#                         |_|
+}
diff --git a/PaymentProviders/PayPal/Tests/Data/subscr_payment.json 
b/PaymentProviders/PayPal/Tests/Data/subscr_payment.json
new file mode 100644
index 0000000..1543a13
--- /dev/null
+++ b/PaymentProviders/PayPal/Tests/Data/subscr_payment.json
@@ -0,0 +1,32 @@
+{
+    "transaction_subject": "Donation to the Wikimedia Foundation"
+    "payment_date": "01:00:26 Aug 13, 2016 PDT"
+    "txn_type": "subscr_payment"
+    "subscr_id": "S-WWWWWWWWWWWWWWWWW"
+    "last_name": "Doe"
+    "residence_country": "US"
+    "item_name": "Donation to the Wikimedia Foundation"
+    "payment_gross": "10.00"
+    "mc_currency": "USD"
+    "business": "nob...@wikimedia.org"
+    "payment_type": "instant"
+    "protection_eligibility": "Ineligible"
+    "verify_sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+    "payer_status": "unverified"
+    "payer_email": "nob...@example.com"
+    "txn_id": "YYYYYYYYYYYYYYYYY"
+    "receiver_email": "nob...@wikimedia.org"
+    "first_name": "John"
+    "payer_id": "ZZZZZZZZZZZZZ"
+    "receiver_id": "AAAAAAAAAAAAA"
+    "item_number": "DONATE"
+    "payment_status": "Completed"
+    "payment_fee": "0.42"
+    "mc_fee": "0.42"
+    "mc_gross": "10.00"
+    "custom": "33333333"
+    "charset": "UTF-8"
+    "notify_version": "3.8"
+    "ipn_track_id": "8888888888888"
+}
+
diff --git a/PaymentProviders/PayPal/Tests/Data/web_accept.json 
b/PaymentProviders/PayPal/Tests/Data/web_accept.json
new file mode 100644
index 0000000..feb5131
--- /dev/null
+++ b/PaymentProviders/PayPal/Tests/Data/web_accept.json
@@ -0,0 +1,41 @@
+{
+    "mc_gross": "7.00",
+    "protection_eligibility": "Eligible",
+    "address_status": "unconfirmed",
+    "payer_id": "AAAAAAAAAAAAA",
+    "tax": "0.00",
+    "address_street": "hell no",
+    "payment_date": "23:28:47 Aug 12, 2016 PDT",
+    "payment_status": "Completed",
+    "charset": "UTF-8",
+    "address_zip": "8032",
+    "first_name": "thanks",
+    "mc_fee": "0.44",
+    "address_country_code": "CH",
+    "address_name": "where indeed",
+    "notify_version": "3.8",
+    "custom": "33333333",
+    "payer_status": "verified",
+    "business": "nob...@wikimedia.org",
+    "address_country": "Switzerland",
+    "address_city": "Zurich",
+    "quantity": "0",
+    "verify_sign": "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
+    "payer_email": "no...@example.com",
+    "txn_id": "JJJJJJJJJJJJJJJJJ",
+    "payment_type": "instant",
+    "payer_business_name": "where indeed",
+    "last_name": "bobby",
+    "address_state": "",
+    "receiver_email": "nob...@wikimedia.org",
+    "payment_fee": "0.44",
+    "receiver_id": "EEEEEEEEEEEEE",
+    "txn_type": "web_accept",
+    "item_name": "Donation to the Wikimedia Foundation",
+    "mc_currency": "USD",
+    "item_number": "DONATE",
+    "residence_country": "CH",
+    "transaction_subject": "33333333",
+    "payment_gross": "7.00",
+    "ipn_track_id": "ddddddddddddd"
+}
diff --git a/PaymentProviders/PayPal/Tests/PayPalTestConfiguration.php 
b/PaymentProviders/PayPal/Tests/PayPalTestConfiguration.php
new file mode 100644
index 0000000..e8ea4f7
--- /dev/null
+++ b/PaymentProviders/PayPal/Tests/PayPalTestConfiguration.php
@@ -0,0 +1,15 @@
+<?php
+namespace SmashPig\PaymentProviders\PayPal\Tests;
+
+use SmashPig\Core\Configuration;
+
+class PayPalTestConfiguration extends Configuration {
+
+       public static function get () {
+               return self::createForViewWithOverrideFile(
+                       'paypal',
+                       __DIR__ . '/config_test.yaml'
+               );
+       }
+
+}
diff --git a/PaymentProviders/PayPal/Tests/config_test.yaml 
b/PaymentProviders/PayPal/Tests/config_test.yaml
new file mode 100644
index 0000000..f3da12e
--- /dev/null
+++ b/PaymentProviders/PayPal/Tests/config_test.yaml
@@ -0,0 +1,32 @@
+paypal:
+    data-store:
+        verified:
+            class: PHPQueue\Backend\PDO
+            constructor-parameters:
+                -
+                    connection_string: 'sqlite::memory:'
+                    queue: 'verified'
+
+        jobs-paypal:
+            class: PHPQueue\Backend\PDO
+            constructor-parameters:
+                -
+                    connection_string: 'sqlite::memory:'
+                    queue: 'jobs-paypal'
+
+        recurring:
+            class: SmashPig\Tests\MockDataStore
+            constructor-parameters: []
+
+        refund:
+            class: SmashPig\Tests\MockDataStore
+            constructor-parameters: []
+
+        verified:
+            class: SmashPig\Tests\MockDataStore
+            constructor-parameters: []
+
+        pending-db:
+            class: PDO
+            constructor-parameters:
+                - 'sqlite::memory:'
diff --git 
a/PaymentProviders/PayPal/Tests/phpunit/CaptureIncomingMessageTest.php 
b/PaymentProviders/PayPal/Tests/phpunit/CaptureIncomingMessageTest.php
new file mode 100644
index 0000000..36fa095
--- /dev/null
+++ b/PaymentProviders/PayPal/Tests/phpunit/CaptureIncomingMessageTest.php
@@ -0,0 +1,81 @@
+<?php
+namespace SmashPig\PaymentProviders\PayPal\Tests;
+
+use SmashPig\Core\Configuration;
+use SmashPig\Core\Context;
+use SmashPig\Core\QueueConsumers\BaseQueueConsumer;
+use SmashPig\PaymentProviders\PayPal\Listener;
+use SmashPig\PaymentProviders\PayPal\Job;
+use SmashPig\PaymentProviders\PayPal\Tests\PayPalTestConfiguration;
+use SmashPig\Tests\BaseSmashPigUnitTestCase;
+use SmashPig\Core\Http\Response;
+use SmashPig\Core\Http\Request;
+use SmashPig\Core\DataStores\KeyedOpaqueStorableObject;
+
+/**
+ * Test the IPN listener which receives messages, stores and processes them.
+ */
+class CaptureIncomingMessageTest extends BaseSmashPigUnitTestCase {
+
+       /**
+        * @var Configuration
+        */
+       public $config;
+       private $verified_msg;
+
+       public function setUp() {
+               parent::setUp();
+               $this->config = PayPalTestConfiguration::get();
+               Context::initWithLogger( $this->config );
+               $this->verified_msg = json_decode(
+                       file_get_contents( __DIR__ . '/../Data/web_accept.json' 
),
+                       true
+               );
+       }
+
+       private function capture ( $msg ) {
+               $request = new Request( $msg );
+               $response = new Response;
+               $listener = new Listener;
+               $listener->execute( $request, $response );
+       }
+
+       public function testCapture() {
+
+               $this->capture( $this->verified_msg );
+
+               // TODO why get it from BaseQueueConsumer instead of config?
+               $jobQueue = BaseQueueConsumer::getQueue( 'jobs-paypal' );
+               $jobMessage = $jobQueue->pop();
+
+               $this->assertEquals( $jobMessage['php-message-class'],
+                       'SmashPig\PaymentProviders\PayPal\Job' );
+
+               $this->assertEquals( $jobMessage['payload'], 
$this->verified_msg );
+
+       }
+
+       public function testConsume () {
+
+               $this->capture( $this->verified_msg );
+
+               // TODO DRY?
+               $jobQueue = BaseQueueConsumer::getQueue( 'jobs-paypal' );
+               $jobMessage = $jobQueue->pop();
+
+               $job = KeyedOpaqueStorableObject::fromJsonProxy(
+                       $jobMessage['php-message-class'],
+                       json_encode( $jobMessage )
+               );
+
+               $job->execute();
+
+               $verifiedQueue = $this->config->object( 'data-store/verified' );
+               $verifiedMessage = $verifiedQueue->pop();
+
+               // TODO can we verify that it looks right after 
transmogrification? might be
+               // a job for the crm consumer
+               $this->assertTrue( ! empty( $verifiedMessage ) );
+
+       }
+}
diff --git a/SmashPig.yaml b/SmashPig.yaml
index 8f0f69d..4f7ea35 100644
--- a/SmashPig.yaml
+++ b/SmashPig.yaml
@@ -352,10 +352,74 @@
     charset: iso-8859-1
 
 paypal:
-    listener:
-        # For testing purposes override this entry with
-        # postback-url: https://www.sandbox.paypal.com/cgi-bin/webscr
-        postback-url: https://www.paypal.com/cgi-bin/webscr
+    endpoints:
+        listener:
+            class: SmashPig\PaymentProviders\PayPal\Listener
+            # For testing purposes override this entry with
+            # postback-url: https://www.sandbox.paypal.com/cgi-bin/webscr
+            postback-url: https://www.sandbox.paypal.com/cgi-bin/webscr
+
+    data-store:
+        jobs-paypal:
+            class: PHPQueue\Backend\Predis
+            constructor-parameters:
+                -
+                    <<: *REDIS
+
+    var_map:
+        payment_date: date # needs strtotime(payment_date)
+        txn_type: txn_type
+        payment_status: payment_status
+        parent_txn_id: gateway_parent_id
+        txn_id: gateway_refund_id
+        mc_currency: gross_currency
+        reason_code: type
+        test_ipn:  # signals test mode
+        custom: contribution_tracking_id
+        payer_email: email
+        first_name: first_name
+        last_name: last_name
+        # FIXME this used to get split up
+        address_street: supplemental_address_1
+        address_city: city_2
+        address_state: state_province_2
+        address_country_code: country_2
+        address_zip: postal_code_2
+        # FIXME this too
+        address_name: supplemental_address_2
+        gateway: gateway
+        mc_gross: gross
+        mc_fee: fee
+
+    messages:
+        verified:
+            valid_statuses: # TODO is this message type agnostic?
+                - Completed
+                - Reversed
+            txn_types:
+                - adjustment
+                - cart
+                - new_case
+                - send_money
+                - web_accept
+                - merch_pmt
+                - express_checkout
+                - masspay
+                - virtual_terminal
+        recurring:
+            txn_types:
+                - recurring_payment_profile_created
+                - subscr_cancel
+                - subscr_eot
+                - subscr_failed
+                - subscr_modify
+                - subscr_signup
+                # the following mean we got money \o/
+                - recurring_payment
+                - subscr_payment
+        refund:
+            txn_types:
+                - refund
 
 globalcollect:
     actions:
diff --git a/Tests/MockDataStore.php b/Tests/MockDataStore.php
index 3789b8a..14ae370 100644
--- a/Tests/MockDataStore.php
+++ b/Tests/MockDataStore.php
@@ -31,6 +31,7 @@
         */
        public function addObject( KeyedOpaqueStorableObject $obj ) {
                $keys = $obj->getObjectKeys();
+               // FIXME magic string is magic
                if ( !array_key_exists( 'correlationId', $keys ) ) {
                        throw new DataStoreException(
                                "Required property correlationId was not 
exposed."
diff --git a/phpunit.xml b/phpunit.xml
index 452a358..9fde5f5 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -16,5 +16,8 @@
       <testsuite name="AstroPay tests">
         <directory>PaymentProviders/AstroPay/Tests/phpunit</directory>
       </testsuite>
+      <testsuite name="PayPal tests">
+        <directory>PaymentProviders/PayPal/Tests/phpunit</directory>
+      </testsuite>
     </testsuites>
 </phpunit>

-- 
To view, visit https://gerrit.wikimedia.org/r/305761
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic4654eda51d860293b9177d101454a2d9efd0460
Gerrit-PatchSet: 32
Gerrit-Project: wikimedia/fundraising/SmashPig
Gerrit-Branch: master
Gerrit-Owner: Cdentinger <cdentin...@wikimedia.org>
Gerrit-Reviewer: Awight <awi...@wikimedia.org>
Gerrit-Reviewer: Cdentinger <cdentin...@wikimedia.org>
Gerrit-Reviewer: Ejegg <eeggles...@wikimedia.org>
Gerrit-Reviewer: Siebrand <siebr...@kitano.nl>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to