Extensions Paheko  Check-in [5c964845a2]

Overview
Comment:HelloAsso: CheckoutIntent support implemented
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 5c964845a22e10b6d60a58382581caa6d752c720
User & Date: alinaar on 2023-05-22 22:00:40
Other Links: branch diff | manifest | tags
Context
2023-05-22
22:05
Merge check-in: 1fdf8c0816 user: alinaar tags: dev
22:00
HelloAsso: CheckoutIntent support implemented check-in: 5c964845a2 user: alinaar tags: dev
2023-05-19
08:54
HelloAsso: sync redirection fixed check-in: 28a9ee9417 user: alinaar tags: dev
Changes

Modified helloasso/admin/index.php from [1552d8b51b] to [da9f56c3de].

1
2
3
4
5
6








7
8
9
10
11
12
13
14
15









16
17
18

19
20
<?php

namespace Garradin;

use Garradin\Plugin\HelloAsso\Forms;









require __DIR__ . '/_inc.php';

if ($plugin->needUpgrade()) {
	$plugin->upgrade();
}

if (!$ha->getLastSync()) {
	Utils::redirect(PLUGIN_ADMIN_URL . 'sync.php');
}










$tpl->assign('list', Forms::list());
$tpl->assign('restricted', $ha::isTrial());


$tpl->display(PLUGIN_ROOT . '/templates/index.tpl');






>
>
>
>
>
>
>
>









>
>
>
>
>
>
>
>
>



>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace Garradin;

use Garradin\Plugin\HelloAsso\Forms;

use Garradin\Plugin\HelloAsso\HelloAsso;
use Garradin\Plugin\HelloAsso\API;
use Garradin\Entities\Payments\Payment;
use Garradin\Payments\Payments;
use Garradin\Entities\Users\User;

use KD2\DB\EntityManager as EM;

require __DIR__ . '/_inc.php';

if ($plugin->needUpgrade()) {
	$plugin->upgrade();
}

if (!$ha->getLastSync()) {
	Utils::redirect(PLUGIN_ADMIN_URL . 'sync.php');
}

$checkout = null;
$form->runIf('generate_checkout', function () use ($ha, $checkout, $tpl) {
	// ToDo: add a nice check
	$user = EM::findOneById(User::class, (int)(array_keys($_POST['user'])[0]));
	$checkout = $ha->createCheckout($_POST['org_slug'], $_POST['label'], (int)($_POST['amount'] * 100), $user);

	$tpl->assign('checkout', $checkout);
});

$tpl->assign('list', Forms::list());
$tpl->assign('restricted', $ha::isTrial());
$tpl->assign('orgOptions', [ $ha->plugin()->getConfig()->default_organization => $ha->plugin()->getConfig()->default_organization ]);

$tpl->display(PLUGIN_ROOT . '/templates/index.tpl');

Modified helloasso/lib/API.php from [32377093f0] to [4bd9fbded5].

1
2
3
4
5

6


7

8
9
10
11
12
13
14
<?php

namespace Garradin\Plugin\HelloAsso;

use Garradin\UserException;




use KD2\HTTP;


class API
{
	//const BASE_URL = 'https://api.helloasso.com/';
	const BASE_URL = 'https://api.helloasso-sandbox.com/';

	protected $ha;





>

>
>

>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace Garradin\Plugin\HelloAsso;

use Garradin\UserException;
use Garradin\Entities\Payments\Payment;

use Garradin\Plugin\HelloAsso\Entities as HA;

use KD2\HTTP;
use KD2\DB\EntityManager;

class API
{
	//const BASE_URL = 'https://api.helloasso.com/';
	const BASE_URL = 'https://api.helloasso-sandbox.com/';

	protected $ha;
38
39
40
41
42
43
44
45
46
47
48
49
50
51

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69



70
71
72
73
74
75
76
77
	}

	protected function GET(string $url, array $data = [])
	{
		return $this->request('GET', $url, $data);
	}

	protected function POST(string $url, array $data = [])
	{
		return $this->request('POST', $url, $data);
	}

	protected function request(string $type, string $url, array $data = [])
	{

		$url = self::BASE_URL . $url;

		$token = $this->getToken();

		$headers = [
			'Authorization' => sprintf('Bearer %s', $token),
			'Accept'        => 'application/json',
			'User-Agent'    => 'Garradin',
		];

		if ($type == 'GET') {
			if ($data) {
				$url .= '?' . http_build_query($data);
			}

			$response = (new HTTP)->GET($url, $headers);
		}
		else {



			$response = (new HTTP)->POST($url, $data, HTTP::FORM, $headers);
		}

		if ($response->fail || $response->status != 200) {
			$error = sprintf('%d - %s', $response->status, $response->body ?: $response->error);
			throw new UserException('Erreur de l\'API HelloAsso : ' . $error);
		}








|

|


|

>


















>
>
>
|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
	}

	protected function GET(string $url, array $data = [])
	{
		return $this->request('GET', $url, $data);
	}

	protected function POST(string $url, array $data = [], string $format = HTTP::FORM)
	{
		return $this->request('POST', $url, $data, $format);
	}

	protected function request(string $type, string $url, array $data = [], string $format = HTTP::FORM)
	{
		$allowed_formats = [HTTP::FORM, HTTP::JSON];
		$url = self::BASE_URL . $url;

		$token = $this->getToken();

		$headers = [
			'Authorization' => sprintf('Bearer %s', $token),
			'Accept'        => 'application/json',
			'User-Agent'    => 'Garradin',
		];

		if ($type == 'GET') {
			if ($data) {
				$url .= '?' . http_build_query($data);
			}

			$response = (new HTTP)->GET($url, $headers);
		}
		else {
			if (!in_array($format, $allowed_formats)) {
				throw new \InvalidArgumentException(sprintf('Wrong request format: %s. Allowed formats are: %s.', $format, implode(', ', $allowed_formats)));
			}
			$response = (new HTTP)->POST($url, $data, $format, $headers);
		}

		if ($response->fail || $response->status != 200) {
			$error = sprintf('%d - %s', $response->status, $response->body ?: $response->error);
			throw new UserException('Erreur de l\'API HelloAsso : ' . $error);
		}

291
292
293
294
295
296
297



298


























			$this->assert(isset($r->state));
			$this->assert(isset($r->type));
			$this->assert(isset($r->id));
			$this->assert(isset($r->amount) && ctype_digit($r->amount));
		}
	}




}

































>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
			$this->assert(isset($r->state));
			$this->assert(isset($r->type));
			$this->assert(isset($r->id));
			$this->assert(isset($r->amount) && ctype_digit($r->amount));
		}
	}

	public function getCheckout(string $organization, int $id): \stdClass
	{
		return $this->GET('v5/organizations/' . $organization . '/checkout-intents/' . (int)$id);
	}

	public function createCheckout(string $organization, int $amount, string $label, int $payment_id, string $url, array $metadata): \stdClass
	{
		$params = [
			'totalAmount'      => $amount,
			'initialAmount'    => $amount,
			'itemName'         => $label,
			'backUrl'          => sprintf('%s?p=%s&action=cancel', $url, $payment_id),
			'errorUrl'         => sprintf('%s?p=%d&action=cancel', $url, $payment_id),
			'returnUrl'        => sprintf('%s?p=%d&action=return', $url, $payment_id),
			'containsDonation' => true,
			'metadata' => $metadata,
		];

		$response = $this->POST(sprintf('v5/organizations/%s/checkout-intents', $organization), $params, HTTP::JSON);

		if (!isset($response->id, $response->redirectUrl)) {
			throw new \RuntimeException('Erreur API HelloAsso: id ou redirectUrl manquants: ' . json_encode($response));
		}

		return (object) [
			'id' => (string) $response->id,
			'url' => $response->redirectUrl
		];
	}
}

Modified helloasso/lib/HelloAsso.php from [abc5349bfa] to [6cbd7fae5e].

1
2
3
4
5
6
7


8
9
10
11
12
13
14
15
16
17
18
19
20






21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

namespace Garradin\Plugin\HelloAsso;

use Garradin\Config;
use Garradin\DB;
use Garradin\Entities\Plugin;


use KD2\DB\EntityManager;

use Garradin\Plugin\HelloAsso\Entities\Form;

use function Garradin\garradin_contributor_license;

class HelloAsso
{
	const NAME = 'helloasso';
	const PROVIDER_NAME = self::NAME;
	const PROVIDER_LABEL = 'HelloAsso';
	const ACCOUNTING_ENABLED = 1;
	const CHART_ID = 1; // ToDo: make it dynamic






	const PER_PAGE = 100;

	const MERGE_NAMES_FIRST_LAST = 0;
	const MERGE_NAMES_LAST_FIRST = 1;

	const MERGE_NAMES_OPTIONS = [
		self::MERGE_NAMES_FIRST_LAST => 'Prénom Nom',
		self::MERGE_NAMES_LAST_FIRST => 'Nom Prénom',
	];

	protected $plugin;
	protected $config;


	static protected $_instance;

	static public function getInstance()
	{
		if (null === self::$_instance) {







>
>













>
>
>
>
>
>











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

namespace Garradin\Plugin\HelloAsso;

use Garradin\Config;
use Garradin\DB;
use Garradin\Entities\Plugin;
use Garradin\Entities\Users\User;
use Garradin\Entities\Payments\Payment;
use KD2\DB\EntityManager;

use Garradin\Plugin\HelloAsso\Entities\Form;

use function Garradin\garradin_contributor_license;

class HelloAsso
{
	const NAME = 'helloasso';
	const PROVIDER_NAME = self::NAME;
	const PROVIDER_LABEL = 'HelloAsso';
	const ACCOUNTING_ENABLED = 1;
	const CHART_ID = 1; // ToDo: make it dynamic
	const PAYMENT_EXPIRATION = '2 days';
	const CHECKOUT_LINK_EXPIRATION = '15 minutes';
	const CHECKOUT_CREATION_LOG_LABEL = 'Tunnel de paiement ' . self::PROVIDER_LABEL . ' n°%d créé.';
	const PAYMENT_RESUMING_LOG_LABEL = 'Reprise du paiement.';
	const LOG_FILE = __DIR__ . '/../logs';
	const REDIRECTION_FILE = 'payer.php';
	const PER_PAGE = 100;

	const MERGE_NAMES_FIRST_LAST = 0;
	const MERGE_NAMES_LAST_FIRST = 1;

	const MERGE_NAMES_OPTIONS = [
		self::MERGE_NAMES_FIRST_LAST => 'Prénom Nom',
		self::MERGE_NAMES_LAST_FIRST => 'Nom Prénom',
	];

	protected $plugin;
	protected ?\stdClass $config;


	static protected $_instance;

	static public function getInstance()
	{
		if (null === self::$_instance) {
65
66
67
68
69
70
71
72


73
74
75
76
77
78
79

		return $date;
	}

	public function sync(): bool
	{
		Forms::sync();
		$organizations = array_keys(Forms::listOrganizations());



		foreach ($organizations as $org_slug) {
			Orders::sync($org_slug);
			Payments::sync($org_slug, $this->config->accounting);
			Items::sync($org_slug);
		}








|
>
>







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

		return $date;
	}

	public function sync(): bool
	{
		Forms::sync();
		if ($organizations = array_keys(Forms::listOrganizations())) {
			$this->plugin->setConfigProperty('default_organization', $organizations[0]);
		}

		foreach ($organizations as $org_slug) {
			Orders::sync($org_slug);
			Payments::sync($org_slug, $this->config->accounting);
			Items::sync($org_slug);
		}

132
133
134
135
136
137
138


































































































































139
140
141
142
143
144
145
	{
		return empty($this->config->oauth) ? false : true;
	}

	public function getConfig(): ?\stdClass {
		return $this->config;
	}



































































































































/*
	public function listTargets(): array
	{
		return EM::getInstance(Target::class, 'SELECT * FROM @TABLE ORDER BY label;');
	}








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
	{
		return empty($this->config->oauth) ? false : true;
	}

	public function getConfig(): ?\stdClass {
		return $this->config;
	}

	public function createCheckout(string $organization, string $label, int $amount, User $user): \stdClass
	{
		$label .= ' - ' . $user->nom . ' - ' . self::PROVIDER_LABEL;

		// Resume user failed attemp
		if ($payment = EntityManager::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE id_author = :id_user AND label = :label AND status = :status AND method = :method AND type = :type AND date >= datetime(\'now\', :expiration)', (int)$user->id, $label, Payment::AWAITING_STATUS, Payment::BANK_CARD_METHOD, Payment::UNIQUE_TYPE, '-' . self::PAYMENT_EXPIRATION)) {
			// Resume current checkout
			if (isset($payment->extra_data->checkout) && !(new \DateTime($payment->extra_data->checkout->date) < new \DateTime('now -' . self::CHECKOUT_LINK_EXPIRATION))) {
				return $payment->extra_data->checkout;
			}
			$payment->addLog(self::PAYMENT_RESUMING_LOG_LABEL);
		}
		else {
			$payment = Payments::createPayment(Payment::UNIQUE_TYPE, Payment::BANK_CARD_METHOD, Payment::AWAITING_STATUS, self::PROVIDER_NAME, null, $user->id, $user->nom, null, $label, $amount, null, null);
		}
		$csrf = 'COMING_SOON_CSRF';
		$metadata = [
			'payment_id' => $payment->id,
			'user_id'  => $payment->id_author,
			'csrf' => $csrf
		];
		$checkout = API::getInstance()->createCheckout($organization, $payment->amount, $label, $payment->id, $this->plugin->url() . self::REDIRECTION_FILE, $metadata);
		$checkout->date = (new \DateTime())->format('Y-m-d H:i:s');
		$checkout->csrf = $csrf;
		$payment->setExtraData('checkout', $checkout);
		$payment->set('reference', $checkout->id);
		$payment->setExtraData('organization', $organization);
		$payment->addLog(sprintf(self::CHECKOUT_CREATION_LOG_LABEL, (int)$checkout->id));
		$payment->save();

		return $checkout;
	}

	static public function checkPaymentStatus(Payment $payment): void
	{
		if ($payment->status !== Payment::AWAITING_STATUS) {
			return ;
		}

		if ($payment->provider != self::PROVIDER_NAME) {
			throw new \LogicException(sprintf('This is not a %s payment!', self::PROVIDER_LABEL));
		}

		if (empty($payment->reference)) {
			throw new \LogicException('This payment does not have a reference.');
		}

		$checkout = API::getInstance()->getCheckout($payment->organization, (int)$payment->reference);

		file_put_contents(self::LOG_FILE, sprintf("\n\n==== %s - Fetch: %s ====\n\n%s\n", date('d/m/Y H:i:s'), $payment->reference, json_encode($checkout, JSON_PRETTY_PRINT)), FILE_APPEND);

		if ($checkout->metadata->payment_id != $payment->id) {
			throw new \LogicException(sprintf('Payment ref. "%s" does not match payment ID "%s" (metadata payment_id = %s)', $payment->reference, $payment->id, $checkout->metadata->payment_id));
		}

		if ($checkout->metadata->csrf !== $payment->checkout->csrf) {
			throw new \LogicException(sprintf('Wrong received CSRF (%s) while trying to check payment status of payment ID %s.', $checkout->metadata->csrf, $payment->id));
		}

		/*if (!isset($checkout->order)) {
			// The payment is still waiting
			//mail(ROOT_EMAIL, 'HelloAsso payment data issue', json_encode(['data' => $checkout, 'SERVER' => $_SERVER], JSON_PRETTY_PRINT));
			return;
		}*/

		if (!isset($checkout->order->payments[0]->state, $checkout->order->payments[0]->amount)) {
			throw new \LogicException('Payment is missing details: ' . json_encode($checkout, JSON_PRETTY_PRINT));
		}

		if ($checkout->order->payments[0]->state != Payments::AUTHORIZED_STATUS) {
			self::log(sprintf('NOTHING TO DO. Reason: only handle authorized status (received: %s).', $checkout->order->payments[0]->state));
			return;
		}

		$payment->validate((int)$checkout->order->payments[0]->amount, $checkout->order->payments[0]->paymentReceiptUrl ?? null);
	}

	public static function handleCallback(): void
	{
		if ($_SERVER['REQUEST_METHOD'] != 'POST') {
			throw new \RuntimeException('Invalid request method');
		}

		$data = file_get_contents('php://input');

		if (empty($data)) {
			throw new \RuntimeException('Empty POST data');
		}

		$json = json_decode($data);

		if (null === $json) {
			throw new \RuntimeException('Invalid JSON data: ' . $data);
		}

		// Some logging
		self::log(sprintf("\n\n==== Callback: %s ====\n\n%s\n", date('d/m/Y H:i:s'), json_encode($json, JSON_PRETTY_PRINT)));

		if (empty($json->eventType)) {
			throw new \RuntimeException('Invalid JSON response, missing eventType: ' . $data);
		}

		if (strtolower($json->eventType) == 'payment') {
			if (empty($json->metadata->payment_id)) {
				self::log('CALLBACK IGNORED. Reason: no payment_id inside metadata.');
				// Ignore
				return;
			}

			$payment = EntityManager::findOneById(Payment::class, (int)$json->metadata->payment_id);

			if (!$payment) {
				//mail(ROOT_EMAIL, 'Callback d\'un paiement qui n\'existe pas', json_encode($json, JSON_PRETTY_PRINT));
				self::log(sprintf('CALLBACK IGNORED. Reason: Payment not found for ID #%s.', $json->metadata->payment_id));
				return;
			}
			
			if ($payment->reference != $json->data->id) {
				throw new \RuntimeException(sprintf('Payment reference (#%s) and checkout ID (#%s) mismatch!', $payment->id, $json->data->id));
			}

			// Don't trust data sent to this callback, let's fetch data
			self::checkPaymentStatus($payment);
		}
		else {
			// Ignore for now
			self::log(sprintf('CALLBACK IGNORED. Reason: \'eventType\'s different from \'payment\' are not yet implemented (received: %s).', $json->eventType));
		}
	}

/*
	public function listTargets(): array
	{
		return EM::getInstance(Target::class, 'SELECT * FROM @TABLE ORDER BY label;');
	}

211
212
213
214
215
216
217





218
219
220
221
222
223
224
				$out[$map->firstName] = $payer->lastName . ' ' . $payer->firstName;
			}
		}

		return $out;
	}
*/






	static public function cron() {
	}

	/**
	 * Toutes ces lignes de code ne se sont pas écrites toutes seules…
	 * Merci de contribuer à Garradin ;)







>
>
>
>
>







351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
				$out[$map->firstName] = $payer->lastName . ' ' . $payer->firstName;
			}
		}

		return $out;
	}
*/

	static public function log(string $message): void
	{
		file_put_contents(self::LOG_FILE, $message, FILE_APPEND);
	}

	static public function cron() {
	}

	/**
	 * Toutes ces lignes de code ne se sont pas écrites toutes seules…
	 * Merci de contribuer à Garradin ;)
242
243
244
245
246
247
248
249

		if ($level < self::LEVEL_REQUIRED) {
			return true;
		}
		else {
			return false;
		}
	}
}








|
>
387
388
389
390
391
392
393
394
395
		if ($level < self::LEVEL_REQUIRED) {
			return true;
		}
		else {
			return false;
		}
	}

}

Modified helloasso/lib/Payments.php from [81dbb24416] to [1a7d54b265].

15
16
17
18
19
20
21

22
23
24
25
26
27
28
29
use Garradin\DynamicList;
use Garradin\Utils;

use KD2\DB\EntityManager as EM;

class Payments extends Paheko_Payments
{

	const PAHEKO_STATUS = [ HA\Payment::STATE_OK => Payment::VALIDATED_STATUS ]; // ToDo: complete the list
	const UPDATE_MESSAGE = 'Mise à jour du paiement';
	const TRANSACTION_NOTE = 'Générée automatiquement par l\'extension ' . HelloAsso::PROVIDER_LABEL . '.';

	static public function get(int $id): ?Payment
	{
		return EM::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE provider = :provider AND json_extract(extra_data, \'$.id\') = :id', HelloAsso::PROVIDER_NAME, $id);
	}







>
|







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use Garradin\DynamicList;
use Garradin\Utils;

use KD2\DB\EntityManager as EM;

class Payments extends Paheko_Payments
{
	const AUTHORIZED_STATUS = 'Authorized';
	const STATUSES = [ self::AUTHORIZED_STATUS => Payment::VALIDATED_STATUS ]; // ToDo: complete the list from HA\Payment class
	const UPDATE_MESSAGE = 'Mise à jour du paiement';
	const TRANSACTION_NOTE = 'Générée automatiquement par l\'extension ' . HelloAsso::PROVIDER_LABEL . '.';

	static public function get(int $id): ?Payment
	{
		return EM::findOne(Payment::class, 'SELECT * FROM @TABLE WHERE provider = :provider AND json_extract(extra_data, \'$.id\') = :id', HelloAsso::PROVIDER_NAME, $id);
	}
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
			// If accounting is enabled, we record the payment only if credit and debit accounts are set
			if (!$accounting || ($accounting && $form->id_credit_account && $form->id_debit_account)) {
				$id = DB::getInstance()->firstColumn(sprintf('SELECT id FROM %s WHERE email = \'%s\' LIMIT 1;', User::TABLE, $data->payer->email));
				$author_id = $id ?? null;
				$author_name = $data->payer->lastName . ' ' . $data->payer->firstName;
				$label = ($data->order ? $data->order->formName . ' - ' : '') . $data->payer_name . ' - ' . HelloAsso::PROVIDER_NAME . ' #' . $data->id;
				$accounts = $accounting ? [$form->id_credit_account, $form->id_debit_account] : null;
				$payment = Payments::createPayment(Payment::UNIQUE_TYPE, Payment::BANK_CARD_METHOD, self::PAHEKO_STATUS[$data->state], HelloAsso::PROVIDER_NAME, $accounts, $author_id, $author_name, $data->id, $label, $data->amount, $data, self::TRANSACTION_NOTE);
			}
		}
		else
		{
			if ($accounting && !$payment->id_transaction) { // Happens when the user decided to switch on the accounting while sync had already be done without accounting
				$transaction = Payments::createTransaction($payment, [$form->id_credit_account, $form->id_debit_account], self::TRANSACTION_NOTE);
				$payment->set('id_transaction', (int)$transaction->id);
			}
			$payment->set('amount', $data->amount);
			$payment->set('status', self::PAHEKO_STATUS[$data->state]);
			$payment->set('history', $data->date->format('Y-m-d H:i:s') . ' - '. self::UPDATE_MESSAGE . "\n" . $payment->history);
			
			$payment->setExtraData('date', $data->date);
			$payment->setExtraData('transfer_date', $data->transfer_date);
			$payment->setExtraData('person', $data->payer_name);
			$payment->setExtraData('receipt_url', $data->paymentReceiptUrl ?? null);








|









|







159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
			// If accounting is enabled, we record the payment only if credit and debit accounts are set
			if (!$accounting || ($accounting && $form->id_credit_account && $form->id_debit_account)) {
				$id = DB::getInstance()->firstColumn(sprintf('SELECT id FROM %s WHERE email = \'%s\' LIMIT 1;', User::TABLE, $data->payer->email));
				$author_id = $id ?? null;
				$author_name = $data->payer->lastName . ' ' . $data->payer->firstName;
				$label = ($data->order ? $data->order->formName . ' - ' : '') . $data->payer_name . ' - ' . HelloAsso::PROVIDER_NAME . ' #' . $data->id;
				$accounts = $accounting ? [$form->id_credit_account, $form->id_debit_account] : null;
				$payment = Payments::createPayment(Payment::UNIQUE_TYPE, Payment::BANK_CARD_METHOD, self::STATUSES[$data->state], HelloAsso::PROVIDER_NAME, $accounts, $author_id, $author_name, $data->id, $label, $data->amount, $data, self::TRANSACTION_NOTE);
			}
		}
		else
		{
			if ($accounting && !$payment->id_transaction) { // Happens when the user decided to switch on the accounting while sync had already be done without accounting
				$transaction = Payments::createTransaction($payment, [$form->id_credit_account, $form->id_debit_account], self::TRANSACTION_NOTE);
				$payment->set('id_transaction', (int)$transaction->id);
			}
			$payment->set('amount', $data->amount);
			$payment->set('status', self::STATUSES[$data->state]);
			$payment->set('history', $data->date->format('Y-m-d H:i:s') . ' - '. self::UPDATE_MESSAGE . "\n" . $payment->history);
			
			$payment->setExtraData('date', $data->date);
			$payment->setExtraData('transfer_date', $data->transfer_date);
			$payment->setExtraData('person', $data->payer_name);
			$payment->setExtraData('receipt_url', $data->paymentReceiptUrl ?? null);

Added helloasso/public/callback.php version [3c39a07ff8].



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
<?php

namespace Garradin;

use Garradin\Plugin\HelloAsso\HelloAsso;

require_once __DIR__ . '/../../../../include/init.php';

HelloAsso::handleCallback();

Added helloasso/public/payer.php version [a6fba52e95].





































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

namespace Garradin;

echo '<html><head></head><body>
<h1>Redirected from the HelloAsso checkout</h1>';

echo '<pre>';

echo 'GET:' . "\n";
var_dump($_GET);

echo "\n\n". 'POST:' . "\n";
var_dump($_POST);

echo '</pre></body></html>';

/*
 * Expected GET content
 * 

array(4) {
  ["p"]=>
  string(2) "21"
  ["action"]=>
  string(6) "return"
  ["checkoutIntentId"]=>
  string(4) "6104"
  ["code"]=>
  string(9) "succeeded"
}

* 
*/

Modified helloasso/templates/index.tpl from [dcb6d484bc] to [8edf5d4ec2].

18
19
20
21
22
23
24
25





























26
			<th><a href="orders.php?id={$form.id}">{$form.name}</a></th>
			<td>{$form.type_label}</td>
			<td>{$form.state_label}</td>
		</tr>
		{/foreach}
	</tbody>
</table>






























{include file="_foot.tpl"}








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
			<th><a href="orders.php?id={$form.id}">{$form.name}</a></th>
			<td>{$form.type_label}</td>
			<td>{$form.state_label}</td>
		</tr>
		{/foreach}
	</tbody>
</table>

{if isset($checkout)}
	<h2 class="ruler">Tunnel de paiement</h2>
	<p class="confirm block">Tunnel de paiement généré avec succès.</p>
	<dl class="describe">
		<dt>ID</dt>
		<dd>{$checkout->id}</dd>
		<dt>URL</dt>
		<dd><a href="{$checkout->url}">{$checkout->url}</a></dd>
	</dl>
	<p class="help block">Pour rappel, ce tunnel de paiement/lien n'est valide que 15 minutes.</p>
{else}
	<h2 class="ruler">Créer un tunnel de paiement</h2>

	<form method="POST" action="{$self_url}">
		<fieldset>
			<legend>Paiement</legend>
			<dl>
				{input type="select" name="org_slug" label="Association" options=$orgOptions required=true}
				{input type="text" name="label" label="Libellé" required=true}
				{input type="money" name="amount" label="Montant" required=true}
				{input type="list" name="user" label="Membre" target="!users/selector.php" can_delete="true" required=true}
			</dl>
		</fieldset>

		{**** ToDo: add csrf token ****}
		{button type="submit" name="generate_checkout" label="Créer" class="main"}
	</form>
{/if}

{include file="_foot.tpl"}