Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(payment_link): add multiple custom css support in business level #5137

Conversation

sahkal
Copy link
Contributor

@sahkal sahkal commented Jun 26, 2024

Type of Change

  • Enhancement

Description

Currently we support one theme config for payment link that can be set in business profile level, with this PR we will allow multiple payment link theme configuration in business profile.

Additional Changes

  • This PR modifies the API contract
"payment_link_config": {
        "theme": "#8A0808",
        "logo": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg",
        "seller_name": "sahkal",
        "display_sdk_only": false,
        "business_specific_configs": {
            "key1": {
                "theme": "#3B845E",
                "logo": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg",
                "seller_name": "sahkal",
                "display_sdk_only": true
            },
            "key2": {
                "theme": "#B202FF",
                "logo": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg",
                "seller_name": "sahkal",
                "enabled_saved_payment_method":true
                // "display_sdk_only": true
            }
        }
    }
}

we are adding a new optional param that is business_specific_configs for multi theme configuration

How did you test it?

Create Merchant account

curl --location 'http://localhost:8080/accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data-raw '{
  "merchant_id": "merchant_1719479157",
  "locker_id": "m0010",
  "merchant_name": "NewAge Retailer",
  "merchant_details": {
    "primary_contact_person": "John Test",
    "primary_email": "[email protected]",
    "primary_phone": "sunt laborum",
    "secondary_contact_person": "John Test2",
    "secondary_email": "[email protected]",
    "secondary_phone": "cillum do dolor id",
    "website": "www.example.com",
    "about_business": "Online Retail with a wide selection of organic products for North America",
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US"
    }
  },
  "webhook_details": {
    "webhook_version": "1.0.1",
    "webhook_username": "ekart_retail",
    "webhook_password": "password_ekart@123",
    "webhook_url":"https://eny91mlyw8bh.x.pipedream.net",
    "payment_created_enabled": true,
    "payment_succeeded_enabled": true,
    "payment_failed_enabled": true
  },
  "sub_merchants_enabled": false,
  "metadata": {
    "city": "NY",
    "unit": "245"
  },
  "primary_business_details": [
    {
      "country": "US",
      "business": "default"
    }
  ]
}'

Create Payment Connector

curl --location 'http://localhost:8080/account/merchant_1719470020/connectors' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '
{
    "connector_type": "fiz_operations",
    "connector_name": "stripe",
    "connector_account_details": {
        "auth_type": "HeaderKey",
        "api_key":  {stripe-api-key}
    },
    "test_mode": true,
    "disabled": false,
    "connector_webhook_details": {
        "merchant_secret": {stripe-merchant-secret}
    },
    
    "payment_methods_enabled": [
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "credit",
                    "card_networks": [
                        "AmericanExpress",
                        "DinersClub",
                        "Discover",
                        "JCB",
                        "Mastercard",
                        "Visa"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "accepted_currencies": {
                        "type": "enable_only",
                        "list": [
                            "CZK",
                            "RON",
                            "TRY",
                            "USD",
                            "GBP",
                            "CAD",
                            "AUD",
                            "ARS",
                            "CHF",
                            "CNY",
                            "DKK",
                            "GHS",
                            "JPY",
                            "PLN",
                            "NGN",
                            "NOK",
                            "KES",
                            "SEK",
                            "NZD",
                            "ZAR",
                            "SGD",
                            "INR"
                        ]
                    }
                },
                {
                    "payment_method_type": "debit",
                    "card_networks": [
                        "AmericanExpress",
                        "DinersClub",
                        "Discover",
                        "JCB",
                        "Mastercard",
                        "Visa"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "accepted_currencies": {
                        "type": "enable_only",
                        "list": [
                            "CZK",
                            "RON",
                            "TRY",
                            "USD",
                            "GBP",
                            "CAD",
                            "AUD",
                            "ARS",
                            "CHF",
                            "CNY",
                            "DKK",
                            "GHS",
                            "JPY",
                            "PLN",
                            "NGN",
                            "NOK",
                            "KES",
                            "SEK",
                            "NZD",
                            "ZAR",
                            "SGD",
                            "INR"
                        ]
                    }
                },
                {
                    "payment_method_type": "credit",
                    "card_networks": [
                        "Mastercard",
                        "Visa"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "accepted_currencies": {
                        "type": "enable_only",
                        "list": [
                            "EUR"
                        ]
                    }
                },
                {
                    "payment_method_type": "debit",
                    "card_networks": [
                        "Mastercard",
                        "Visa"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "accepted_currencies": {
                        "type": "enable_only",
                        "list": [
                            "EUR"
                        ]
                    }
                }
            ]
        },
        {
            "payment_method": "pay_later",
            "payment_method_types": [
                {
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "payment_experience": "redirect_to_url",
                    "payment_method_type": "affirm"
                }
            ]
        },
        {
            "payment_method": "pay_later",
            "payment_method_types": [
                {
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "payment_experience": "redirect_to_url",
                    "payment_method_type": "afterpay_clearpay"
                }
            ]
        },
        {
            "payment_method": "bank_redirect",
            "payment_method_types": [
                {
                    "payment_method_type": "sofort",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": false
                },
                {
                    "payment_method_type": "bancontact_card",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": false
                },
                {
                    "payment_method_type": "ideal",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": false
                },
                {
                    "payment_method_type": "blik",
                    "payment_experience": "redirect_to_url",
                    "card_networks": null,
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "pay_later",
            "payment_method_types": [
                {
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "payment_experience": "redirect_to_url",
                    "payment_method_type": "klarna"
                }
            ]
        },
        {
            "payment_method": "pay_later",
            "payment_method_types": [
                {
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true,
                    "payment_experience": "invoke_sdk_client",
                    "payment_method_type": "klarna"
                }
            ]
        },
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "credit",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "debit",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "wallet",
            "payment_method_types": [
                {
                    "payment_method_type": "apple_pay",
                    "payment_experience": "invoke_sdk_client",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "wallet",
            "payment_method_types": [
                {
                    "payment_method_type": "google_pay",
                    "payment_experience": "invoke_sdk_client",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "paypal",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "installment_payment_enabled": true,
                    "recurring_enabled": true
                }
            ]
        }
    ],
    "metadata": {
        "google_pay": {
            "allowed_payment_methods": [
                {
                    "type": "CARD",
                    "parameters": {
                        "allowed_auth_methods": [
                            "PAN_ONLY",
                            "CRYPTOGRAM_3DS"
                        ],
                        "allowed_card_networks": [
                            "AMEX",
                            "DISCOVER",
                            "INTERAC",
                            "JCB",
                            "MASTERCARD",
                            "VISA"
                        ]
                    },
                    "tokenization_specification": {
                        "type": "PAYMENT_GATEWAY",
                        "parameters": {
                            "gateway": "example",
                            "gateway_merchant_id": {publishable_key}
                        }
                    }
                }
            ],
            "merchant_info": {
                "merchant_name": "Narayan Bhat"
            }
        },
        "apple_pay": {
            "session_token_data": {
                "initiative": "web",
                "certificate": {certificate},
                "display_name": "applepay",
                "certificate_keys": {certificate_keys},
                "initiative_context": "hyperswitch-sdk-test.netlify.app",
                "merchant_identifier": "merchant.com.stripe.sang"
            },
            "payment_request_data": {
                "label": "applepay pvt.ltd",
                "supported_networks": [
                    "visa",
                    "masterCard",
                    "amex",
                    "discover"
                ],
                "merchant_capabilities": [
                    "supports3DS"
                ]
            }
        }
    }
}
'

Create Api-key

curl --location 'http://localhost:8080/api_keys/merchant_1719479670' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '{
  "name": "API Key 1",
  "description": null,
  "expiration": "2025-09-23T01:02:03.000Z"
}'

Now,

Updating the default business profile with payment link config with business_specific_configs

curl --location 'http://localhost:8080/account/merchant_1719479670/business_profile/pro_yl0tPMSCxxJlndl0aMHz' \
--header 'Content-Type: application/json' \
--header 'api-key: test_admin' \
--data '{
"payment_link_config": {
        "theme": "#8A0808",
        "logo": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg",
        "seller_name": "sahkal",
        "display_sdk_only": false,
        "business_specific_configs": {
            "key1": {
                "theme": "#3B845E",
                "logo": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg",
                "seller_name": "sahkal",
                "display_sdk_only": true
            },
            "key2": {
                "theme": "#B202FF",
                "logo": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg",
                "seller_name": "sahkal",
                "enabled_saved_payment_method":true
                
            }
        }
    }
}'

Do Payments Create

Case 1: incase, business_specific_configs is present and key is not send in payment create request, config will be picked up from the default config.

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_DZb1asJFT5ywRneS0niZUSrkXAiwGopwoWDKETATbIJqvPm2hj58q1Yrpbhqdb6D' \
--data-raw '
{
    "amount": 6540,
    "currency": "USD",
     "payment_link": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 6540,
    "customer_id": "stripesavecard_1234",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "setup_future_usage": "on_session",
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "127.0.0.1",
            "user_agent": "amet irure esse"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "routing": {
        "type": "single",
        "data": "stripe"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "session_expiry": 2423545
}'

Screenshot 2024-06-27 at 2 53 29 PM

Case 2:incase, business_specific_configs is present and key is send in the request, then it should pick up config from the config mapped to the key in business profile.

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_DZb1asJFT5ywRneS0niZUSrkXAiwGopwoWDKETATbIJqvPm2hj58q1Yrpbhqdb6D' \
--data-raw '
{
    "amount": 6540,
    "currency": "USD",
     "payment_link": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 6540,
    "customer_id": "stripesavecard_1234",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "setup_future_usage": "on_session",
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "127.0.0.1",
            "user_agent": "amet irure esse"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "routing": {
        "type": "single",
        "data": "stripe"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "session_expiry": 2423545,
    "payment_link_config_id":"key1"
}

Screenshot 2024-06-27 at 2 56 23 PM

Case 3 :incase, business_specific_configs is present and invalid key is send in the request, then it should pick up default config from business profile.

curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_DZb1asJFT5ywRneS0niZUSrkXAiwGopwoWDKETATbIJqvPm2hj58q1Yrpbhqdb6D' \
--data-raw '
{
    "amount": 6540,
    "currency": "USD",
     "payment_link": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 6540,
    "customer_id": "stripesavecard_1234",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://google.com",
    "setup_future_usage": "on_session",
    "customer_acceptance": {
        "acceptance_type": "offline",
        "accepted_at": "1963-05-03T04:07:52.723Z",
        "online": {
            "ip_address": "127.0.0.1",
            "user_agent": "amet irure esse"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "routing": {
        "type": "single",
        "data": "stripe"
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "session_expiry": 2423545,
    "payment_link_config_id":"key234"
}

Screenshot 2024-06-27 at 3 00 36 PM

Note: Next test cases, can be tested based on creating new business profile, and doing feature and edge case testing.

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible

@sahkal sahkal requested review from a team as code owners June 26, 2024 15:22
@sahkal sahkal changed the title feat(paymentlink): add multiple custom css support in business level for payment link feat(paymentlink): add multiple custom css support in business level Jun 26, 2024
@sahkal sahkal self-assigned this Jun 26, 2024
@sahkal sahkal added A-core Area: Core flows C-feature Category: Feature request or enhancement S-waiting-on-review Status: This PR has been implemented and needs to be reviewed labels Jun 26, 2024
@sahkal sahkal added this to the June 2024 Release milestone Jun 26, 2024
@hyperswitch-bot hyperswitch-bot bot added the M-api-contract-changes Metadata: This PR involves API contract changes label Jun 26, 2024
…siness-level-for-payment-link' of https://github.com/juspay/hyperswitch into 5582-payment-link-add-multiple-custom-css-support-in-business-level-for-payment-link
@sahkal sahkal requested a review from a team as a code owner June 27, 2024 07:44
@sahkal sahkal changed the title feat(paymentlink): add multiple custom css support in business level feat(payment_link): add multiple custom css support in business level Jun 27, 2024
.to_currency_base_unit(payment_intent.amount.get_amount_as_i64())
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;

let required_conversion_type = StringMajorUnitForConnector;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a different type for this like StringMajorUnit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i cant use pub struct StringMajorUnit since we are already using it for conversion type pub struct StringMajorUnit(String)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, added StringMajorUnitForCore type to impl on!

@sahkal sahkal requested a review from jarnura June 28, 2024 08:54
@@ -468,6 +468,9 @@ pub struct PaymentsRequest {
#[schema(value_type = Option<PaymentCreatePaymentLinkConfig>)]
pub payment_link_config: Option<PaymentCreatePaymentLinkConfig>,

/// custom payment link config id set at business profile send only if business_specific_configs is configured
pub payment_link_config_id: Option<String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be sent in Confirm and update as well? If not can you hide it in the api ref for those two requests?

@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Jul 1, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 1, 2024
@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Jul 1, 2024
Merged via the queue into main with commit ecc6c00 Jul 1, 2024
11 checks passed
@Gnanasundari24 Gnanasundari24 deleted the 5582-payment-link-add-multiple-custom-css-support-in-business-level-for-payment-link branch July 1, 2024 11:16
pixincreate added a commit that referenced this pull request Jul 2, 2024
…ror-handling-in-cypress

* 'main' of github.com:juspay/hyperswitch:
  fix(auth_methods): Add checks for duplicate `auth_method` in create API (#5161)
  chore(version): 2024.07.02.0
  fix(router): rename the browser name header to `x-browser-name` (#5162)
  fix(router): mark retry payment as failure if `connector_tokenization` fails (#5114)
  fix(connector): [Paypal] dispute webhook deserialization failure (#5111)
  feat(analytics): Add v2 payment analytics (payment-intents analytics) (#5150)
  feat(globalsearch): Implement tag-based filters in global search (#5151)
  refactor(connector): Add amount conversion framework to iatapay along with amount conversion code to connector template (#4866)
  feat(payment_link): add multiple custom css support in business level  (#5137)
  feat(connector): [Bambora Apac] Template for integration (#5062)
  feat(tls): add support for https in actix web (#5089)
  chore(ci): fix ci tests failing by removing them (#5167)
  chore(version): 2024.07.01.0
  chore(postman): update Postman collection files
  ci(postman): log request id for user tests (#5159)
  chore(euclid_wasm): make field domain optional wasm (#5154)
@SanchithHegde SanchithHegde removed the S-waiting-on-review Status: This PR has been implemented and needs to be reviewed label Jul 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Core flows C-feature Category: Feature request or enhancement M-api-contract-changes Metadata: This PR involves API contract changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants