# Update purchase

Endpoint: PUT /people/{personId}/loans/{loanId}/draws/{drawId}/purchases/{purchaseId}
Version: 2025-03-26
Security: oauth2, bearerAuth, apiKeyHeader

## Path parameters:

  - `personId` (string, required)
    A Borrower's unique identifier, tied to a person or a business. Can be Peach or a lender's external identifier.

  - `loanId` (string, required)
    The unique identifier of the Loan we wish to retrieve. Can be
Peach or lender's external identifier.

  - `drawId` (string, required)
    The draw identifier.

  - `purchaseId` (string, required)
    The purchase identifier.

## Query parameters:

  - `force` (boolean)
    Allows creating or updating purchases, even if:
- The purchase amount pushes the line or draw above the credit limit.
- The line or draw `isClosed=true`.

If `force=true` is passed, the `forceOverLimit` and `forceIfClosed` parameters are ignored.

Requires the `draw.purchase:force` permission.

  - `forceOverLimit` (boolean)
    Allows creating or updating purchases even if the purchase amount pushes the line or draw above the credit limit.
Requires the `draw.purchase:force` permission.
If `force=true` is passed, this parameter is ignored.

  - `forceIfClosed` (boolean)
    Allows creating or updating purchases even if the line or draw `isClosed=true`.
Requires the `draw.purchase:force` permission.
If `force=true` is passed, this parameter is ignored.

  - `sync` (boolean)
    Make the process synchronous.

## Request fields (application/json):

  - `amount` (number)
    The amount of the purchase.

  - `authorizationPurchaseId` (string)
    Peach's unique or lender's external identifier for the authorization purchase. This attribute is used to reference an initial authorization, so
lenders know which captures (purchase object(s) in Peach) are related to the original authorization.

  - `authorizedAt` (string)
    The timestamp when the purchase was `authorized`.

  - `cardId` (string)
    Peach id, Peach public id (`CD-...`), or lender external id (`ext-...`) of the card used for this purchase.
Resolved against cards belonging to this line of credit. If the card cannot be resolved (unknown id or wrong loan),
the purchase is still updated and `validation.cardIdMissing` is set to `true` in the response.

  - `declineReason` (object)
    The reason the purchase was declined

  - `declineReason.mainExternalReasonCode` (string)
    The external decline reason code

  - `declineReason.mainScriptForRep` (string)
    The main decline reason script that a customer service agent can provide to the borrower.

  - `declineReason.mainText` (string)
    The main decline reason text that is shown to the borrower

  - `effectiveAt` (string)
    The effective timestamp when the purchase was applied in the ledger. `effectiveAt` is equal to the timestamp when the purchase is set to
`pending` or `settled` status, whichever comes first. Changing the `effectiveAt` via the `Update purchase` endpoint will trigger a replay
of the line of credit. Keep in mind that an existing statement might've included the purchase based on the existing `effectiveAt` value
and the interest was accrued based on the existing `effectiveAt`. `effectiveAt` can be updated only if the purchase is in `settled` or `pending` status.
The timezone is UTC for this timestamp and the format is ISO.
For example, in order to set a purchase `effectiveAt` to July 28th 2022, 10:44 am PDT, send `2022-07-28T17:44:00Z`
    Example: 2022-06-10T11:45Z

  - `externalId` (string)
    The lender's identifier for the purchase.

  - `metadata` (object)

  - `originalPurchaseId` (string)
    Peach's unique or lender's external identifier. Typically used for purchases of type `refund` which allows the lender to
reference the original purchase being refunded. For authorizations, please see `authorizationPurchaseId` attribute.

  - `purchaseDate` (string)
    The date of a purchase. This attribute can be passed only during the line of credit migration process.
Don't use this attribute after the migration process was successfully completed.
- The value must be before or equal to today, and cannot be greater than the "migration at date" (aka the actual date on which you run the migration.)
- If you pass `purchaseDate` you cannot pass `authorizedAt` and/or `effectiveAt`.
- If `purchaseDate` is passed, the system will set `authorizedAt` and `effectiveAt` to `purchaseDate` at 6:00 AM in the product timezone.
- If you `purchaseDate` is not passed, you must pass both `authorizedAt` and `effectiveAt`. `authorizedAt` cannot be greater than `effectiveAt`.

For non-migrated purchases:
- The value is equal to the date part of the `authorizedAt` converted to the product timezone (e.g. Eastern or Pacific).
If `authorizedAt` is null, this value is equal to the date part of the purchase's `createdAt` converted to the product timezone.

The `purchaseDate` is returned in Get endpoints for both migrated and non-migrated purchases.

  - `purchaseDetails` (object)
    The details of purchase.

  - `purchaseDetails.categoryId` (string)
    The category identifier of the purchase.

  - `purchaseDetails.conversionRate` (number)
    The conversion rate that was used to convert the purchase amount from the `originalCurrencyCode` to the line of credit main currency.
For example:
- Line of credit main currency code = `USD`
- `originalCurrencyCode=GBP`
- `originalCurrencyAmount=200.45`
- `conversionRate=1.33`

Then `amount = 200.45 * 1.33 = 266.59`.

  - `purchaseDetails.description` (string)
    The description of the purchase. Purchases in authorized status are shown in borrower portal but NOT on statements.
Purchases in `pending` or `succeeded` status are shown in borrower portal and on statements.  At least one of the attributes,
`description` or `merchantName` must be passed

  - `purchaseDetails.externalCardId` (string)
    DEPRECATED. Use the top-level `cardId` field on Create/Update Purchase instead, which resolves to a
Peach `Card` row scoped to the line of credit. This legacy field is preserved for back-compat and continues
to populate the `external_card_id` column verbatim.
Precedence: if both top-level `cardId` and `purchaseDetails.externalCardId` are supplied in the same
request, top-level `cardId` wins and `externalCardId` is silently ignored (no error, no warning).

  - `purchaseDetails.merchantCategoryCode` (string)
    The merchant category code of the card acceptor (merchant).

  - `purchaseDetails.merchantCity` (string)
    The city of the merchant.

  - `purchaseDetails.merchantCountry` (string)
    The country of the merchant.

  - `purchaseDetails.merchantId` (string)
    The merchant identifier where the purchase was made.

  - `purchaseDetails.merchantName` (string)
    The merchant name where the purchase was made. At least one of the attributes, `description` or `merchantName` must be passed.

  - `purchaseDetails.merchantState` (string)
    The state of the merchant.

  - `purchaseDetails.metadata` (object)
    Store any type of key/value pairs in the form of a JSON dictionary.

  - `purchaseDetails.originalCurrencyAmount` (number)
    The amount of the purchase before currency conversion.

  - `purchaseDetails.originalCurrencyCode` (string)
    The currency in which the purchase was made before currency conversion. Three letters according to [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217).

  - `purchaseDetails.pointOfSaleType` (string)
    - `online` - the purchase was made online on a website or in a mobile application.
- `physical` - the purchase was made in a store with a physical card or instrument.
- `recurring` - the purchase was made as part of a subscription service.
    Enum: "online", "physical", "recurring"

  - `status` (string)
    The status of the purchase.
- `authorized` - the purchase is authorized and not yet `settled`. Authorized purchases are included in `utilizationAmount`, `openToBuy` and `availableCreditAmount` calculations; however, authorizations are not written to the ledger and are not included in total outstanding loan balances.
- `settled` - the purchase was settled.
- `pending` - the purchase is pending. `pending` purchases increases the draw balance similar to `settled`. Pending status is used mainly
for visuals and normally shown in Borrower Portal and on statements.
- `canceled` - the purchase was canceled. `canceled` purchases reduce the draw balance. A new purchase cannot be created in `canceled` status.
- `declined` - the purchase was declined. `declined` purchases reduce the draw balance if the status changes from `pending` to `declined`. A new purchase can be created in `declined` status for record purposes (it will not impact the draw balance).
- `disputed` - The purchase was disputed for full or partial amount. `disputed` status is a terminal state and once set cannot be changed to another status. This status can not be set manually. Submitting a purchase dispute will set the purchase's status to `disputed`.

The following status transitions are allowed:
- `authorized` -> `pending`
- `authorized` -> `settled`
- `authorized` -> `canceled`
- `authorized` -> `declined`
- `pending` -> `settled`
- `pending` -> `canceled`
- `pending` -> `declined`
- `declined` -> `authorized`
- `settled` -> `canceled`
- `settled` -> `disputed` (only by calling "Create purchase dispute")
    Enum: "authorized", "pending", "settled", "canceled", "declined", "disputed"

  - `refundDetails` (object)
    The details of how a refund was applied:
- The system will first deplete any non-due principal. This amount will be stored as `refundNonDuePrincipalAmount`.
- If any refund amount is left to apply, the system will create a transaction of type `serviceCredit` and
`serviceCreditType=refund`. This transaction will be applied on the Draw level first and any leftover amount will
be applied on the LOC level (based on the loan type waterfall configuration for payments). This amount will be stored as
`refundTransactionAmount`.
- Any unapplied refund amount will be recorded as credit to the borrower (also called `reimbursementAmount`.)

  - `refundDetails.refundNonDuePrincipalAmount` (number)
    Amount of refund that was applied to reduce the non-due principal of the draw.
The total amount of refund is equal to `refundNonDuePrinicpalAmount + refundTransactionAmount`.

  - `refundDetails.refundTransactionAmount` (number)
    Amount of refund that was applied to as a transaction to reduce other balances of the draw and potentially other draws.
The total amount of refund is equal to `refundNonDuePrinicpalAmount + refundTransactionAmount`.

  - `refundDetails.refundTransactionId` (string)
    Reference to the transaction ID of type service credit and serviceCreditType=refund. This is Peach Public ID.

## Response 200 fields (application/json):

  - `message` (string)
    Description of the server action or error.

  - `status` (integer)
    HTTP status code.

  - `data` (object)

  - `data.amount` (number, required)
    The amount of the purchase. The amount can be updated only if the purchase status is `pending` or `authorized`. The amount and status of a purchase may be changed on the same call.

  - `data.authorizationPurchaseId` (string)
    Peach's unique (or lender's external identifier) for the authorization purchase. This attribute is used to reference an initial authorization, so
lenders know which captures (purchase object(s) in Peach) are related to the original authorization.

  - `data.cardId` (string)
    Peach id, Peach public id (`CD-...`), or lender external id (`ext-...`) of the card used for this purchase.
Resolved against cards belonging to this line of credit. If the card cannot be resolved (unknown id or wrong loan),
the purchase is still created and `validation.cardIdMissing` is set to `true` in the response.
In responses from read endpoints, this field is always returned as the Peach public id (`CD-...`) or `null`.

  - `data.declineReason` (object)
    The reason the purchase was declined

  - `data.declineReason.mainExternalReasonCode` (string)
    The external decline reason code

  - `data.declineReason.mainScriptForRep` (string)
    The main decline reason script that a customer service agent can provide to the borrower.

  - `data.declineReason.mainText` (string)
    The main decline reason text that is shown to the borrower

  - `data.displayId` (string)
    The purchase ID to show in the UI (Peach public or external).

  - `data.drawId` (string)
    The unique identifier of the draw that the purchase belongs to.

  - `data.eligibleDisputeAmount` (number)
    The maximum amount eligible to be disputed. The amount is dynamic and can change depending on past disputes. Sum of disputed amounts cannot exceed the original purchase amounts (regardless of disputes outcomes).

  - `data.externalId` (string)
    A lender's identifier for a resource.
After the resource is successfully created, a lender can use the Peach assigned ID OR
the `externalId` identifiers to fetch the resource.
### Fetching with `externalId`:
To fetch a resource using an `externalId`, you MUST use the prefix `ext-` in the URL.
For example, a loan with an external identifier of `ABCDE` would be referenced like `/api/people/BO-FAKE-IDNT/loans/ext-ABCDE`.
### Creating with `externalId`:
To create a resource with an external identifer, you MUST NOT use the prefix `ext-`.
For example, if the external identifier should be `ABCDE`, then pass
`{ externalId: "ABCDE", ... }` in the request body.

  - `data.id` (string)
    The unique identifier of the purchase.

  - `data.lineOfCreditId` (string)
    The unique identifier of the line of credit that the purchase belongs to.

  - `data.metadata` (object)
    Store any type of key/value pairs in the form of a JSON dictionary.

  - `data.migration` (object)
    Purchase migration related details. This object should be used for lines that are migrated from another system to Peach.

  - `data.migration.originalDrawId` (string, required)
    The draw identifier. Can be Peach or lender's external identifier.
All past purchases prior to the "migration cutoff date" will be posted on the "migration" draw. After the "migration cutoff date",
a lender can have multiple draws (e.g. Purchases, Cash Withdrawals, etc.) Use this attribute to indicate the original draw ID
that the purchase was posted on in another system. In case of a dispute created after the "migration cutoff date", the `originalDrawId`
will be used to adjust the draw balance. The `originalDrawId` is required for all purchases that are migrated from another system.

  - `data.originalAmount` (number)
    The original amount of the purchase when it was created. If the purchase amount changes after creation, `originalAmount` will not change.

  - `data.purchaseDate` (string)
    The date of a purchase. This attribute can be passed only during the line of credit migration process.
Don't use this attribute after the migration process was successfully completed.
- The value must be before or equal to today, and cannot be greater than the "migration at date" (aka the actual date on which you run the migration.)
- If you pass `purchaseDate` you cannot pass `authorizedAt` and/or `effectiveAt`.
- If `purchaseDate` is passed, the system will set `authorizedAt` and `effectiveAt` to `purchaseDate` at 6:00 AM in the product timezone.
- If you `purchaseDate` is not passed, you must pass both `authorizedAt` and `effectiveAt`. `authorizedAt` cannot be greater than `effectiveAt`.

For non-migrated purchases:
- The value is equal to the date part of the `authorizedAt` converted to the product timezone (e.g. Eastern or Pacific).
If `authorizedAt` is null, this value is equal to the date part of the purchase's `createdAt` converted to the product timezone.

The `purchaseDate` is returned in Get endpoints for both migrated and non-migrated purchases.

  - `data.purchaseDetails` (object)
    The details of purchase.

  - `data.purchaseDetails.categoryId` (string)
    The category identifier of the purchase.

  - `data.purchaseDetails.conversionRate` (number)
    The conversion rate that was used to convert the purchase amount from the `originalCurrencyCode` to the line of credit main currency.
For example:
- Line of credit main currency code = `USD`
- `originalCurrencyCode=GBP`
- `originalCurrencyAmount=200.45`
- `conversionRate=1.33`

Then `amount = 200.45 * 1.33 = 266.59`.

  - `data.purchaseDetails.description` (string)
    The description of the purchase. Purchases in authorized status are shown in borrower portal but NOT on statements.
Purchases in `pending` or `succeeded` status are shown in borrower portal and on statements.  At least one of the attributes,
`description` or `merchantName` must be passed

  - `data.purchaseDetails.externalCardId` (string)
    DEPRECATED. Use the top-level `cardId` field on Create/Update Purchase instead, which resolves to a
Peach `Card` row scoped to the line of credit. This legacy field is preserved for back-compat and continues
to populate the `external_card_id` column verbatim.
Precedence: if both top-level `cardId` and `purchaseDetails.externalCardId` are supplied in the same
request, top-level `cardId` wins and `externalCardId` is silently ignored (no error, no warning).

  - `data.purchaseDetails.isValidMerchantId` (boolean)
    `true` - if the merchant ID exists in Peach Merchants table.
`false` - if the merchant ID doesn't exist in Peach Merchants table.
Credit card lenders normally should not create merchants details in Peach and `isMerchantIdValid` attribute can be ignored.
Point of sale lenders normally have merchants details in Peach and can use `isMerchantIdValid` attribute as needed.

  - `data.purchaseDetails.merchantCategoryCode` (string)
    The merchant category code of the card acceptor (merchant).

  - `data.purchaseDetails.merchantCity` (string)
    The city of the merchant.

  - `data.purchaseDetails.merchantCountry` (string)
    The country of the merchant.

  - `data.purchaseDetails.merchantId` (string)
    The merchant identifier where the purchase was made.

  - `data.purchaseDetails.merchantName` (string)
    The merchant name where the purchase was made. At least one of the attributes, `description` or `merchantName` must be passed.

  - `data.purchaseDetails.merchantState` (string)
    The state of the merchant.

  - `data.purchaseDetails.metadata` (object)
    Store any type of key/value pairs in the form of a JSON dictionary.

  - `data.purchaseDetails.originalCurrencyAmount` (number)
    The amount of the purchase before currency conversion.

  - `data.purchaseDetails.originalCurrencyCode` (string)
    The currency in which the purchase was made before currency conversion. Three letters according to [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217).

  - `data.purchaseDetails.pointOfSaleType` (string)
    - `online` - the purchase was made online on a website or in a mobile application.
- `physical` - the purchase was made in a store with a physical card or instrument.
- `recurring` - the purchase was made as part of a subscription service.
    Enum: "online", "physical", "recurring"

  - `data.status` (string)
    The status of the purchase.
- `authorized` - the purchase is authorized and not yet `settled`. Authorized purchases are included in `utilizationAmount`, `openToBuy` and `availableCreditAmount` calculations; however, authorizations are not written to the ledger and are not included in total outstanding loan balances.
- `settled` - the purchase was settled.
- `pending` - the purchase is pending. `pending` purchases increases the draw balance similar to `settled`. Pending status is used mainly
for visuals and normally shown in Borrower Portal and on statements.
- `canceled` - the purchase was canceled. `canceled` purchases reduce the draw balance. A new purchase cannot be created in `canceled` status.
- `declined` - the purchase was declined. `declined` purchases reduce the draw balance if the status changes from `pending` to `declined`. A new purchase can be created in `declined` status for record purposes (it will not impact the draw balance).
- `disputed` - The purchase was disputed for full or partial amount. `disputed` status is a terminal state and once set cannot be changed to another status. This status can not be set manually. Submitting a purchase dispute will set the purchase's status to `disputed`.

The following status transitions are allowed:
- `authorized` -> `pending`
- `authorized` -> `settled`
- `authorized` -> `canceled`
- `authorized` -> `declined`
- `pending` -> `settled`
- `pending` -> `canceled`
- `pending` -> `declined`
- `declined` -> `authorized`
- `settled` -> `canceled`
- `settled` -> `disputed` (only by calling "Create purchase dispute")
    Enum: "authorized", "pending", "settled", "canceled", "declined", "disputed"

  - `data.timestamps` (object)

  - `data.timestamps.authorizedAt` (string)
    The timestamp when the purchase was `authorized`. This value can be set by lenders at the time of purchase creation.
If the value is not set by the lender, it will be populated based on `authorizedAt` (if it is non-null) otherwise `createdAt` for the corresponding `authorizationPurchaseId`.
If the value is not set by the lender and no `authorizationPurchaseId` is set, this value corresponds to `purchase.authorized` event.
If the purchase was never in `authorized` status and none of the prior conditions are met, this value will be null.

  - `data.timestamps.createdAt` (string)

  - `data.timestamps.effectiveAt` (string)
    The effective timestamp when the purchase was applied in the ledger. `effectiveAt` is equal to the timestamp when the purchase is set to
`pending` or `settled` status, whichever comes first. Changing the `effectiveAt` via the `Update purchase` endpoint will trigger a replay
of the line of credit. Keep in mind that an existing statement might've included the purchase based on the existing `effectiveAt` value
and the interest was accrued based on the existing `effectiveAt`. `effectiveAt` can be updated only if the purchase is in `settled` or `pending` status.

  - `data.timestamps.settledAt` (string)
    The timestamp when the purchase was `settled`. Corresponds to `purchase.settled` event.

  - `data.timestamps.updatedAt` (string)

  - `data.type` (string)
    - `regular` - a regular purchase made by the borrower. Increases draw's balance.
- `refund` - a refund of the purchase made in the past. Decreases draw's balance.
- `cashBack` - cash back given to the borrower by the issuing institution.
    Enum: "regular", "refund", "cashBack"

  - `data.refundDetails` (object)
    The details of how a refund was applied:
- The system will first deplete any non-due principal. This amount will be stored as `refundNonDuePrincipalAmount`.
- If any refund amount is left to apply, the system will create a transaction of type `serviceCredit` and
`serviceCreditType=refund`. This transaction will be applied on the Draw level first and any leftover amount will
be applied on the LOC level (based on the loan type waterfall configuration for payments). This amount will be stored as
`refundTransactionAmount`.
- Any unapplied refund amount will be recorded as credit to the borrower (also called `reimbursementAmount`.)

  - `data.refundDetails.refundNonDuePrincipalAmount` (number)
    Amount of refund that was applied to reduce the non-due principal of the draw.
The total amount of refund is equal to `refundNonDuePrinicpalAmount + refundTransactionAmount`.

  - `data.refundDetails.refundTransactionAmount` (number)
    Amount of refund that was applied to as a transaction to reduce other balances of the draw and potentially other draws.
The total amount of refund is equal to `refundNonDuePrinicpalAmount + refundTransactionAmount`.

  - `data.refundDetails.refundTransactionId` (string)
    Reference to the transaction ID of type service credit and serviceCreditType=refund. This is Peach Public ID.

  - `validation` (object)
    Soft validation warnings for fields the system accepted without enforcing.

  - `validation.cardIdMissing` (boolean)
    True when `cardId` was provided but could not be resolved to a card on this loan.

## Response 423 fields (application/json):

  - `message` (string)
    Enum: "Loan is locked for updates. Please try again.", "Loan is locked for updates. Please check back again in a few minutes.", "Previous purchase is processing."

