_paymentProcessorID = $this->paymentProcessorCreate(['is_test' => 0]); $this->_contactID = $this->individualCreate(); $contributionPage = $this->callAPISuccess('contribution_page', 'create', [ 'title' => "Test Contribution Page", 'financial_type_id' => $this->_financialTypeID, 'currency' => 'USD', 'payment_processor' => $this->_paymentProcessorID, ]); $this->_contributionPageID = $contributionPage['id']; } /** * Tear down function. */ public function tearDown() { $this->quickCleanUpFinancialEntities(); } /** * Test IPN response updates contribution_recur & contribution for first & second contribution. * * The scenario is that a pending contribution exists and the first call will update it to completed. * The second will create a new contribution. */ public function testIPNPaymentRecurSuccess() { $this->setupRecurringPaymentProcessorTransaction(); global $_GET; $_GET = $this->getPaypalProRecurTransaction(); $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurTransaction()); $paypalIPN->main(); $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]); $this->assertEquals(1, $contribution['contribution_status_id']); $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']); // source gets set by processor $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:"); $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); $this->assertEquals(5, $contributionRecur['contribution_status_id']); $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurSubsequentTransaction()); $paypalIPN->main(); $contribution = $this->callAPISuccess('contribution', 'get', [ 'contribution_recur_id' => $this->_contributionRecurID, 'sequential' => 1, ]); $this->assertEquals(2, $contribution['count']); $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']); $this->assertEquals('Debit Card', $contribution['values'][1]['payment_instrument']); } /** * Test IPN response updates contribution_recur & contribution for first & second contribution. * * @throws \CRM_Core_Exception */ public function testIPNPaymentMembershipRecurSuccess() { $durationUnit = 'year'; $this->setupMembershipRecurringPaymentProcessorTransaction(['duration_unit' => $durationUnit, 'frequency_unit' => $durationUnit]); $this->callAPISuccessGetSingle('membership_payment', []); $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurTransaction()); $paypalIPN->main(); $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]); $membershipEndDate = $this->callAPISuccessGetValue('membership', ['return' => 'end_date']); $this->assertEquals(1, $contribution['contribution_status_id']); $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']); // source gets set by processor $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:"); $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); $this->assertEquals(5, $contributionRecur['contribution_status_id']); $paypalIPN = new CRM_Core_Payment_PaypalProIPN($this->getPaypalProRecurSubsequentTransaction()); $paypalIPN->main(); $renewedMembershipEndDate = $this->membershipRenewalDate($durationUnit, $membershipEndDate); $this->assertEquals($renewedMembershipEndDate, $this->callAPISuccessGetValue('membership', ['return' => 'end_date'])); $contribution = $this->callAPISuccess('contribution', 'get', [ 'contribution_recur_id' => $this->_contributionRecurID, 'sequential' => 1, ]); $this->assertEquals(2, $contribution['count']); $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']); $this->callAPISuccessGetCount('line_item', [ 'entity_id' => $this->ids['membership'], 'entity_table' => 'civicrm_membership', ], 2); $this->callAPISuccessGetSingle('line_item', [ 'contribution_id' => $contribution['values'][1]['id'], 'entity_table' => 'civicrm_membership', ]); $this->callAPISuccessGetSingle('membership_payment', ['contribution_id' => $contribution['values'][1]['id']]); } /** * CRM-13743 test IPN edge case where the first transaction fails and the second succeeds. * * We are checking that the created contribution has the same date as IPN says it should * Note that only one contribution will be created (no evidence of the failed contribution is left) * It seems likely that may change in future & this test will start failing (I point this out in the hope it * will help future debuggers) */ public function testIPNPaymentCRM13743() { $this->setupRecurringPaymentProcessorTransaction(); $firstPaymentParams = $this->getPaypalProRecurTransaction(); $firstPaymentParams['txn_type'] = 'recurring_payment_failed'; $paypalIPN = new CRM_Core_Payment_PayPalProIPN($firstPaymentParams); $paypalIPN->main(); $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]); $this->assertEquals(2, $contribution['contribution_status_id']); $this->assertEquals('', $contribution['trxn_id']); $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); $this->assertEquals(2, $contributionRecur['contribution_status_id']); $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurSubsequentTransaction()); $paypalIPN->main(); $contribution = $this->callAPISuccess('contribution', 'get', [ 'contribution_recur_id' => $this->_contributionRecurID, 'sequential' => 1, ]); $this->assertEquals(1, $contribution['count']); $this->assertEquals('secondone', $contribution['values'][0]['trxn_id']); $this->assertEquals(strtotime('03:59:05 Jul 14, 2013 PDT'), strtotime($contribution['values'][0]['receive_date'])); } /** * Check a payment express IPN call does not throw any errors. * * At this stage nothing it supposed to happen so it's a pretty blunt test * but at least it should be e-notice free * The browser interaction will update Paypal express payments * The ipn code redirects POSTs to paypal pro & GETs to paypal std but the * documentation (https://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/ipnguide.pdf) * implies only POSTS are sent server to server. * So, it's likely Paypal Std IPNs aren't working. * However, for Paypal Pro users payment express transactions can't work as they don't hold the component * which is required for them to be handled by either the Pro or Express class * * So, the point of this test is simply to ensure it fails in a known way */ public function testIPNPaymentExpressNoError() { $this->setupRecurringPaymentProcessorTransaction(); $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalExpressTransactionIPN()); $paypalIPN->main(); $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]); // no change $this->assertEquals(2, $contribution['contribution_status_id']); } /** * Get PaymentExpress IPN for a single transaction. * @return array * array representing a Paypal IPN POST */ public function getPaypalExpressTransactionIPN() { return [ 'mc_gross' => '200.00', 'invoice' => $this->_invoiceID, 'protection_eligibility' => 'Eligible', 'address_status' => 'confirmer', 'payer_id' => 'ZYXHBZSULPQE3', 'tax' => '0.00', 'address_street' => '13 Streety Street', 'payment_rate' => '03:32:12 Jul 29, 2013 PDT', 'payment_status' => 'Completed', 'charset' => 'windows-1252', 'address_zip' => '90210', 'first_name' => 'Mary-Jane', 'mc_fee' => '4.70', 'address_country_core' => 'US', 'address_name' => 'Mary-Jane', 'notify_version' => '3.7', 'custom' => '', 'payer_status' => 'unverified', 'address_country' => 'UNITED STATES', 'address_city' => 'Portland', 'quantity' => '1', 'verify_sign' => 'AUyUU3IMAvssa3j4KorlbLnfr.9.AW7GX-sL7Ts1brCHvn13npvO-pqf', 'payer_email' => 'mary@nowhere.com', 'txn_id' => '3X9131350B932393N', 'payment_type' => 'instant', 'last_name' => 'Bob', 'address_state' => 'ME', 'receiver_email' => 'email@civicrm.org', 'payment_fee' => '4.70', 'received_id' => 'GUH3W7BJLGTY3', 'txn_type' => 'express_checkout', 'item_name' => '', 'mc_currency' => 'USD', 'item_number' => '', 'residence_country' => 'US', 'handling_amount' => '0.00', 'transaction_subject' => '', 'payment_gross' => '200.00', 'shipping' => '0.00', 'ipn_track_id' => '5r27c2e31rl7c', 'is_unit_test' => TRUE, ]; } /** * Get IPN results from follow on IPN transactions. * @return array * array representing a Paypal IPN POST */ public function getSubsequentPaypalExpressTransaction() { return [ 'mc_gross' => '5.00', 'period_type' => ' Regular', 'outstanding_balance' => '0.00', 'next_payment_date' => '03:00:00 Aug 14, 2013 PDT', 'protection_eligibility' => 'Eligible', 'payment_cycle' => 'Monthly', 'address_status' => 'confirmed', 'tax' => '0.00', 'payer_id' => 'ACRAM59AAS2E4', 'address_street' => '54 Soul Street', 'payment_date' => '03:58:39 Jul 14, 2013 PDT', 'payment_status' => 'Completed', 'product_name' => '5 Per 1 month', 'charset' => 'windows-1252', 'rp_invoice_id' => 'i=' . $this->_invoiceID . '&m=&c=&r=&b=&p=' . $this->_contributionPageID, 'recurring_payment_id' => 'I-3EEUC094KYQW', 'address_zip' => '90210', 'first_name' => 'Alanna', 'mc_fee' => '0.41', 'address_country_code' => 'US', 'address_name' => 'Alanna Morrissette', 'notify_version' => '3.7', 'amount_per_cycle' => '5.00', 'payer_status' => 'unverified', 'currency_code' => 'USD', 'business' => 'mpa@example.com', 'address_country' => 'UNITED STATES', 'address_city' => 'Limestone', 'verify_sign' => 'AXi4DULbes8quzIiq2YNsdTJH5ciPPPzG9PcQvkQg4BjfvWi8aY9GgDb', 'payer_email' => 'passport45051@yahoo.com', 'initial_payment_amount' => '0.00', 'profile_status' => 'Active', 'amount' => '5.00', 'txn_id' => '03W6561902100533N', 'payment_type' => 'instant', 'last_name' => 'Morrissette', 'address_state' => 'ME', 'receiver_email' => 'info@example.com', 'payment_fee' => '0.41', 'receiver_id' => 'GTH8P7UQWWTY6', 'txn_type' => 'recurring_payment', 'mc_currency' => 'USD', 'residence_country' => 'US', 'transaction_subject' => '5 Per 1 month', 'payment_gross' => '5.00', 'shipping' => '0.00', 'product_type' => '1', 'time_created' => '12:02:25 May 14, 2013 PDT', 'ipn_track_id' => '912e5010eb5a6', ]; } /** * Get IPN style details for an incoming recurring transaction. */ public function getPaypalProRecurTransaction() { return [ 'amount' => '15.00', 'initial_payment_amount' => '0.00', 'profile_status' => 'Active', 'payer_id' => '4NHUTA7ZUE92C', 'product_type' => '1', 'ipn_track_id' => '30171ad0afe3g', 'outstanding_balance' => '0.00', 'shipping' => '0.00', 'charset' => 'windows-1252', 'period_type' => ' Regular', 'payment_gross' => '15.00', 'currency_code' => 'USD', 'receipt_id' => '1428-3355-5949-8495', 'verify_sign' => 'AoPC4BjkCyDFEXbSkoZcgqH3hpacA3RXyCD10axGfqyaRhHqwz1UZzX7', 'payment_cycle' => 'Monthly', 'txn_type' => 'recurring_payment', 'receiver_id' => 'GWE8P7BJVLMY6', 'payment_fee' => '0.63', 'mc_currency' => 'USD', 'transaction_subject' => '', 'protection_eligibility' => 'Ineligible', 'payer_status' => 'unverified', 'first_name' => 'Robert', 'product_name' => ' => 15 Per 1 month', 'amount_per_cycle' => '15.00', 'mc_gross' => '15.00', 'payment_date' => '03:59:05 Jul 14, 2013 PDT', 'rp_invoice_id' => 'i=' . $this->_invoiceID . '&m=contribute&c=' . $this->_contactID . '&r=' . $this->_contributionRecurID . '&b=' . $this->_contributionID . '&p=null', 'payment_status' => 'Completed', 'business' => 'nowhere@civicrm.org', 'last_name' => 'Roberty', 'txn_id' => '8XA571746W2698126', 'mc_fee' => '0.63', 'time_created' => '14 => 51 => 55 Feb 14, 2013 PST', 'resend' => 'true', 'payment_type' => 'instant', 'notify_version' => '3.7', 'recurring_payment_id' => 'I-8XHAKBG12SFP', 'receiver_email' => 'nil@civicrm.org', 'next_payment_date' => '03:00:00 Aug 14, 2013 PDT', 'tax' => '0.00', 'residence_country' => 'US', ]; } /** * Get IPN-style details for a second incoming transaction. * * @return array */ public function getPaypalProRecurSubsequentTransaction() { return array_merge($this->getPaypalProRecurTransaction(), ['txn_id' => 'secondone']); } /** * Test IPN response update for a paypal express profile creation confirmation. */ public function testIPNPaymentExpressRecurSuccess() { $this->setupRecurringPaymentProcessorTransaction(['processor_id' => '']); $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalExpressRecurSubscriptionConfirmation()); $paypalIPN->main(); $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); $this->assertEquals('I-JW77S1PY2032', $contributionRecur['processor_id']); } /** * Get response consistent with creating a new profile. * * @return array */ public function getPaypalExpressRecurSubscriptionConfirmation() { return [ 'payment_cycle' => 'Monthly', 'txn_type' => 'recurring_payment_profile_created', 'last_name' => 'buyer', 'next_payment_date' => '03:00:00 May 09, 2018 PDT', 'residence_country' => 'GB', 'initial_payment_amount' => '0.00', 'rp_invoice_id' => 'i=' . $this->_invoiceID . '&m=&c=' . $this->_contributionID . '&r=' . $this->_contributionRecurID . '&b=' . $this->_contactID . '&p=' . $this->_contributionPageID, 'currency_code' => 'GBP', 'time_created' => '12:39:01 May 09, 2018 PDT', 'verify_sign' => 'AUg223oCjn4HgJXKkrICawXQ3fyUA2gAd1.f1IPJ4r.9sln-nWcB-EJG', 'period_type' => 'Regular', 'payer_status' => 'verified', 'test_ipn' => '1', 'tax' => '0.00', 'payer_email' => 'payer@example.com', 'first_name' => 'test', 'receiver_email' => 'shop@example.com', 'payer_id' => 'BWXXXM8111HDS', 'product_type' => 1, 'shipping' => '0.00', 'amount_per_cycle' => '6.00', 'profile_status' => 'Active', 'charset' => 'windows-1252', 'notify_version' => '3.9', 'amount' => '6.00', 'outstanding_balance' => '0.00', 'recurring_payment_id' => 'I-JW77S1PY2032', 'product_name' => '6 Per 1 month', 'ipn_track_id' => '6255554274055', ]; } }