You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

775 lines
24 KiB

  1. # Copyright 2019 Brainbean Apps (https://brainbeanapps.com)
  2. # Copyright 2022 CorporateHub (https://corporatehub.eu)
  3. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
  4. from datetime import datetime
  5. from dateutil.relativedelta import relativedelta
  6. from decimal import Decimal
  7. import json
  8. from unittest import mock
  9. from urllib.error import HTTPError
  10. from odoo import fields
  11. from odoo.exceptions import UserError
  12. from odoo.tests import common
  13. _module_ns = 'odoo.addons.account_bank_statement_import_online_paypal'
  14. _provider_class = (
  15. _module_ns
  16. + '.models.online_bank_statement_provider_paypal'
  17. + '.OnlineBankStatementProviderPayPal'
  18. )
  19. class FakeHTTPError(HTTPError):
  20. def __init__(self, content):
  21. self.content = content
  22. def read(self):
  23. return self.content.encode('utf-8')
  24. class UrlopenRetValMock:
  25. def __init__(self, content, throw=False):
  26. self.content = content
  27. self.throw = throw
  28. def __enter__(self):
  29. return self
  30. def __exit__(self, type, value, tb):
  31. pass
  32. def read(self):
  33. if self.throw:
  34. raise FakeHTTPError(self.content)
  35. return self.content.encode('utf-8')
  36. class TestAccountBankAccountStatementImportOnlinePayPal(
  37. common.TransactionCase
  38. ):
  39. def setUp(self):
  40. super().setUp()
  41. self.now = fields.Datetime.now()
  42. self.now_isoformat = self.now.isoformat() + '+0000'
  43. self.today = datetime(self.now.year, self.now.month, self.now.day)
  44. self.today_isoformat = self.today.isoformat() + '+0000'
  45. self.today_timestamp = str(int(self.today.timestamp()))
  46. self.yesterday = self.today - relativedelta(days=1)
  47. self.yesterday_isoformat = self.yesterday.isoformat() + '+0000'
  48. self.yesterday_timestamp = str(int(self.yesterday.timestamp()))
  49. self.currency_eur = self.env.ref('base.EUR')
  50. self.currency_usd = self.env.ref('base.USD')
  51. self.AccountJournal = self.env['account.journal']
  52. self.OnlineBankStatementProvider = self.env[
  53. 'online.bank.statement.provider'
  54. ]
  55. self.AccountBankStatement = self.env['account.bank.statement']
  56. self.AccountBankStatementLine = self.env['account.bank.statement.line']
  57. Provider = self.OnlineBankStatementProvider
  58. self.paypal_parse_transaction = lambda payload: (
  59. Provider._paypal_transaction_to_lines(
  60. Provider._paypal_preparse_transaction(
  61. json.loads(
  62. payload,
  63. parse_float=Decimal,
  64. )
  65. )
  66. )
  67. )
  68. self.mock_token = lambda: mock.patch(
  69. _provider_class + '._paypal_get_token',
  70. return_value='--TOKEN--',
  71. )
  72. def test_good_token(self):
  73. journal = self.AccountJournal.create({
  74. 'name': 'Bank',
  75. 'type': 'bank',
  76. 'code': 'BANK',
  77. 'currency_id': self.currency_eur.id,
  78. 'bank_statements_source': 'online',
  79. 'online_bank_statement_provider': 'paypal',
  80. })
  81. provider = journal.online_bank_statement_provider_id
  82. mocked_response = json.loads("""{
  83. "scope": "https://uri.paypal.com/services/reporting/search/read",
  84. "access_token": "---TOKEN---",
  85. "token_type": "Bearer",
  86. "app_id": "APP-1234567890",
  87. "expires_in": 32400,
  88. "nonce": "---NONCE---"
  89. }""", parse_float=Decimal)
  90. token = None
  91. with mock.patch(
  92. _provider_class + '._paypal_retrieve',
  93. return_value=mocked_response,
  94. ):
  95. token = provider._paypal_get_token()
  96. self.assertEqual(token, '---TOKEN---')
  97. def test_bad_token_scope(self):
  98. journal = self.AccountJournal.create({
  99. 'name': 'Bank',
  100. 'type': 'bank',
  101. 'code': 'BANK',
  102. 'currency_id': self.currency_eur.id,
  103. 'bank_statements_source': 'online',
  104. 'online_bank_statement_provider': 'paypal',
  105. })
  106. provider = journal.online_bank_statement_provider_id
  107. mocked_response = json.loads("""{
  108. "scope": "openid https://uri.paypal.com/services/applications/webhooks",
  109. "access_token": "---TOKEN---",
  110. "token_type": "Bearer",
  111. "app_id": "APP-1234567890",
  112. "expires_in": 32400,
  113. "nonce": "---NONCE---"
  114. }""", parse_float=Decimal)
  115. with mock.patch(
  116. _provider_class + '._paypal_retrieve',
  117. return_value=mocked_response,
  118. ):
  119. with self.assertRaises(Exception):
  120. provider._paypal_get_token()
  121. def test_bad_token_type(self):
  122. journal = self.AccountJournal.create({
  123. 'name': 'Bank',
  124. 'type': 'bank',
  125. 'code': 'BANK',
  126. 'currency_id': self.currency_eur.id,
  127. 'bank_statements_source': 'online',
  128. 'online_bank_statement_provider': 'paypal',
  129. })
  130. provider = journal.online_bank_statement_provider_id
  131. mocked_response = json.loads("""{
  132. "scope": "https://uri.paypal.com/services/reporting/search/read",
  133. "access_token": "---TOKEN---",
  134. "token_type": "NotBearer",
  135. "app_id": "APP-1234567890",
  136. "expires_in": 32400,
  137. "nonce": "---NONCE---"
  138. }""", parse_float=Decimal)
  139. with mock.patch(
  140. _provider_class + '._paypal_retrieve',
  141. return_value=mocked_response,
  142. ):
  143. with self.assertRaises(Exception):
  144. provider._paypal_get_token()
  145. def test_no_token(self):
  146. journal = self.AccountJournal.create({
  147. 'name': 'Bank',
  148. 'type': 'bank',
  149. 'code': 'BANK',
  150. 'currency_id': self.currency_eur.id,
  151. 'bank_statements_source': 'online',
  152. 'online_bank_statement_provider': 'paypal',
  153. })
  154. provider = journal.online_bank_statement_provider_id
  155. mocked_response = json.loads("""{
  156. "scope": "https://uri.paypal.com/services/reporting/search/read",
  157. "token_type": "Bearer",
  158. "app_id": "APP-1234567890",
  159. "expires_in": 32400,
  160. "nonce": "---NONCE---"
  161. }""", parse_float=Decimal)
  162. with mock.patch(
  163. _provider_class + '._paypal_retrieve',
  164. return_value=mocked_response,
  165. ):
  166. with self.assertRaises(Exception):
  167. provider._paypal_get_token()
  168. def test_no_data_on_monday(self):
  169. journal = self.AccountJournal.create({
  170. 'name': 'Bank',
  171. 'type': 'bank',
  172. 'code': 'BANK',
  173. 'currency_id': self.currency_eur.id,
  174. 'bank_statements_source': 'online',
  175. 'online_bank_statement_provider': 'paypal',
  176. })
  177. provider = journal.online_bank_statement_provider_id
  178. mocked_response_1 = UrlopenRetValMock("""{
  179. "debug_id": "eec890ebd5798",
  180. "details": "xxxxxx",
  181. "links": "xxxxxx",
  182. "message": "Data for the given start date is not available.",
  183. "name": "INVALID_REQUEST"
  184. }""", throw=True)
  185. mocked_response_2 = UrlopenRetValMock("""{
  186. "balances": [
  187. {
  188. "currency": "EUR",
  189. "primary": true,
  190. "total_balance": {
  191. "currency_code": "EUR",
  192. "value": "0.75"
  193. },
  194. "available_balance": {
  195. "currency_code": "EUR",
  196. "value": "0.75"
  197. },
  198. "withheld_balance": {
  199. "currency_code": "EUR",
  200. "value": "0.00"
  201. }
  202. }
  203. ],
  204. "account_id": "1234567890",
  205. "as_of_time": "%s",
  206. "last_refresh_time": "%s"
  207. }""" % (self.now_isoformat, self.now_isoformat,)
  208. )
  209. with mock.patch(
  210. _provider_class + '._paypal_urlopen',
  211. side_effect=[mocked_response_1, mocked_response_2],
  212. ), self.mock_token():
  213. data = provider.with_context(
  214. test_account_bank_statement_import_online_paypal_monday=True,
  215. )._obtain_statement_data(
  216. self.now - relativedelta(hours=1),
  217. self.now,
  218. )
  219. self.assertEqual(data, ([], {
  220. 'balance_start': 0.75,
  221. 'balance_end_real': 0.75,
  222. }))
  223. def test_error_handling_1(self):
  224. journal = self.AccountJournal.create({
  225. 'name': 'Bank',
  226. 'type': 'bank',
  227. 'code': 'BANK',
  228. 'currency_id': self.currency_eur.id,
  229. 'bank_statements_source': 'online',
  230. 'online_bank_statement_provider': 'paypal',
  231. })
  232. provider = journal.online_bank_statement_provider_id
  233. mocked_response = UrlopenRetValMock("""{
  234. "message": "MESSAGE",
  235. "name": "ERROR"
  236. }""", throw=True)
  237. with mock.patch(
  238. _provider_class + '._paypal_urlopen',
  239. return_value=mocked_response,
  240. ):
  241. with self.assertRaises(UserError):
  242. provider._paypal_retrieve('https://url', '')
  243. def test_error_handling_2(self):
  244. journal = self.AccountJournal.create({
  245. 'name': 'Bank',
  246. 'type': 'bank',
  247. 'code': 'BANK',
  248. 'currency_id': self.currency_eur.id,
  249. 'bank_statements_source': 'online',
  250. 'online_bank_statement_provider': 'paypal',
  251. })
  252. provider = journal.online_bank_statement_provider_id
  253. mocked_response = UrlopenRetValMock("""{
  254. "error_description": "ERROR DESCRIPTION",
  255. "error": "ERROR"
  256. }""", throw=True)
  257. with mock.patch(
  258. _provider_class + '._paypal_urlopen',
  259. return_value=mocked_response,
  260. ):
  261. with self.assertRaises(UserError):
  262. provider._paypal_retrieve('https://url', '')
  263. def test_empty_pull(self):
  264. journal = self.AccountJournal.create({
  265. 'name': 'Bank',
  266. 'type': 'bank',
  267. 'code': 'BANK',
  268. 'currency_id': self.currency_eur.id,
  269. 'bank_statements_source': 'online',
  270. 'online_bank_statement_provider': 'paypal',
  271. })
  272. provider = journal.online_bank_statement_provider_id
  273. mocked_response_1 = json.loads("""{
  274. "transaction_details": [],
  275. "account_number": "1234567890",
  276. "start_date": "%s",
  277. "end_date": "%s",
  278. "last_refreshed_datetime": "%s",
  279. "page": 1,
  280. "total_items": 0,
  281. "total_pages": 0
  282. }""" % (self.now_isoformat, self.now_isoformat, self.now_isoformat,),
  283. parse_float=Decimal,
  284. )
  285. mocked_response_2 = json.loads(
  286. """{
  287. "balances": [
  288. {
  289. "currency": "EUR",
  290. "primary": true,
  291. "total_balance": {
  292. "currency_code": "EUR",
  293. "value": "0.75"
  294. },
  295. "available_balance": {
  296. "currency_code": "EUR",
  297. "value": "0.75"
  298. },
  299. "withheld_balance": {
  300. "currency_code": "EUR",
  301. "value": "0.00"
  302. }
  303. }
  304. ],
  305. "account_id": "1234567890",
  306. "as_of_time": "%s",
  307. "last_refresh_time": "%s"
  308. }"""
  309. % (self.now_isoformat, self.now_isoformat,),
  310. parse_float=Decimal,
  311. )
  312. with mock.patch(
  313. _provider_class + '._paypal_retrieve',
  314. side_effect=[mocked_response_1, mocked_response_2],
  315. ), self.mock_token():
  316. data = provider._obtain_statement_data(
  317. self.now - relativedelta(hours=1),
  318. self.now,
  319. )
  320. self.assertEqual(data, ([], {
  321. 'balance_start': 0.75,
  322. 'balance_end_real': 0.75,
  323. }))
  324. def test_ancient_pull(self):
  325. journal = self.AccountJournal.create({
  326. 'name': 'Bank',
  327. 'type': 'bank',
  328. 'code': 'BANK',
  329. 'currency_id': self.currency_eur.id,
  330. 'bank_statements_source': 'online',
  331. 'online_bank_statement_provider': 'paypal',
  332. })
  333. provider = journal.online_bank_statement_provider_id
  334. mocked_response = json.loads("""{
  335. "transaction_details": [],
  336. "account_number": "1234567890",
  337. "start_date": "%s",
  338. "end_date": "%s",
  339. "last_refreshed_datetime": "%s",
  340. "page": 1,
  341. "total_items": 0,
  342. "total_pages": 0
  343. }""" % (self.now_isoformat, self.now_isoformat, self.now_isoformat,),
  344. parse_float=Decimal,
  345. )
  346. with mock.patch(
  347. _provider_class + '._paypal_retrieve',
  348. return_value=mocked_response,
  349. ), self.mock_token():
  350. with self.assertRaises(Exception):
  351. provider._obtain_statement_data(
  352. self.now - relativedelta(years=5),
  353. self.now,
  354. )
  355. def test_pull(self):
  356. journal = self.AccountJournal.create({
  357. 'name': 'Bank',
  358. 'type': 'bank',
  359. 'code': 'BANK',
  360. 'currency_id': self.currency_eur.id,
  361. 'bank_statements_source': 'online',
  362. 'online_bank_statement_provider': 'paypal',
  363. })
  364. provider = journal.online_bank_statement_provider_id
  365. mocked_response = json.loads("""{
  366. "transaction_details": [{
  367. "transaction_info": {
  368. "paypal_account_id": "1234567890",
  369. "transaction_id": "1234567890",
  370. "transaction_event_code": "T1234",
  371. "transaction_initiation_date": "%s",
  372. "transaction_updated_date": "%s",
  373. "transaction_amount": {
  374. "currency_code": "USD",
  375. "value": "1000.00"
  376. },
  377. "fee_amount": {
  378. "currency_code": "USD",
  379. "value": "-100.00"
  380. },
  381. "transaction_status": "S",
  382. "transaction_subject": "Payment for Invoice(s) 1",
  383. "ending_balance": {
  384. "currency_code": "USD",
  385. "value": "900.00"
  386. },
  387. "available_balance": {
  388. "currency_code": "USD",
  389. "value": "900.00"
  390. },
  391. "invoice_id": "1"
  392. },
  393. "payer_info": {
  394. "account_id": "1234567890",
  395. "email_address": "partner@example.com",
  396. "address_status": "Y",
  397. "payer_status": "N",
  398. "payer_name": {
  399. "alternate_full_name": "Acme, Inc."
  400. },
  401. "country_code": "US"
  402. },
  403. "shipping_info": {},
  404. "cart_info": {},
  405. "store_info": {},
  406. "auction_info": {},
  407. "incentive_info": {}
  408. }, {
  409. "transaction_info": {
  410. "paypal_account_id": "1234567890",
  411. "transaction_id": "1234567891",
  412. "transaction_event_code": "T1234",
  413. "transaction_initiation_date": "%s",
  414. "transaction_updated_date": "%s",
  415. "transaction_amount": {
  416. "currency_code": "USD",
  417. "value": "1000.00"
  418. },
  419. "fee_amount": {
  420. "currency_code": "USD",
  421. "value": "-100.00"
  422. },
  423. "transaction_status": "S",
  424. "transaction_subject": "Payment for Invoice(s) 1",
  425. "ending_balance": {
  426. "currency_code": "USD",
  427. "value": "900.00"
  428. },
  429. "available_balance": {
  430. "currency_code": "USD",
  431. "value": "900.00"
  432. },
  433. "invoice_id": "1"
  434. },
  435. "payer_info": {
  436. "account_id": "1234567890",
  437. "email_address": "partner@example.com",
  438. "address_status": "Y",
  439. "payer_status": "N",
  440. "payer_name": {
  441. "alternate_full_name": "Acme, Inc."
  442. },
  443. "country_code": "US"
  444. },
  445. "shipping_info": {},
  446. "cart_info": {},
  447. "store_info": {},
  448. "auction_info": {},
  449. "incentive_info": {}
  450. }],
  451. "account_number": "1234567890",
  452. "start_date": "%s",
  453. "end_date": "%s",
  454. "last_refreshed_datetime": "%s",
  455. "page": 1,
  456. "total_items": 1,
  457. "total_pages": 1
  458. }""" % (
  459. self.yesterday_isoformat,
  460. self.yesterday_isoformat,
  461. self.today_isoformat,
  462. self.today_isoformat,
  463. self.yesterday_isoformat,
  464. self.today_isoformat,
  465. self.now_isoformat,
  466. ),
  467. parse_float=Decimal,
  468. )
  469. with mock.patch(
  470. _provider_class + '._paypal_retrieve',
  471. return_value=mocked_response,
  472. ), self.mock_token():
  473. data = provider._obtain_statement_data(self.yesterday, self.today,)
  474. self.assertEqual(len(data[0]), 2)
  475. self.assertEqual(
  476. data[0][0],
  477. {
  478. "date": self.yesterday,
  479. "amount": "1000.00",
  480. "name": "Invoice 1",
  481. "note": "1234567890: Payment for Invoice(s) 1",
  482. "partner_name": "Acme, Inc.",
  483. "unique_import_id": "1234567890-%s" % (self.yesterday_timestamp,),
  484. },
  485. )
  486. self.assertEqual(
  487. data[0][1],
  488. {
  489. "date": self.yesterday,
  490. "amount": "-100.00",
  491. "name": "Fee for Invoice 1",
  492. "note": "Transaction fee for 1234567890: Payment for Invoice(s) 1",
  493. "partner_name": "PayPal",
  494. "unique_import_id": "1234567890-%s-FEE" % (self.yesterday_timestamp,),
  495. },
  496. )
  497. self.assertEqual(data[1], {"balance_start": 0.0, "balance_end_real": 900.0})
  498. def test_transaction_parse_1(self):
  499. lines = self.paypal_parse_transaction("""{
  500. "transaction_info": {
  501. "paypal_account_id": "1234567890",
  502. "transaction_id": "1234567890",
  503. "transaction_event_code": "T1234",
  504. "transaction_initiation_date": "%s",
  505. "transaction_updated_date": "%s",
  506. "transaction_amount": {
  507. "currency_code": "USD",
  508. "value": "1000.00"
  509. },
  510. "fee_amount": {
  511. "currency_code": "USD",
  512. "value": "0.00"
  513. },
  514. "transaction_status": "S",
  515. "transaction_subject": "Payment for Invoice(s) 1",
  516. "ending_balance": {
  517. "currency_code": "USD",
  518. "value": "1000.00"
  519. },
  520. "available_balance": {
  521. "currency_code": "USD",
  522. "value": "1000.00"
  523. },
  524. "invoice_id": "1"
  525. },
  526. "payer_info": {
  527. "account_id": "1234567890",
  528. "email_address": "partner@example.com",
  529. "address_status": "Y",
  530. "payer_status": "N",
  531. "payer_name": {
  532. "alternate_full_name": "Acme, Inc."
  533. },
  534. "country_code": "US"
  535. },
  536. "shipping_info": {},
  537. "cart_info": {},
  538. "store_info": {},
  539. "auction_info": {},
  540. "incentive_info": {}
  541. }""" % (self.today_isoformat, self.today_isoformat,)
  542. )
  543. self.assertEqual(len(lines), 1)
  544. self.assertEqual(
  545. lines[0],
  546. {
  547. "date": self.today,
  548. "amount": "1000.00",
  549. "name": "Invoice 1",
  550. "note": "1234567890: Payment for Invoice(s) 1",
  551. "partner_name": "Acme, Inc.",
  552. "unique_import_id": "1234567890-%s" % (self.today_timestamp,),
  553. },
  554. )
  555. def test_transaction_parse_2(self):
  556. lines = self.paypal_parse_transaction("""{
  557. "transaction_info": {
  558. "paypal_account_id": "1234567890",
  559. "transaction_id": "1234567890",
  560. "transaction_event_code": "T1234",
  561. "transaction_initiation_date": "%s",
  562. "transaction_updated_date": "%s",
  563. "transaction_amount": {
  564. "currency_code": "USD",
  565. "value": "1000.00"
  566. },
  567. "fee_amount": {
  568. "currency_code": "USD",
  569. "value": "0.00"
  570. },
  571. "transaction_status": "S",
  572. "transaction_subject": "Payment for Invoice(s) 1",
  573. "ending_balance": {
  574. "currency_code": "USD",
  575. "value": "1000.00"
  576. },
  577. "available_balance": {
  578. "currency_code": "USD",
  579. "value": "1000.00"
  580. },
  581. "invoice_id": "1"
  582. },
  583. "payer_info": {
  584. "account_id": "1234567890",
  585. "email_address": "partner@example.com",
  586. "address_status": "Y",
  587. "payer_status": "N",
  588. "payer_name": {
  589. "alternate_full_name": "Acme, Inc."
  590. },
  591. "country_code": "US"
  592. },
  593. "shipping_info": {},
  594. "cart_info": {},
  595. "store_info": {},
  596. "auction_info": {},
  597. "incentive_info": {}
  598. }""" % (self.today_isoformat, self.today_isoformat,)
  599. )
  600. self.assertEqual(len(lines), 1)
  601. self.assertEqual(
  602. lines[0],
  603. {
  604. "date": self.today,
  605. "amount": "1000.00",
  606. "name": "Invoice 1",
  607. "note": "1234567890: Payment for Invoice(s) 1",
  608. "partner_name": "Acme, Inc.",
  609. "unique_import_id": "1234567890-%s" % (self.today_timestamp,),
  610. },
  611. )
  612. def test_transaction_parse_3(self):
  613. lines = self.paypal_parse_transaction("""{
  614. "transaction_info": {
  615. "paypal_account_id": "1234567890",
  616. "transaction_id": "1234567890",
  617. "transaction_event_code": "T1234",
  618. "transaction_initiation_date": "%s",
  619. "transaction_updated_date": "%s",
  620. "transaction_amount": {
  621. "currency_code": "USD",
  622. "value": "1000.00"
  623. },
  624. "fee_amount": {
  625. "currency_code": "USD",
  626. "value": "-100.00"
  627. },
  628. "transaction_status": "S",
  629. "transaction_subject": "Payment for Invoice(s) 1",
  630. "ending_balance": {
  631. "currency_code": "USD",
  632. "value": "900.00"
  633. },
  634. "available_balance": {
  635. "currency_code": "USD",
  636. "value": "900.00"
  637. },
  638. "invoice_id": "1"
  639. },
  640. "payer_info": {
  641. "account_id": "1234567890",
  642. "email_address": "partner@example.com",
  643. "address_status": "Y",
  644. "payer_status": "N",
  645. "payer_name": {
  646. "alternate_full_name": "Acme, Inc."
  647. },
  648. "country_code": "US"
  649. },
  650. "shipping_info": {},
  651. "cart_info": {},
  652. "store_info": {},
  653. "auction_info": {},
  654. "incentive_info": {}
  655. }""" % (self.today_isoformat, self.today_isoformat,)
  656. )
  657. self.assertEqual(len(lines), 2)
  658. self.assertEqual(
  659. lines[0],
  660. {
  661. "date": self.today,
  662. "amount": "1000.00",
  663. "name": "Invoice 1",
  664. "note": "1234567890: Payment for Invoice(s) 1",
  665. "partner_name": "Acme, Inc.",
  666. "unique_import_id": "1234567890-%s" % (self.today_timestamp,),
  667. },
  668. )
  669. self.assertEqual(
  670. lines[1],
  671. {
  672. "date": self.today,
  673. "amount": "-100.00",
  674. "name": "Fee for Invoice 1",
  675. "note": "Transaction fee for 1234567890: Payment for Invoice(s) 1",
  676. "partner_name": "PayPal",
  677. "unique_import_id": "1234567890-%s-FEE" % (self.today_timestamp,),
  678. },
  679. )
  680. def test_transaction_parse_4(self):
  681. lines = self.paypal_parse_transaction("""{
  682. "transaction_info": {
  683. "paypal_account_id": "1234567890",
  684. "transaction_id": "1234567890",
  685. "transaction_event_code": "T1234",
  686. "transaction_initiation_date": "%s",
  687. "transaction_updated_date": "%s",
  688. "transaction_amount": {
  689. "currency_code": "USD",
  690. "value": "1000.00"
  691. },
  692. "transaction_status": "S",
  693. "transaction_subject": "Payment for Invoice(s) 1",
  694. "ending_balance": {
  695. "currency_code": "USD",
  696. "value": "1000.00"
  697. },
  698. "available_balance": {
  699. "currency_code": "USD",
  700. "value": "1000.00"
  701. },
  702. "invoice_id": "1"
  703. },
  704. "payer_info": {
  705. "account_id": "1234567890",
  706. "email_address": "partner@example.com",
  707. "address_status": "Y",
  708. "payer_status": "N",
  709. "payer_name": {
  710. "alternate_full_name": "Acme, Inc."
  711. },
  712. "country_code": "US"
  713. },
  714. "shipping_info": {},
  715. "cart_info": {},
  716. "store_info": {},
  717. "auction_info": {},
  718. "incentive_info": {}
  719. }""" % (self.today_isoformat, self.today_isoformat,)
  720. )
  721. self.assertEqual(len(lines), 1)
  722. self.assertEqual(
  723. lines[0],
  724. {
  725. "date": self.today,
  726. "amount": "1000.00",
  727. "name": "Invoice 1",
  728. "note": "1234567890: Payment for Invoice(s) 1",
  729. "partner_name": "Acme, Inc.",
  730. "unique_import_id": "1234567890-%s" % (self.today_timestamp,),
  731. },
  732. )