> ## Documentation Index
> Fetch the complete documentation index at: https://docs.singapay.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Update v2

> Updates an existing payment link. True partial update — any field omitted from the request body is left untouched (unlike v1, which recomputes several fields to their "not sent" defaults and silently clears them). Rejected outright with 422 if the link is already expired or already closed (status=closed), before any field is processed. See `UpdatePaymentLinkV2Request` for per-field behavior.



## OpenAPI

````yaml https://payment-b2b.singapay.id/api/docs/merchant-api.json put /api/v2.0/payment-link/update/{link_id}
openapi: 3.1.0
info:
  title: Singa Merchant API
  description: >-
    OpenAPI specification for the merchant/partner HTTP API. All routes below
    are additionally protected by `ip.whitelisted.merchant` — the caller IP must
    be registered for the credential or merchant. Obtain a JWT using `POST
    /api/v1.0/access-token/b2b` (Basic auth) or `POST
    /api/v1.1/access-token/b2b` (X-Signature) before calling secured endpoints.
  version: 1.0.0
servers:
  - url: https://sandbox-payment-b2b.singapay.id
    description: >-
      API host. Paths include `/api` prefix (see `RouteServiceProvider`).
      Replace scheme/host with your environment.
security: []
tags:
  - name: Security
    description: >-
      Merchant authentication (`OauthMerchantTokenController`). **v1.1** B2B
      token uses `X-CLIENT-ID`, `X-PARTNER-ID`, and `X-Signature` (no Basic
      auth). Secured routes also require the issued Bearer JWT plus
      `X-PARTNER-ID`.
  - name: Accounts
    description: >-
      Account management (`routes/merchantApiRoute.php`, `v1.0`). Path parameter
      `{id}` is always the account ULID.
  - name: Balance Inquiry
    description: >-
      Merchant and per-account balance inquiry (`BalanceController`, `v1.0`).
      Path `account_id` is the account ULID.
  - name: Statements
    description: >-
      Per-account statement list and detail
      (`AccountController::accountStatements`, `accountStatementDetail`, prefix
      `v1.0/statements`). Flugger responses; detail path param `{statement_id}`
      maps to `statements.transaction_id`.
  - name: Payment Link
    description: >-
      Payment link CRUD and payment-method catalog (`PaymentLinkApiController`,
      prefix `v1.0/payment-link-manage`). `account_id` is ULID;
      `payment_link_id` is numeric `payment_links.id`.
  - name: Payment Link History
    description: >-
      Payment link transaction/history listing and detail
      (`PaymentLinkApiController`, prefix `v1.0/payment-link-histories`).
      `history_id` is numeric `payment_link_histories.id`.
  - name: Virtual Account
    description: >-
      Native VA CRUD (`VirtualAccountController`, prefix
      `v1.0/virtual-accounts`). `account_id` and `virtual_account_id` are ULIDs.
  - name: VA Transaction
    description: >-
      VA money-in transaction listing and detail (`VirtualAccountController`,
      prefix `v1.0/va-transactions`).
  - name: QRIS (Money In)
    description: >-
      MPM dynamic QRIS list, show, and generate (`QrisMpmDynamicApiController`,
      prefix `v1.0/qris-dynamic`).
  - name: QRIS (Money Out)
    description: >-
      Issuer MPM decode/inquiry, payment credit (money out), and transaction
      status (`QrisIssuerMpmController`, `QrisApiV2Controller::checkStatus`,
      prefix `v2.0/qris`). Related list/detail: `GET
      /api/v2.0/qris/transaction/...`.
  - name: E-Wallet (Money In)
    description: >-
      E-Wallet Native checkout and transactions (`EwalletNativeApiController`,
      `EwalletNativeTransactionApiController`, `EwalletNativeV2ApiController`).
      Paths include `v1.0/ewallet-native`, `v1.0/ewallet-native-transactions`,
      and `v2.0/ewallet-native`.
  - name: E-Wallet (Money Out)
    description: >-
      E-wallet disbursement / top-up to beneficiary wallets
      (`EWalletTopUpController`, prefix `v2.0/ewallet`). Responses use the
      custom **`MerchantV2ApiEnvelope`** with **`MerchantV2ResponseCode`**
      (SP000–SP020).
  - name: Card (Money In)
    description: >-
      One-time card payment, cancel, and inquiry
      (`CardPaymentMerchantApiController`, prefix `v2.0/card`).
  - name: Subscription (Recurring)
    description: >-
      Credit-card recurring subscription plans (`SubscriptionPlanApiController`,
      prefix `v2.0/recurring`). Plan `{id}` is UUID (`sub_plans.id`).
  - name: Direct Debit
    description: >-
      Direct Debit — bind a customer bank account once via hosted webview, then
      charge it host-to-host (`DirectDebitMerchantController`, prefix
      `v2.0/direct-debit`). Binding `{binding_id}` and transaction
      `{transaction_id}` are UUIDs. Responses use the
      **`MerchantV2ApiEnvelope`** with standard Singapay SP000–SP020 dictionary
      codes and messages. Field-level validation detail is returned in
      `data.errors` for **SP018**. The `/charge` endpoint is additionally
      protected by **`X-Signature`** + **`X-Timestamp`**
      (`verify.signature-internal`); see operation parameters for the signing
      scheme. Asynchronous outcomes (bind / unbind / charge) are HTTP POST
      callbacks to `callback_urls.direct_debit_notif_url` — see the Direct Debit
      **webhooks** (`directDebitBindingNotification`,
      `directDebitUnbindingNotification`, `directDebitTransactionNotification`).
      All three share that single URL; discriminate on `event`.
  - name: Account Transfer
    description: >-
      Move funds between sub-accounts within the same merchant
      (`AnotherAccountApiController`, prefix `v1.0/account-transfer`). The
      transfer endpoint requires request signature headers
      (`verify.signature-internal`). Both accounts must belong to the
      authenticated merchant and be accessible to the credential.
  - name: Disbursement (Money Out)
    description: >-
      Bank disbursement (`DisbursementController` **v1.0**,
      `DisbursementV2Controller` **v2.0**). **v1.0**
      list/show/fee/beneficiary/transfer use Flugg envelopes; v1 inquiry-status
      uses the **custom v2 envelope** (`MerchantV2ApiEnvelope`, codes
      **SP000–SP020**). **v2.0** check-beneficiary, transfer, and inquiry-status
      use the same custom envelope — see component schema
      **`MerchantV2ResponseCode`** for the full response code table.
  - name: Cardless Withdrawal
    description: >-
      Cardless withdrawal API for initiating ATM cash withdrawals without a
      physical card. Supports creating withdrawals with OTP generation, listing
      transaction history, viewing transaction details, canceling pending
      withdrawals, and deleting canceled records. All endpoints use the
      `v1.0/cardless-withdrawals` route prefix and the standard success/error
      response envelope. Path parameter `{id}` refers to the `transaction_id`
      (platform-assigned business identifier).
  - name: Retail Store
    description: >-
      Native Retail Store money-in — generate an Alfamart / Indomaret payment
      code the customer pays at the cashier, then read its status
      (`RetailTransactionController`, prefix `v1.0/retail-transactions`). Only
      two endpoints: create and show. `account_id` is the account ULID;
      `transaction_id` is the platform-assigned
      `retail_transactions.transaction_id` string returned by create. Payment
      confirmation arrives as a POST callback to
      `callback_urls.transaction_notif_url` with `event = retail-transaction`.
  - name: Settlement Transaction
    description: >-
      Read-only listing of the transactions inside a settlement
      (`SettlementMerchantApiController`, `GET
      v1.0/settlement-transactions/{identifier}`). A sub-menu of the merchant
      Transaction Record APIs. Merchant-scoped — only settlements with status
      `completed` are exposed. `{identifier}` is the numeric `settlements.id` or
      the `settlements.reference_no`. Each item mirrors the money-in transaction
      shape (`account`, `fees`, `amount`, `post_timestamp`,
      `processed_timestamp`, `has_settle`, `settle_at`). Merchants cannot
      create, approve, or refund settlements; those actions are BOSS-only.
paths:
  /api/v2.0/payment-link/update/{link_id}:
    put:
      tags:
        - Payment Link
      summary: Update v2
      description: >-
        Updates an existing payment link. True partial update — any field
        omitted from the request body is left untouched (unlike v1, which
        recomputes several fields to their "not sent" defaults and silently
        clears them). Rejected outright with 422 if the link is already expired
        or already closed (status=closed), before any field is processed. See
        `UpdatePaymentLinkV2Request` for per-field behavior.
      operationId: paymentLinkV2Update
      parameters:
        - name: link_id
          in: path
          required: true
          schema:
            description: Payment link identifier (`payment_links.id`).
            type: integer
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePaymentLinkV2Request'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponderSuccessPaymentLinkV2'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponderErrorEnvelope401'
        '403':
          description: Access denied to this account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponderErrorEnvelope403'
        '404':
          description: Payment link not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponderErrorEnvelope404'
        '405':
          description: Method not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponderErrorEnvelope405'
        '422':
          description: >-
            Validation error (including the payment link already being expired
            or already closed — either rejected outright regardless of which
            fields are sent — `max_usage` less than current usage, `max_usage`
            sent on a link with target customer details and/or a
            notification/reminder configuration already set, invalid payment
            method codes, no payment methods eligible for the link's amount when
            re-auto-selecting, or `expired_at` sent on a link that already has a
            reminder notification configured)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponderErrorEnvelope422'
      security:
        - BearerAuth: []
          PartnerId: []
components:
  schemas:
    UpdatePaymentLinkV2Request:
      description: >-
        True partial update — unlike v1's update (which recomputes several
        fields to their "not sent" defaults, silently clearing them when
        omitted), every field here is optional and any field left out of the
        request body is left completely untouched. `reff_no`, `title`,
        `payment_link_type`, `total_amount`, `items`, and
        `customer_name`/`customer_email`/`customer_phone` are immutable after
        creation and not accepted here.
      properties:
        description:
          description: >-
            Updates the payment link description. Send an empty string/`null` to
            clear it.
          type:
            - string
            - 'null'
          maxLength: 255
          example: Updated description
        max_usage:
          description: >-
            Updates the usage cap. `0` means unlimited. Cannot be set below the
            current usage count (422). Also re-derives
            `is_multiple_payment`/`is_unlimited_usage`/`required_customer_detail`.
            Rejected with 422 if this payment link has
            `customer_name`/`customer_email`/`customer_phone` and/or a
            notification/reminder configuration set — those only work for
            single-use links, so changing max_usage could silently move the link
            to multi-use and break them. Omit to leave unchanged.
          type:
            - integer
            - 'null'
          maximum: 999
          minimum: 0
          example: 5
        expired_at:
          description: >-
            Updates the expiration timestamp (must be in the future). Rejected
            with 422 if this payment link already has a reminder notification
            configured (i.e. `reminder_at` is set from
            `reminder_unit`/`reminder_period` sent at creation) — reminder_at is
            only computed once and would otherwise desync from the new expiry.
            Omit to leave unchanged.
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-08-10T00:00:00+07:00'
        status:
          description: Updates the payment link lifecycle status. Omit to leave unchanged.
          type:
            - string
            - 'null'
          enum:
            - open
            - closed
            - expired
          example: closed
        whitelisted_payment_method:
          description: >-
            Updates the allowed payment method codes. Send `null`/an empty array
            to re-auto-select eligible methods based on the link's existing
            `total_amount`. Omit the field entirely to leave the current
            whitelist untouched.
          type:
            - array
            - 'null'
          items:
            type: string
        success_redirect_url:
          description: Updates the success redirect URL. Omit to leave unchanged.
          type:
            - string
            - 'null'
          format: uri
          maxLength: 2048
          example: https://merchant.example.com/payment/success
        expired_redirect_url:
          description: Updates the expired redirect URL. Omit to leave unchanged.
          type:
            - string
            - 'null'
          format: uri
          maxLength: 2048
          example: https://merchant.example.com/payment/expired
        optional_metadata:
          description: Updates the merchant-defined metadata. Omit to leave unchanged.
          type:
            - object
            - 'null'
          additionalProperties: true
        required_customer_number:
          description: >-
            Only applied when the link is (or is being made) multi-use. Omit to
            leave unchanged.
          type:
            - boolean
            - 'null'
          example: false
        required_customer_email:
          description: >-
            Only applied when the link is (or is being made) multi-use. Omit to
            leave unchanged.
          type:
            - boolean
            - 'null'
          example: true
      type: object
    ResponderSuccessPaymentLinkV2:
      required:
        - status
        - success
      properties:
        status:
          description: HTTP status code.
          type: integer
          example: 200
        success:
          description: Indicates whether the request was successful.
          type: boolean
          example: true
        data:
          $ref: '#/components/schemas/PaymentLinkV2Resource'
          description: Payment link resource payload.
      type: object
    ResponderErrorEnvelope401:
      required:
        - status
        - success
        - error
      properties:
        status:
          type: integer
          example: 401
        success:
          type: boolean
          example: false
        error:
          $ref: '#/components/schemas/ResponderErrorBody401'
      type: object
    ResponderErrorEnvelope403:
      required:
        - status
        - success
        - error
      properties:
        status:
          type: integer
          example: 403
        success:
          type: boolean
          example: false
        error:
          $ref: '#/components/schemas/ResponderErrorBody403'
      type: object
    ResponderErrorEnvelope404:
      required:
        - status
        - success
        - error
      properties:
        status:
          type: integer
          example: 404
        success:
          type: boolean
          example: false
        error:
          $ref: '#/components/schemas/ResponderErrorBody404'
      type: object
    ResponderErrorEnvelope405:
      required:
        - status
        - success
        - error
      properties:
        status:
          type: integer
          example: 405
        success:
          type: boolean
          example: false
        error:
          $ref: '#/components/schemas/ResponderErrorBody405'
      type: object
    ResponderErrorEnvelope422:
      required:
        - status
        - success
        - error
      properties:
        status:
          type: integer
          example: 422
        success:
          type: boolean
          example: false
        error:
          $ref: '#/components/schemas/ResponderErrorBody422'
        data:
          type: object
          example:
            errors:
              field:
                - error message
          additionalProperties: true
      type: object
    PaymentLinkV2Resource:
      description: >-
        Payment link resource returned by the v2 create/get/update endpoints.
        Includes nested account details when available. Distinct from
        `PaymentLinkResource` (v1) — v2 exposes `payment_link_type`,
        `redirect_url_enabled`, `target_customer_details_enabled`,
        `customer_notification_preference`, `reminder_unit`, and
        `reminder_period` instead.
      properties:
        id:
          description: >-
            Payment link identifier. Use this value as `{link_id}` in path
            parameters.
          type: integer
          example: 103
        reff_no:
          description: Merchant reference number assigned to the payment link.
          type: string
          example: INV20240513001
        description:
          description: Free-text description for the payment link.
          type:
            - string
            - 'null'
          example: 'Invoice #INV20240513001'
        source:
          description: >-
            How the payment link was created: `api v1`, `api v2`, or
            `dashboard`.
          type:
            - string
            - 'null'
          example: api v2
        payment_url:
          description: Hosted payment page URL for this payment link.
          type: string
          example: https://example.com/payment-link
        status:
          description: Configured payment link status.
          type: string
          example: open
        is_expired:
          description: Whether the payment link is currently expired.
          type: boolean
          example: false
        payment_link_type:
          description: >-
            Derived from whether line items were stored for this link — not an
            accepted input field. `items` when the link has line items, `total`
            otherwise.
          type: string
          enum:
            - total
            - items
          example: total
        is_multiple_payment:
          description: >-
            Whether this link accepts more than one successful payment
            (`max_usage` > 1 or unlimited).
          type: boolean
          example: false
        is_unlimited_usage:
          description: Whether this link has no usage cap (`max_usage` = 0).
          type: boolean
          example: false
        max_usage:
          description: >-
            Maximum number of successful payments permitted. `0` means
            unlimited.
          type: integer
          example: 1
        current_usage:
          description: Current number of successful payments recorded for the link.
          type: integer
          example: 0
        expired_at:
          description: Expiration datetime in ISO 8601 format.
          type:
            - string
            - 'null'
          example: '2024-05-13T00:00:00Z'
        required_customer_detail:
          description: >-
            Whether the payer must fill in a customer-detail form at checkout
            (used for multi-use links, where the payer is not known in advance).
          type: boolean
          example: true
        required_customer_fields:
          description: >-
            Which customer-detail fields are required from the payer at checkout
            when `required_customer_detail` is true.
          properties:
            name:
              type: boolean
              example: true
            phone:
              type: boolean
              example: false
            email:
              type: boolean
              example: false
          type: object
        total_amount:
          description: Total payment amount for the payment link.
          type: number
          example: 150000
        items:
          description: >-
            Line items stored for the payment link. `null` when the link was
            created with `payment_link_type: "total"` or without item details.
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/PaymentLinkLineItemStored'
        whitelisted_payment_method:
          description: >-
            List of allowed payment method codes for this link. When the
            create/update request omits this field, it is auto-populated with
            whichever payment methods are actually eligible for `total_amount`
            at that time — never left empty.
          type: array
          items:
            type: string
        redirect_url_enabled:
          description: >-
            Derived from whether either redirect URL below is set — not an
            accepted input field.
          type: boolean
          example: false
        success_redirect_url:
          description: Redirect URL for successful payment.
          type:
            - string
            - 'null'
          example: https://merchant.example.com/payment/success
        expired_redirect_url:
          description: Redirect URL for expired payment.
          type:
            - string
            - 'null'
          example: https://merchant.example.com/payment/expired
        optional_metadata:
          description: Merchant-defined metadata attached to the payment link.
          type:
            - object
            - 'null'
          additionalProperties: true
        target_customer_details_enabled:
          description: >-
            Whether the merchant pre-supplied any known-payer contact info
            (`customer_name`, `customer_email`, or `customer_phone`) for this
            single-use link. Not the same as `required_customer_detail`.
          type: boolean
          example: true
        customer_name:
          description: >-
            Pre-filled/known customer name. Only set on single-use (`max_usage`
            = 1) links where the merchant knows the payer in advance.
          type:
            - string
            - 'null'
          example: Jane Doe
        customer_email:
          description: Pre-filled/known customer email. Only set on single-use links.
          type:
            - string
            - 'null'
          example: jane@example.com
        customer_phone:
          description: >-
            Pre-filled/known customer phone number. Only set on single-use
            links.
          type:
            - string
            - 'null'
          example: '081234567890'
        customer_notification_preference:
          description: >-
            Which channels notify the customer at each lifecycle event. WhatsApp
            incurs a fixed notification fee (charged to the merchant's balance);
            email is always free.
          properties:
            payment_link_created:
              type: array
              items:
                type: string
                enum:
                  - whatsapp
                  - email
              example:
                - whatsapp
                - email
            payment_link_reminder:
              type: array
              items:
                type: string
                enum:
                  - whatsapp
                  - email
              example:
                - whatsapp
          type:
            - object
            - 'null'
        reminder_unit:
          description: >-
            Unit for `reminder_period`. Only set when a reminder channel is
            configured.
          type:
            - string
            - 'null'
          enum:
            - day
            - hour
            - null
          example: day
        reminder_period:
          description: >-
            How long before `expired_at` the reminder notification fires, in
            `reminder_unit` units.
          type:
            - integer
            - 'null'
          example: 1
        created_at:
          description: Resource creation datetime in ISO 8601 format.
          type:
            - string
            - 'null'
          example: '2024-05-13T00:00:00Z'
        updated_at:
          description: Last update datetime in ISO 8601 format.
          type:
            - string
            - 'null'
          example: '2024-05-13T00:00:00Z'
        account:
          oneOf:
            - $ref: '#/components/schemas/PaymentLinkV2AccountResource'
              description: >-
                Related account resource when available. Slimmer than v1's full
                Account resource — only `id` and `name`.
            - type: 'null'
          description: >-
            Related account resource when available. Slimmer than v1's full
            Account resource — only `id` and `name`.
          example:
            id: 1
            name: Account 1
      type: object
    ResponderErrorBody401:
      properties:
        code:
          type: integer
          example: 401
        message:
          type: string
          example: Unauthorized merchant, please sign in
      type: object
    ResponderErrorBody403:
      properties:
        code:
          type: integer
          example: 403
        message:
          type: string
          example: Access denied.
      type: object
    ResponderErrorBody404:
      properties:
        code:
          type: integer
          example: 404
        message:
          type: string
          example: Account not found.
      type: object
    ResponderErrorBody405:
      properties:
        code:
          type: integer
          example: 4059901
        message:
          type: string
          example: >-
            The PATCH method is not supported for this route. Supported methods:
            GET, HEAD, POST.
      type: object
    ResponderErrorBody422:
      properties:
        code:
          type: integer
          example: 422
        message:
          type: string
          example: Validation error.
        errors:
          type: object
          example:
            field:
              - error message
      type: object
    PaymentLinkLineItemStored:
      properties:
        name:
          description: Line item name stored on the payment link.
          type: string
          example: Product A
        quantity:
          description: Stored quantity for the line item.
          type: integer
          example: 1
        unit_price:
          description: Stored unit price for the line item.
          type: number
          example: 150000
        subtotal:
          description: Calculated line item subtotal (`quantity x unit_price`).
          type: number
          example: 150000
      type: object
    PaymentLinkV2AccountResource:
      description: >-
        Slim account resource nested under `PaymentLinkV2Resource.account` —
        only `id` and `name`. Distinct from v1's full `Account` schema, which
        also exposes account_number, status, account_type, kyb_status,
        legal_name, brand_name, kyb_onboarding_url, and invite_members.
      properties:
        id:
          description: Account identifier in ULID format.
          type: string
          example: 01M1BFRSG3BY6AQ1PE8PB7RCB5
        name:
          description: Account display name.
          type: string
          example: Jeremy Main Account
      type: object
  securitySchemes:
    BearerAuth:
      type: http
      description: >-
        JWT issued by `POST /api/v1.1/access-token/b2b`. Send `Authorization:
        Bearer <token>`.
      bearerFormat: JWT
      scheme: bearer
    PartnerId:
      type: apiKey
      description: Merchant API key (`Credential.api_key`). Required on every request.
      name: X-PARTNER-ID
      in: header

````