Issue Summary
When an event type is configured to accept PayPal payments in Japanese yen (JPY), the amount sent to PayPal is incorrectly divided by 100, causing PayPal to collect only 1/100th of the price configured by the organizer and agreed to by the attendee.
For example, if an organizer configures an event price of ¥10,000, the booking page correctly displays ¥10,000 and the Payment row stores 10000, but PayPal is asked to collect only ¥100.
The issue originates in the PayPal order-creation path under packages/app-store/paypal, where every payment amount is divided by 100 before being sent to PayPal, regardless of currency. JPY is a zero-decimal currency and its prices are correctly stored unscaled, so applying this conversion corrupts the amount sent to PayPal.
The issue has two failure modes:
- For a price such as ¥10,000, PayPal collects ¥100 even though the booking and Payment row indicate ¥10,000. The booking is subsequently confirmed as paid.
- For a JPY price that is not divisible by 100, such as ¥1,050, dividing the stored amount by 100 produces a decimal amount. PayPal rejects decimal amounts for JPY, the order-creation error is swallowed, and the payment record can be created without a usable external payment/order ID.
Nothing in the capture/authorization path compares the captured amount against the expected payment amount, so in the first failure mode neither the organizer nor attendee receives any indication that the wrong amount was collected.
This is inconsistent with the existing currency handling elsewhere in the application. Both the price input and price display use the zero-decimal-aware conversion logic in packages/lib/currencyConversions.ts, but the PayPal order-creation path does not use the same conversion logic.
PayPal's own currency documentation also explicitly identifies JPY as a currency that does not support decimals. The PayPal currency codes reference labels Japanese yen as "Japanese yen 1", where footnote 1 states that the currency does not support decimals and passing a decimal amount results in an error.
https://developer.paypal.com/api/rest/reference/currency-codes/
The bug affects both PayPal payment modes:
- On-booking / capture
- Hold / authorize
Both paths create the PayPal order through the same affected order-creation logic.
Steps to Reproduce
- Install the PayPal app and connect valid PayPal credentials.
- Open an event type and go to Apps → PayPal.
- Enable PayPal payments.
- Set the currency to Japanese yen (JPY).
- Set the event price to ¥10,000.
- Save the configuration.
- Open the public booking page and verify that the price is correctly displayed as ¥10,000.
- Book the event and continue to PayPal checkout.
- Observe the amount PayPal asks the attendee to approve.
Actual Results
- The booking page correctly displays ¥10,000, but PayPal checkout requests only ¥100.
- The amount sent to PayPal is therefore 1/100th of the configured price.
- After payment, the Payment row still stores
amount: 10000.
- The booking is marked as paid even though PayPal collected only ¥100.
- No error or warning is raised to indicate that the captured amount differs from the expected amount.
- For a JPY price that is not divisible by 100, such as ¥1,050, the PayPal order creation produces a decimal amount. PayPal rejects the request because JPY does not support decimals.
- The order-creation error is swallowed, leaving the payment record without a usable external ID.
Expected Results
- PayPal should be asked to collect exactly the amount configured and displayed to the attendee.
- A configured price of ¥10,000 should result in PayPal receiving
"10000", not "100".
- Zero-decimal currencies such as JPY should not be divided by 100.
- Currencies that use minor units should retain their existing behaviour. For example, a $50 USD price is stored as
5000 and should continue to be sent to PayPal as "50".
- The amount expected by the payment record, booking page, and external payment provider should remain consistent.
- The capture/authorization amount comparison is intentionally out of scope for this fix. The current PR is focused on correcting the currency conversion used during PayPal order creation; adding capture-time amount validation would be a separate change.
Technical details
- Node.js:
v22.22.3
- Browser-dependent: No. The incorrect amount is calculated server-side during PayPal order creation.
- Affected area:
packages/app-store/paypal
- Related currency handling:
packages/lib/currencyConversions.ts
- Storage and display are already zero-decimal aware. The price input and
formatPrice correctly handle JPY without scaling.
- The PayPal order-creation path uses a different conversion and incorrectly assumes every stored amount is in minor units.
- Both capture (on-booking) and authorize (hold) payment options are affected because both create the PayPal order through the same path.
- Among PayPal's supported currencies, JPY is the only one affected, because it is the only currency that is both zero-decimal in
currencyConversions.ts and supported by PayPal. The order-creation path incorrectly assumes every stored amount is in minor units.
- HUF and TWD also reject decimal amounts at PayPal, but they are stored using the application's scaled representation and are therefore correctly converted before being sent to PayPal.
Evidence
The issue was verified through a deterministic trace using the repository's existing currency conversion helpers:
| Organizer configures |
Stored price |
Booker is shown |
Sent to PayPal |
| 10000 JPY |
10000 |
¥10,000 |
"100" ❌ |
| 1000 JPY |
1000 |
¥1,000 |
"10" ❌ |
| 50 USD |
5000 |
$50.00 |
"50" ✅ |
This demonstrates that the stored/displayed amount and the amount sent to PayPal diverge specifically for JPY.
The behaviour is also reproducible in a unit test against the PayPal client by asserting the outgoing order request body:
- JPY: the current request contains
value: "10" where value: "1000" is expected.
- USD: unaffected.
- EUR: unaffected.
- HUF: unaffected.
- TWD: unaffected.
A regression test covering this behaviour is ready and will be included with the fix.
Issue Summary
When an event type is configured to accept PayPal payments in Japanese yen (JPY), the amount sent to PayPal is incorrectly divided by 100, causing PayPal to collect only 1/100th of the price configured by the organizer and agreed to by the attendee.
For example, if an organizer configures an event price of ¥10,000, the booking page correctly displays ¥10,000 and the Payment row stores
10000, but PayPal is asked to collect only ¥100.The issue originates in the PayPal order-creation path under
packages/app-store/paypal, where every payment amount is divided by 100 before being sent to PayPal, regardless of currency. JPY is a zero-decimal currency and its prices are correctly stored unscaled, so applying this conversion corrupts the amount sent to PayPal.The issue has two failure modes:
Nothing in the capture/authorization path compares the captured amount against the expected payment amount, so in the first failure mode neither the organizer nor attendee receives any indication that the wrong amount was collected.
This is inconsistent with the existing currency handling elsewhere in the application. Both the price input and price display use the zero-decimal-aware conversion logic in
packages/lib/currencyConversions.ts, but the PayPal order-creation path does not use the same conversion logic.PayPal's own currency documentation also explicitly identifies JPY as a currency that does not support decimals. The PayPal currency codes reference labels Japanese yen as
"Japanese yen 1", where footnote 1 states that the currency does not support decimals and passing a decimal amount results in an error.https://developer.paypal.com/api/rest/reference/currency-codes/
The bug affects both PayPal payment modes:
Both paths create the PayPal order through the same affected order-creation logic.
Steps to Reproduce
Actual Results
amount: 10000.Expected Results
"10000", not"100".5000and should continue to be sent to PayPal as"50".Technical details
v22.22.3packages/app-store/paypalpackages/lib/currencyConversions.tsformatPricecorrectly handle JPY without scaling.currencyConversions.tsand supported by PayPal. The order-creation path incorrectly assumes every stored amount is in minor units.Evidence
The issue was verified through a deterministic trace using the repository's existing currency conversion helpers:
10000"100"❌1000"10"❌5000"50"✅This demonstrates that the stored/displayed amount and the amount sent to PayPal diverge specifically for JPY.
The behaviour is also reproducible in a unit test against the PayPal client by asserting the outgoing order request body:
value: "10"wherevalue: "1000"is expected.A regression test covering this behaviour is ready and will be included with the fix.