Skip to content

Commit

Permalink
Merge pull request #135 from wmde/payment-with-ppl-api
Browse files Browse the repository at this point in the history
Implement new PayPal API
Implement new parameter passing from app and bounded contexts
  • Loading branch information
gbirke authored Oct 23, 2023
2 parents 710a238 + b68c008 commit f05fbe3
Show file tree
Hide file tree
Showing 140 changed files with 5,655 additions and 647 deletions.
10 changes: 10 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# URL of the paypal API v1 endpoint
PAYPAL_API_URL="https://example.com/"

# See https://developer.paypal.com/api/rest/authentication/
PAYPAL_API_CLIENT_ID="something"
PAYPAL_API_CLIENT_SECRET="n0ts0s3cr1t"

# Database connection string for connecting to a local database wit the the doctrine CLI
# Only needed if you're using the bin/doctrine script in the bounded context
DB_DSN="mysqli:/user:password@dtabase-server/database-name"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ coverage/

.idea/

.env

15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![Code Coverage](https://scrutinizer-ci.com/g/wmde/fundraising-payments/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/wmde/fundraising-payments/?branch=master)

Bounded Context for the Wikimedia Deutschland fundraising payment (sub-)domain. Used by the
[user facing donation application](https://github.com/wmde/FundraisingFrontend) and the
[user facing donation application](https://github.com/wmde/fundraising-application) and the
"Fundraising Operations Center" (which is not public software).

## Installation
Expand All @@ -22,6 +22,19 @@ file that just defines a dependency on Fundraising Payments 1.x:
}
```

## Setting up the PayPal API
The payment context calls the PayPal REST API to create payments.
These API calls need credentials and a one-time setup of subscription plans
(i.e. definition of recurring payments) on the PayPal server.
There is a command line tool to do the subscription plan setup.
You can call this command (`create-subscription-plans`) from the console in [Fundraising Application](https://github.com/wmde/fundraising-application)
or from the `bin/console` file in this bounded context.

There is another command, `list-subscription-plans` that lists all the configured plans.

See [Configuring the PayPal API](docs/paypal_api.md) for more details on these commands and their configuration.


## Development

This project has a [Makefile](Makefile) that runs all tasks in Docker containers via
Expand Down
41 changes: 41 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env php
<?php

require __DIR__.'/../vendor/autoload.php';

use GuzzleHttp\Client;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Dotenv\Dotenv;
use WMDE\Fundraising\PaymentContext\Services\PayPal\GuzzlePaypalAPI;

$dotenv = new Dotenv();
$dotenv->load( __DIR__ . '/../.env' );

function createPayPalAPI(): \WMDE\Fundraising\PaymentContext\Services\PayPal\PayPalAPI {
$clientId = $_ENV['PAYPAL_API_CLIENT_ID'] ?? '';
$secret = $_ENV['PAYPAL_API_CLIENT_SECRET'] ?? '';
$baseUri = $_ENV['PAYPAL_API_URL'] ?? '';
if ( !$clientId || !$secret || !$baseUri ) {
echo "You must put PAYPAL_API_URL, PAYPAL_API_CLIENT_ID and PAYPAL_API_CLIENT_SECRET\n";
exit( Command::FAILURE );
}

return new GuzzlePaypalAPI(
new Client( [ 'base_uri' => $baseUri ] ),
$clientId,
$secret,
new NullLogger()
);
}

$api = createPayPalAPI();

$application = new Application();

$application->add( new \WMDE\Fundraising\PaymentContext\Commands\ListSubscriptionPlansCommand( $api ) );
$application->add( new \WMDE\Fundraising\PaymentContext\Commands\CreateSubscriptionPlansCommand( $api ) );


$application->run();
42 changes: 42 additions & 0 deletions bin/doctrine
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env php
<?php

use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tools\DsnParser;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
use Symfony\Component\Dotenv\Dotenv;
use WMDE\Fundraising\PaymentContext\PaymentContextFactory;

require __DIR__.'/../vendor/autoload.php';

$dotenv = new Dotenv();
$dotenv->load( __DIR__ . '/../.env' );

function createEntityManager(): EntityManager {
if (empty( $_ENV['DB_DSN'] ) ) {
echo "You must set the database connection string in 'DB_DSN'\n";
exit(1);
}
$dsnParser = new DsnParser(['mysql' => 'pdo_mysql']);
$connectionParams = $dsnParser
->parse( $_ENV['DB_DSN'] );
$connection = DriverManager::getConnection( $connectionParams );

$contextFactory = new PaymentContextFactory();
$contextFactory->registerCustomTypes( $connection );
$doctrineConfig = ORMSetup::createXMLMetadataConfiguration(
$contextFactory->getDoctrineMappingPaths(),
true
);

return new EntityManager( $connection, $doctrineConfig );
}


ConsoleRunner::run(
new SingleManagerProvider(createEntityManager()),
[]
);
11 changes: 8 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@
"doctrine/orm": "^2.16.1",
"doctrine/dbal": "^3.3",
"doctrine/migrations": "^3.5",
"guzzlehttp/guzzle": "^7.4",
"sofort/sofortlib-php": "^3.2",
"symfony/cache": "^6.0",
"guzzlehttp/guzzle": "^7.4"
"symfony/cache": "^6.3",
"symfony/console": "^6.3",
"symfony/dotenv": "^6.3",
"symfony/yaml": "^6.3",
"symfony/config": "^6.3"
},
"require-dev": {
"phpunit/phpunit": "~10.1",
"wmde/fundraising-phpcs": "~9.0",
"wmde/inspector-generator": "dev-main",
"phpstan/phpstan": "~1.3",
"phpstan/phpstan-phpunit": "^1.0",
"qossmic/deptrac-shim": "^1.0"
"qossmic/deptrac-shim": "^1.0",
"wmde/psr-log-test-doubles": "~v3.2.0"
},
"repositories": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="WMDE\Fundraising\PaymentContext\Domain\Model\PayPalOrder">
<field name="transactionId" type="string" column="transaction_id" nullable="true" />
<field name="orderId" type="string" column="order_id" nullable="false" />

<indexes>
<index name="transaction_id_idx" columns="transaction_id" />
<index name="order_id_idx" columns="order_id" />
</indexes>
</entity>
</doctrine-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="WMDE\Fundraising\PaymentContext\Domain\Model\PayPalPaymentIdentifier" table="payment_paypal_identifier" inheritance-type="SINGLE_TABLE">
<discriminator-column name="identifier_type" type="string" length="1"/>
<discriminator-map>
<discriminator-mapping value="S" class="WMDE\Fundraising\PaymentContext\Domain\Model\PayPalSubscription"/>
<discriminator-mapping value="O" class="WMDE\Fundraising\PaymentContext\Domain\Model\PayPalOrder"/>
</discriminator-map>
<id name="payment" association-key="true" />
<one-to-one field="payment" target-entity="WMDE\Fundraising\PaymentContext\Domain\Model\Payment" >
<cascade>
<cascade-persist />
</cascade>
</one-to-one>
</entity>
</doctrine-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="WMDE\Fundraising\PaymentContext\Domain\Model\PayPalSubscription">
<field name="subscriptionId" type="string" column="subscription_id" nullable="false" />

<indexes>
<index name="subscription_id_idx" columns="subscription_id" />
</indexes>
</entity>
</doctrine-mapping>
102 changes: 102 additions & 0 deletions config/paypal_api.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Configuration file for setting up subscription plans for different products and locales
#
# The first level of the configuration is the "product" level.
# In our application, we currently have two products "donation" and "membership".
# The distinction lies in the _names_ of the products and subscription plans,
# which will be shown on the PayPal page.
#
# The second level of the configuration is the locale level.
# Each product has to have definitions for each locale, e.g. de_DE or en_GB.

donation:
de_DE:
# `product_id` is an ID generated by us. It follows the pattern of <productName>_<languageName>_<number>
# <languageName> is the first part of the locale, e.g. 'de' in de_DE
# The CLI for creating subscription plans uses this product ID to
# reference the product for each subscription plan
product_id: 'donation_de_1'
# The localized product name, used both for subscription plans and one-time payments
# Currently not shown in the PayPal UI for subscription plans, only on the
# PayPal checkout page for one-time payments
product_name: 'Spende an Wikimedia'
subscription_plans:
# ID is generated automatically by PayPal when we create subscription plans with the CLI
# Before the creation, ID can be a stub, after the creation please insert the generated IDs
- id: '123'
# the (public) string that the user sees on the checkout page at PayPal
name: 'Monatliche Spende an Wikimedia'
# internal value, should be a valid string for type PaymentInterval
interval: 'Monthly'
- id: '496'
name: 'Vierteljährliche Spende an Wikimedia'
interval: 'Quarterly'
- id: '456'
name: 'Halbjährliche Spende an Wikimedia'
interval: 'HalfYearly'
- id: 'ABC'
name: 'Jährliche Spende an Wikimedia'
interval: 'Yearly'
# the page the user gets redirected to when the PayPal payment was successfully created
# Where do they get redirected from? From PayPal page
# The UrlAuthenticator will append access tokens to the URL
return_url: 'https://test-spenden.wikimedia.de/show-donation-confirmation?'
# the page the user gets redirected to when they hit 'cancel'
# Where do they get redirected from? From PayPal page
# The UrlAuthenticator will append access tokens to the URL
cancel_url: 'https://test-spenden.wikimedia.de/new?id='

en_GB:
product_id: 'donation_en_1'
product_name: 'Donation to Wikimedia'
subscription_plans:
- id: '786'
name: 'Monthly donation to Wikimedia'
interval: 'Monthly'
- id: 'GHI'
name: 'Quarterly donation to Wikimedia'
interval: 'Quarterly'
- id: 'JKL'
name: 'Half yearly donation to Wikimedia'
interval: 'HalfYearly'
- id: 'NMP'
name: 'Yearly donation to Wikimedia'
interval: 'Yearly'
return_url: 'https://test-spenden.wikimedia.de/show-donation-confirmation?&lang=en'
cancel_url: 'https://test-spenden.wikimedia.de/new?&lang=en'
membership:
de_DE:
product_id: 'membership_de_1'
product_name: 'Mitgliedschaftsbeitrag an Wikimedia'
subscription_plans:
- id: '333'
name: 'Monatlicher Mitgliedschaftsbeitrag an Wikimedia'
interval: 'Monthly'
- id: 'MNT'
name: 'Vierteljährlicher Mitgliedschaftsbeitrag an Wikimedia'
interval: 'Quarterly'
- id: 'DEN'
name: 'Halbjährlicher Mitgliedschaftsbeitrag an Wikimedia'
interval: 'HalfYearly'
- id: 'MBB'
name: 'Jährlicher Mitgliedschaftsbeitrag an Wikimedia'
interval: 'Yearly'
return_url: 'https://test-spenden.wikimedia.de/show-membership-confirmation?'
cancel_url: 'https://test-spenden.wikimedia.de/apply-for-membership?'
en_GB:
product_id: 'membership_en_1'
product_name: 'Wikimedia Membership'
subscription_plans:
- id: '716'
name: 'Monthly membership fee for Wikimedia'
interval: 'Monthly'
- id: 'XZA'
name: 'Quarterly membership fee for Wikimedia'
interval: 'Quarterly'
- id: 'VCX'
name: 'Half yearly membership fee for Wikimedia'
interval: 'HalfYearly'
- id: 'MNB'
name: 'Yearly membership fee for Wikimedia'
interval: 'Yearly'
return_url: 'https://test-spenden.wikimedia.de/show-membership-confirmation?lang=en'
cancel_url: 'https://test-spenden.wikimedia.de/apply-for-membership?lang=en'
40 changes: 34 additions & 6 deletions deptrac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,28 @@ deptrac:
collectors:
- type: classLike
value: WMDE\\Fundraising\\PaymentContext\\DataAccess.*
- name: ServiceInterface
- type: class
value: WMDE\\Fundraising\\PaymentContext\\ScalarTypeConverter
# Interfaces and value objects for services
- name: ServiceModel
collectors:
- type: interface
value: WMDE\\Fundraising\\PaymentContext\\Services.*
- type: class
value: WMDE\\Fundraising\\PaymentContext\\Services\\Paypal\\Model.*
- type: classLike
value: WMDE\\Fundraising\\PaymentContext\\Services\\PaymentUrlGenerator\\Sofort.*
- name: Service
collectors:
- type: class
value: WMDE\\Fundraising\\PaymentContext\\Services.*
- type: bool
must:
- type: class
value: WMDE\\Fundraising\\PaymentContext\\Services.*
must_not:
- type: class
value: WMDE\\Fundraising\\PaymentContext\\Services\\Paypal\\Model.*
- type: class
value: WMDE\\Fundraising\\PaymentContext\\Services\\PaymentUrlGenerator\\Sofort.*
# Domain libraries from WMDE
- name: DomainLibrary
collectors:
Expand All @@ -44,6 +58,14 @@ deptrac:
collectors:
- type: classNameRegex
value: /^Sofort\\.*/
- name: Psr
collectors:
- type: classNameRegex
value: /^Psr\\.*/
- name: Symfony Config
collectors:
- type: classNameRegex
value: /^Symfony\\Component\\(Config|Yaml)\\.*/
ruleset:
Domain:
- DomainLibrary
Expand All @@ -53,6 +75,7 @@ deptrac:
- ServiceInterface
- DomainLibrary
- DomainValidators
- ServiceModel
DataAccess:
- Domain
- DomainLibrary
Expand All @@ -67,24 +90,29 @@ deptrac:
# into the Domain layer.
- UseCase
- DataAccess
- ServiceInterface
- ServiceModel
# Maybe the Services should not directly depend on Doctrine but should go through the `DataAccess` layer
# TODO: Move DoctrineTransactionIdFinder.php and UniquePaymentReferenceCodeGenerator.php into DataAccess
- Doctrine
- Guzzle
- Sofort
ServiceInterface:
- Psr
- Symfony Config
ServiceModel:
- Domain
- DomainLibrary
- ServiceInterface
formatters:
graphviz:
groups:
Service:
- Service
- ServiceInterface
- ServiceModel
Vendor:
- Doctrine
- Guzzle
- Sofort
- Psr
- Symfony Config


Loading

0 comments on commit f05fbe3

Please sign in to comment.