diff --git a/apps/edr-payment-api/src/migrations/1782000000000-AmountToDouble.ts b/apps/edr-payment-api/src/migrations/1782000000000-AmountToDouble.ts new file mode 100644 index 000000000..39a5cf3e3 --- /dev/null +++ b/apps/edr-payment-api/src/migrations/1782000000000-AmountToDouble.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +/** + * Amounts now carry the real/major price (callers pass e.g. 10.50, not minor units), which may be + * fractional. Widen amount_minor / confirmed_amount_minor from integer to double precision so the + * decimals are stored instead of silently rounded. + */ +export class AmountToDouble1782000000000 implements MigrationInterface { + name = "AmountToDouble1782000000000"; + + public async up(queryRunner: QueryRunner): Promise { + // integer -> double precision is an implicit widening cast (no USING needed). + await queryRunner.query( + `ALTER TABLE "edr_payment"."payment_intent" ALTER COLUMN "amount_minor" TYPE double precision`, + ); + await queryRunner.query( + `ALTER TABLE "edr_payment"."payment_intent" ALTER COLUMN "confirmed_amount_minor" TYPE double precision`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "edr_payment"."payment_intent" ALTER COLUMN "confirmed_amount_minor" TYPE integer USING round("confirmed_amount_minor")`, + ); + await queryRunner.query( + `ALTER TABLE "edr_payment"."payment_intent" ALTER COLUMN "amount_minor" TYPE integer USING round("amount_minor")`, + ); + } +} diff --git a/apps/edr-payment-api/src/modules/intents/dto/initiate-payment.dto.ts b/apps/edr-payment-api/src/modules/intents/dto/initiate-payment.dto.ts index 13a2f71a4..a9c8edfea 100644 --- a/apps/edr-payment-api/src/modules/intents/dto/initiate-payment.dto.ts +++ b/apps/edr-payment-api/src/modules/intents/dto/initiate-payment.dto.ts @@ -1,7 +1,7 @@ import { IsEnum, IsIn, - IsInt, + IsNumber, IsOptional, IsPositive, IsString, @@ -46,9 +46,9 @@ export class InitiatePaymentRequestDto implements InitiatePaymentRequest { @ApiProperty({ description: - "Authoritative amount in minor units, computed server-side by the app", + "Authoritative amount computed server-side by the app. Accepts decimals " }) - @IsInt() + @IsNumber() @IsPositive() amountMinor!: number; diff --git a/apps/edr-payment-api/src/modules/intents/entities/payment-intent.entity.ts b/apps/edr-payment-api/src/modules/intents/entities/payment-intent.entity.ts index 68c519604..84e69a57a 100644 --- a/apps/edr-payment-api/src/modules/intents/entities/payment-intent.entity.ts +++ b/apps/edr-payment-api/src/modules/intents/entities/payment-intent.entity.ts @@ -71,12 +71,16 @@ export class PaymentIntent extends BaseEntity { }) providerTxnId?: string | null; - /** App-asserted authoritative amount in minor units. */ - @Column({ name: "amount_minor", type: "integer" }) + /** App-asserted authoritative amount (real/major price; may be fractional, e.g. 10.50). */ + @Column({ name: "amount_minor", type: "double precision" }) amountMinor!: number; /** Provider-reported amount; reconciled against amount_minor (e.g. Waafi truncates decimals). */ - @Column({ name: "confirmed_amount_minor", type: "integer", nullable: true }) + @Column({ + name: "confirmed_amount_minor", + type: "double precision", + nullable: true, + }) confirmedAmountMinor?: number | null; @Column({ name: "currency", type: "varchar", length: 8 })