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

# Add Saved Payment

> **Pre add customer payer payment details in Payment Gateway**

Allow merchants save their customer payment details and using their system generated Unique Id to identify each of customers.

> - Merchants shall pass their customers Unique Id to **"customer.username"** parameter
> - Merchants may get customers saved payment details by using [Check Saved Payment](/api-reference/check-saved-payment)
> - Channel Id may refer to the [Get Channel List](/api-reference/get-tenant-channels-by-country)

<Note>
  **This call places a real MYR 1.00 verification transaction.** CommercePay charges the customer's payment method MYR 1.00 through the provider to verify the card/wallet is valid, then automatically refunds it once the save completes. Expect the normal payment callback/webhook activity for this verification transaction — no separate refund call is needed on your side.
</Note>

<Warning>
  **One saved payment per customer per channel.** If `customer.username` already has a saved payment method on the given `channelId`, this call fails with `UserIsSavedPayment` (2103) instead of replacing it. To save a new payment method for that customer/channel, first revoke the existing one via [Revoke Saved Payment](/api-reference/revoke-saved-payment).
</Warning>

## Errors

In addition to standard signature errors, this endpoint can return:

| Code | Name                             | Meaning                                                           |
| ---- | -------------------------------- | ----------------------------------------------------------------- |
| 2001 | `InvalidChannel`                 | `channelId` does not match an existing channel.                   |
| 2102 | `UsernameIsRequired`             | `customer.username` was not supplied.                             |
| 2103 | `UserIsSavedPayment`             | This customer already has a saved payment method on this channel. |
| 2104 | `ChannelNotSupportPreAddPayment` | The channel's provider does not support saving payment methods.   |


## OpenAPI

````yaml /openapi.json post /api/services/app/PaymentGateway/AddSavePayment
openapi: 3.0.1
info:
  title: CommercePay API
  version: v1
  description: >
    ---

    **Credentials Accounts**


    Every account is provided with separate keys for testing and for running
    live transactions. Once the merchant application has been approved,
    developers can begin the process of integrating the CommercePay API. The
    following details will be provided to get started with the integration. 


    <a href="https://docs.commercepay.asia/docs/api/d19b0f3289ae0-get-started"
    target="_blank">Get Started</a>
  contact: {}
servers:
  - description: Staging
    url: https://staging-payments.commerce.asia
  - url: https://payments.commerce.asia
    description: Production
security: []
paths:
  /api/services/app/PaymentGateway/AddSavePayment:
    post:
      tags:
        - PaymentGateway
      summary: Add Saved Payment
      description: "**Pre add customer payer payment details in Payment Gateway**\r\n\r\nAllow merchants save their customer payment details and using their system generated Unique Id to identify each of customers.\r\n\r\n> - Merchants shall pass their customers Unique Id to **\"customer.username\"** parameter\r\n> - Merchants may get customers saved payment details by using [Check Saved Payment](/api-reference/check-saved-payment)\r\n> - Channel Id may refer to the [Get Channel List](/api-reference/get-tenant-channels-by-country)"
      operationId: ApiServicesAppPaymentgatewayAddsavepaymentPost
      parameters:
        - $ref: '#/components/parameters/AbpTenantId'
        - $ref: '#/components/parameters/Authorization'
        - $ref: '#/components/parameters/CapSignature'
      requestBody:
        content:
          application/json-patch+json:
            schema:
              $ref: '#/components/schemas/AddSavePaymentInputDto'
          application/json:
            schema:
              $ref: '#/components/schemas/AddSavePaymentInputDto'
          text/json:
            schema:
              $ref: '#/components/schemas/AddSavePaymentInputDto'
          application/*+json:
            schema:
              $ref: '#/components/schemas/AddSavePaymentInputDto'
        description: ''
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AddSavePaymentOutputDto'
              examples:
                Example 1:
                  value:
                    transactionNumber: string
                    redirectUrl: string
            text/json:
              schema:
                $ref: '#/components/schemas/AddSavePaymentOutputDto'
            text/plain:
              schema:
                $ref: '#/components/schemas/AddSavePaymentOutputDto'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorOutputDto'
              examples:
                UserIsSavedPayment:
                  value:
                    code: 2103
                    message: >-
                      This customer already has a saved payment method on this
                      channel.
components:
  parameters:
    AbpTenantId:
      schema:
        type: string
      in: header
      name: Abp-TenantId
      required: true
      description: Your unique Merchant ID assigned by CommercePay.
    Authorization:
      schema:
        type: string
      in: header
      name: Authorization
      required: true
      description: Your API credentials used for authentication (typically a Bearer token).
    CapSignature:
      schema:
        type: string
      in: header
      name: cap-signature
      required: true
      description: >-
        A unique security hash used to verify the integrity of the request body.
        See [Generate Signature](/docs/generate-signature) for how to generate
        this.
  schemas:
    AddSavePaymentInputDto:
      type: object
      additionalProperties: false
      required:
        - channelId
        - referenceCode
        - ipAddress
        - returnUrl
      properties:
        currencyCode:
          type: string
          description: Currency code with a 3-letter ISO 4217 standard code.
          example: MYR
          nullable: true
        channelId:
          type: integer
          format: int32
          description: >-
            An identifier that represents a CommercePay payments channel. Refer
            id on response end points [Get Channel
            List](/api-reference/get-tenant-channels-by-country). Must be a
            channel whose provider supports saved payments, otherwise the
            request fails with ChannelNotSupportPreAddPayment (2104).
        referenceCode:
          type: string
          description: Merchant Reference Code
          maxLength: 50
        ipAddress:
          type: string
          description: Customer’s IP address captured by the merchant system
          maxLength: 50
        userAgent:
          type: string
          description: >-
            **[Conditional]** Customer’s user agent. It is recommended to input
            for a better user experience for specific channels due to different
            platforms such as desktop, mobile, or app view. Example channels
            impact: ShopeePay
          maxLength: 200
          nullable: true
        returnUrl:
          type: string
          description: >-
            Return URL supplied by the merchant server for the payment response
            and used by CommercePay to redirect the customer's browser back to
            the desired page on the Merchant’s site
          example: https://returnurl.com/
          maxLength: 500
        callbackUrl:
          type: string
          description: >-
            Callback URL is host-to-host communication. CommercePay sends the
            transaction response to the merchant callback server based on the
            callback URL given, including for the automatic MYR 1.00
            verification charge and its refund. If the URL is provided, the
            callback will be triggered; otherwise, it would not trigger. Refer
            to the Merchant Callback URL to know more
          maxLength: 500
          example: https://callback.com/
          nullable: true
        customer:
          $ref: '#/components/schemas/UserInfoDto'
        tenantId:
          type: integer
          format: int32
          description: Fill in provided Tenant Id.
          nullable: true
        timestamp:
          type: integer
          format: int64
          description: new Date().getTime()
          example: 1621851652617
    AddSavePaymentOutputDto:
      type: object
      additionalProperties: false
      properties:
        transactionNumber:
          type: string
          description: >-
            Unique CAP Transaction Number for the underlying MYR 1.00
            verification transaction (automatically refunded).
          maxLength: 24
          nullable: true
        redirectUrl:
          type: string
          description: URL used for the customer to redirect to the payment page
          nullable: true
    ErrorOutputDto:
      type: object
      additionalProperties: false
      properties:
        code:
          type: integer
          description: See the Errors table above.
        message:
          type: string
          nullable: true
    UserInfoDto:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          description: Customer’s Email Address
          example: email@gmail.com
          maxLength: 150
          nullable: true
        mobileNo:
          type: string
          description: Customer’s Mobile Number
          example: '0123456789'
          maxLength: 16
          nullable: true
        name:
          type: string
          description: Customer’s Name
          example: name
          maxLength: 36
          nullable: true
        username:
          type: string
          description: Customer’s unique ID for recurring payments or next saved payments
          example: username
          maxLength: 36
          nullable: true
        identificationNumber:
          type: string
          description: '**[Conditional for FPX Recurring]**'
          nullable: true
        identificationType:
          $ref: '#/components/schemas/IdentificationType'
    IdentificationType:
      enum:
        - 1
        - 2
        - 3
        - 4
        - 5
      type: integer
      x-enumNames:
        - NewIdentityNumber
        - OldIdentityNumber
        - PassportNumber
        - BusinessRegistration
        - Others
      x-examples: {}
      description: >-
        **[Conditional for FPX Recurring]**


        New Identity Number = 1, Old Identity Number = 2, Passport Number = 3,
        Business Registration = 4, Others = 5

````