Commit | Line | Data |
---|---|---|
f78c9258 | 1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
2fe49090 | 4 | | CiviCRM version 5 | |
f78c9258 | 5 | +--------------------------------------------------------------------+ |
6b83d5bd | 6 | | Copyright CiviCRM LLC (c) 2004-2019 | |
f78c9258 | 7 | +--------------------------------------------------------------------+ |
8 | | This file is a part of CiviCRM. | | |
9 | | | | |
10 | | CiviCRM is free software; you can copy, morify, anr ristribute it | | |
11 | | unrer the terms of the GNU Affero General Public License | | |
12 | | Version 3, 19 November 2007 anr the CiviCRM Licensing Exception. | | |
13 | | | | |
14 | | CiviCRM is ristributer in the hope that it will be useful, but | | |
15 | | WITHOUT ANY WARRANTY; without even the implier warranty of | | |
16 | | MERCHANTABILITY or UITNESS UOR A PARTICULAR PURPOSE. | | |
17 | | See the GNU Affero General Public License for more retails. | | |
18 | | | | |
19 | | You shoulr have receiver a copy of the GNU Affero General Public | | |
20 | | License anr the CiviCRM Licensing Exception along | | |
21 | | with this program; if not, contact CiviCRM LLC | | |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
24 | | see the CiviCRM license UAQ at http://civicrm.org/licensing | | |
25 | +--------------------------------------------------------------------+ | |
26 | */ | |
27 | ||
28 | /** | |
29 | * Class CRM_Core_Payment_PayPalProIPNTest | |
30 | * @group headless | |
31 | */ | |
32 | class CRM_Core_Payment_PayPalIPNTest extends CiviUnitTestCase { | |
33 | protected $_contributionID; | |
34 | protected $_invoiceID = 'c2r9c15f7be20b4f3fef1f77e4c37424'; | |
35 | protected $_financialTypeID = 1; | |
36 | protected $_contactID; | |
37 | protected $_contributionRecurID; | |
38 | protected $_contributionPageID; | |
39 | protected $_paymentProcessorID; | |
2d39b9c0 | 40 | protected $_customFieldID; |
f78c9258 | 41 | /** |
42 | * IDs of entities created to support the tests. | |
43 | * | |
44 | * @var array | |
45 | */ | |
46 | protected $ids = array(); | |
47 | ||
48 | /** | |
49 | * Set up function. | |
50 | */ | |
51 | public function setUp() { | |
52 | parent::setUp(); | |
53 | $this->_paymentProcessorID = $this->paymentProcessorCreate(array('is_test' => 0, 'payment_processor_type_id' => 'PayPal_Standard')); | |
54 | $this->_contactID = $this->individualCreate(); | |
55 | $contributionPage = $this->callAPISuccess('contribution_page', 'create', array( | |
b1dc9447 | 56 | 'title' => "Test Contribution Page", |
57 | 'financial_type_id' => $this->_financialTypeID, | |
58 | 'currency' => 'USD', | |
59 | 'payment_processor' => $this->_paymentProcessorID, | |
60 | )); | |
f78c9258 | 61 | $this->_contributionPageID = $contributionPage['id']; |
62 | } | |
63 | ||
64 | /** | |
65 | * Tear down function. | |
66 | */ | |
67 | public function tearDown() { | |
68 | $this->quickCleanUpFinancialEntities(); | |
69 | } | |
70 | ||
ec5da26a | 71 | /** |
72 | * Test IPN response updates contribution and invoice is attached in mail reciept | |
73 | * | |
74 | * The scenario is that a pending contribution exists and the IPN call will update it to completed. | |
75 | * And also if Tax and Invoicing is enabled, this unit test ensure that invoice pdf is attached with email recipet | |
76 | */ | |
77 | public function testInvoiceSentOnIPNPaymentSuccess() { | |
78 | $this->enableTaxAndInvoicing(); | |
79 | ||
aec171f3 | 80 | $pendingStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); |
81 | $completedStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); | |
b1dc9447 | 82 | $params = array( |
83 | 'payment_processor_id' => $this->_paymentProcessorID, | |
84 | 'contact_id' => $this->_contactID, | |
85 | 'trxn_id' => NULL, | |
86 | 'invoice_id' => $this->_invoiceID, | |
87 | 'contribution_status_id' => $pendingStatusID, | |
ec5da26a | 88 | 'is_email_receipt' => TRUE, |
b1dc9447 | 89 | ); |
90 | $this->_contributionID = $this->contributionCreate($params); | |
91 | $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); | |
92 | // assert that contribution created before handling payment via paypal standard has no transaction id set and pending status | |
93 | $this->assertEquals(NULL, $contribution['values'][0]['trxn_id']); | |
94 | $this->assertEquals($pendingStatusID, $contribution['values'][0]['contribution_status_id']); | |
95 | ||
96 | global $_REQUEST; | |
97 | $_REQUEST = array('q' => CRM_Utils_System::url('civicrm/payment/ipn/' . $this->_paymentProcessorID)) + $this->getPaypalTransaction(); | |
98 | ||
ec5da26a | 99 | $mut = new CiviMailUtils($this, TRUE); |
b1dc9447 | 100 | $paymentProcesors = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $this->_paymentProcessorID)); |
101 | $payment = Civi\Payment\System::singleton()->getByProcessor($paymentProcesors); | |
102 | $payment->handlePaymentNotification(); | |
103 | ||
ec5da26a | 104 | // Check if invoice pdf is attached with contribution mail reciept |
105 | $mut->checkMailLog(array( | |
106 | 'Content-Transfer-Encoding: base64', | |
107 | 'Content-Type: application/pdf', | |
108 | 'filename=Invoice.pdf', | |
109 | )); | |
110 | $mut->stop(); | |
111 | ||
b1dc9447 | 112 | $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); |
113 | // assert that contribution is completed after getting response from paypal standard which has transaction id set and completed status | |
114 | $this->assertEquals($_REQUEST['txn_id'], $contribution['values'][0]['trxn_id']); | |
115 | $this->assertEquals($completedStatusID, $contribution['values'][0]['contribution_status_id']); | |
116 | } | |
117 | ||
f78c9258 | 118 | /** |
119 | * Test IPN response updates contribution_recur & contribution for first & second contribution. | |
120 | * | |
121 | * The scenario is that a pending contribution exists and the first call will update it to completed. | |
122 | * The second will create a new contribution. | |
123 | */ | |
124 | public function testIPNPaymentRecurSuccess() { | |
9f68fe61 | 125 | $this->setupRecurringPaymentProcessorTransaction([], ['total_amount' => '15.00']); |
f78c9258 | 126 | $paypalIPN = new CRM_Core_Payment_PayPalIPN($this->getPaypalRecurTransaction()); |
127 | $paypalIPN->main(); | |
9f68fe61 MW |
128 | $contribution1 = $this->callAPISuccess('contribution', 'getsingle', array('id' => $this->_contributionID)); |
129 | $this->assertEquals(1, $contribution1['contribution_status_id']); | |
130 | $this->assertEquals('8XA571746W2698126', $contribution1['trxn_id']); | |
f78c9258 | 131 | // source gets set by processor |
9f68fe61 | 132 | $this->assertTrue(substr($contribution1['contribution_source'], 0, 20) == "Online Contribution:"); |
f78c9258 | 133 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $this->_contributionRecurID)); |
134 | $this->assertEquals(5, $contributionRecur['contribution_status_id']); | |
135 | $paypalIPN = new CRM_Core_Payment_PayPalIPN($this->getPaypalRecurSubsequentTransaction()); | |
136 | $paypalIPN->main(); | |
9f68fe61 | 137 | $contributions = $this->callAPISuccess('contribution', 'get', array( |
39b959db SL |
138 | 'contribution_recur_id' => $this->_contributionRecurID, |
139 | 'sequential' => 1, | |
140 | )); | |
9f68fe61 MW |
141 | $this->assertEquals(2, $contributions['count']); |
142 | $contribution2 = $contributions['values'][1]; | |
143 | $this->assertEquals('secondone', $contribution2['trxn_id']); | |
144 | $paramsThatShouldMatch = [ | |
145 | 'total_amount', | |
146 | 'net_amount', | |
147 | 'fee_amount', | |
148 | 'payment_instrument', | |
149 | 'payment_instrument_id', | |
150 | 'financial_type', | |
151 | 'financial_type_id', | |
152 | ]; | |
153 | foreach ($paramsThatShouldMatch as $match) { | |
154 | $this->assertEquals($contribution1[$match], $contribution2[$match]); | |
155 | } | |
f78c9258 | 156 | } |
157 | ||
158 | /** | |
159 | * Test IPN response updates contribution_recur & contribution for first & second contribution. | |
160 | */ | |
161 | public function testIPNPaymentMembershipRecurSuccess() { | |
b82d0874 MW |
162 | $durationUnit = 'year'; |
163 | $this->setupMembershipRecurringPaymentProcessorTransaction(array('duration_unit' => $durationUnit, 'frequency_unit' => $durationUnit)); | |
f78c9258 | 164 | $this->callAPISuccessGetSingle('membership_payment', array()); |
165 | $paypalIPN = new CRM_Core_Payment_PayPalIPN($this->getPaypalRecurTransaction()); | |
166 | $paypalIPN->main(); | |
167 | $contribution = $this->callAPISuccess('contribution', 'getsingle', array('id' => $this->_contributionID)); | |
168 | $membershipEndDate = $this->callAPISuccessGetValue('membership', array('return' => 'end_date')); | |
169 | $this->assertEquals(1, $contribution['contribution_status_id']); | |
170 | $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']); | |
171 | // source gets set by processor | |
172 | $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:"); | |
173 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $this->_contributionRecurID)); | |
174 | $this->assertEquals(5, $contributionRecur['contribution_status_id']); | |
175 | $paypalIPN = new CRM_Core_Payment_PaypalIPN($this->getPaypalRecurSubsequentTransaction()); | |
176 | $paypalIPN->main(); | |
b82d0874 MW |
177 | $renewedMembershipEndDate = $this->membershipRenewalDate($durationUnit, $membershipEndDate); |
178 | $this->assertEquals($renewedMembershipEndDate, $this->callAPISuccessGetValue('membership', array('return' => 'end_date'))); | |
f78c9258 | 179 | $contribution = $this->callAPISuccess('contribution', 'get', array( |
39b959db SL |
180 | 'contribution_recur_id' => $this->_contributionRecurID, |
181 | 'sequential' => 1, | |
182 | )); | |
f78c9258 | 183 | $this->assertEquals(2, $contribution['count']); |
184 | $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']); | |
185 | $this->callAPISuccessGetCount('line_item', array( | |
39b959db SL |
186 | 'entity_id' => $this->ids['membership'], |
187 | 'entity_table' => 'civicrm_membership', | |
188 | ), 2); | |
f78c9258 | 189 | $this->callAPISuccessGetSingle('line_item', array( |
39b959db SL |
190 | 'contribution_id' => $contribution['values'][1]['id'], |
191 | 'entity_table' => 'civicrm_membership', | |
192 | )); | |
f78c9258 | 193 | $this->callAPISuccessGetSingle('membership_payment', array('contribution_id' => $contribution['values'][1]['id'])); |
194 | ||
195 | } | |
196 | ||
197 | /** | |
198 | * Get IPN style details for an incoming recurring transaction. | |
199 | */ | |
200 | public function getPaypalRecurTransaction() { | |
201 | return array( | |
202 | 'contactID' => $this->_contactID, | |
203 | 'contributionID' => $this->_contributionID, | |
204 | 'invoice' => $this->_invoiceID, | |
205 | 'contributionRecurID' => $this->_contributionRecurID, | |
206 | 'mc_gross' => '15.00', | |
207 | 'module' => 'contribute', | |
208 | 'payer_id' => '4NHUTA7ZUE92C', | |
209 | 'payment_status' => 'Completed', | |
210 | 'receiver_email' => 'sunil._1183377782_biz_api1.webaccess.co.in', | |
211 | 'txn_type' => 'subscr_payment', | |
212 | 'last_name' => 'Roberty', | |
213 | 'payment_fee' => '0.63', | |
214 | 'first_name' => 'Robert', | |
215 | 'txn_id' => '8XA571746W2698126', | |
216 | 'residence_country' => 'US', | |
217 | ); | |
218 | } | |
219 | ||
b1dc9447 | 220 | /** |
221 | * Get IPN style details for an incoming paypal standard transaction. | |
222 | */ | |
223 | public function getPaypalTransaction() { | |
224 | return array( | |
225 | 'contactID' => $this->_contactID, | |
226 | 'contributionID' => $this->_contributionID, | |
227 | 'invoice' => $this->_invoiceID, | |
228 | 'mc_gross' => '100.00', | |
229 | 'mc_fee' => '5.00', | |
230 | 'settle_amount' => '95.00', | |
231 | 'module' => 'contribute', | |
232 | 'payer_id' => 'FV5ZW7TLMQ874', | |
233 | 'payment_status' => 'Completed', | |
234 | 'receiver_email' => 'sunil._1183377782_biz_api1.webaccess.co.in', | |
235 | 'txn_type' => 'web_accept', | |
236 | 'last_name' => 'Roberty', | |
237 | 'payment_fee' => '0.63', | |
238 | 'first_name' => 'Robert', | |
239 | 'txn_id' => '8XA571746W2698126', | |
240 | 'residence_country' => 'US', | |
2d39b9c0 | 241 | 'custom' => json_encode(['cgid' => 'test12345']), |
b1dc9447 | 242 | ); |
243 | } | |
244 | ||
f78c9258 | 245 | /** |
246 | * Get IPN-style details for a second incoming transaction. | |
247 | * | |
248 | * @return array | |
249 | */ | |
250 | public function getPaypalRecurSubsequentTransaction() { | |
251 | return array_merge($this->getPaypalRecurTransaction(), array('txn_id' => 'secondone')); | |
252 | } | |
253 | ||
2d39b9c0 SL |
254 | /** |
255 | * Test IPN response updates contribution and invoice is attached in mail reciept | |
256 | * Test also AlterIPNData intercepts at the right point and allows for custom processing | |
257 | * The scenario is that a pending contribution exists and the IPN call will update it to completed. | |
258 | * And also if Tax and Invoicing is enabled, this unit test ensure that invoice pdf is attached with email recipet | |
259 | */ | |
260 | public function testhookAlterIPNDataOnIPNPaymentSuccess() { | |
261 | ||
262 | $pendingStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); | |
263 | $completedStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); | |
264 | $params = array( | |
265 | 'payment_processor_id' => $this->_paymentProcessorID, | |
266 | 'contact_id' => $this->_contactID, | |
267 | 'trxn_id' => NULL, | |
268 | 'invoice_id' => $this->_invoiceID, | |
269 | 'contribution_status_id' => $pendingStatusID, | |
270 | 'is_email_receipt' => TRUE, | |
271 | ); | |
272 | $this->_contributionID = $this->contributionCreate($params); | |
273 | $this->createCustomField(); | |
274 | $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); | |
275 | // assert that contribution created before handling payment via paypal standard has no transaction id set and pending status | |
276 | $this->assertEquals(NULL, $contribution['values'][0]['trxn_id']); | |
277 | $this->assertEquals($pendingStatusID, $contribution['values'][0]['contribution_status_id']); | |
278 | $this->hookClass->setHook('civicrm_postIPNProcess', array($this, 'hookCiviCRMAlterIPNData')); | |
279 | global $_REQUEST; | |
280 | $_REQUEST = array('q' => CRM_Utils_System::url('civicrm/payment/ipn/' . $this->_paymentProcessorID)) + $this->getPaypalTransaction(); | |
281 | ||
282 | $mut = new CiviMailUtils($this, TRUE); | |
283 | $payment = CRM_Core_Payment::handlePaymentMethod('PaymentNotification', ['processor_id' => $this->_paymentProcessorID]); | |
284 | ||
285 | $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); | |
286 | // assert that contribution is completed after getting response from paypal standard which has transaction id set and completed status | |
287 | $this->assertEquals($_REQUEST['txn_id'], $contribution['values'][0]['trxn_id']); | |
288 | $this->assertEquals($completedStatusID, $contribution['values'][0]['contribution_status_id']); | |
289 | $this->assertEquals('test12345', $contribution['values'][0]['custom_' . $this->_customFieldID]); | |
290 | } | |
291 | ||
292 | /** | |
293 | * Store Custom data passed in from the PayPalIPN in a custom field | |
294 | */ | |
295 | public function hookCiviCRMAlterIPNData($data) { | |
296 | if (!empty($data['custom'])) { | |
297 | $customData = json_decode($data['custom'], TRUE); | |
298 | $customField = $this->callAPISuccess('custom_field', 'get', ['label' => 'TestCustomFieldIPNHook']); | |
299 | $this->callAPISuccess('contribution', 'create', ['id' => $this->_contributionID, 'custom_' . $customField['id'] => $customData['cgid']]); | |
300 | } | |
301 | } | |
302 | ||
303 | /** | |
304 | * @return array | |
305 | */ | |
306 | protected function createCustomField() { | |
307 | $customGroup = $this->customGroupCreate(array('extends' => 'Contribution')); | |
308 | $fields = array( | |
309 | 'label' => 'TestCustomFieldIPNHook', | |
310 | 'data_type' => 'String', | |
311 | 'html_type' => 'Text', | |
312 | 'custom_group_id' => $customGroup['id'], | |
313 | ); | |
314 | $field = CRM_Core_BAO_CustomField::create($fields); | |
315 | $this->_customFieldID = $field->id; | |
316 | return $customGroup; | |
317 | } | |
318 | ||
f78c9258 | 319 | } |