Processing a payment

Payment processing begins when Shopify makes an HTTP call to your payments app. Your app responds with a redirect URL that Shopify uses to redirect the customer to your app's payment page. After the payment is finalized, you need to communicate the result to Shopify using either PaymentSessionResolve or PaymentSessionReject.

How the payments app flow works

The following diagram illustrates how a payment flow works between the payments app and Shopify:

payment processing steps

  1. The customer completes checkout, triggering a request for payment.
  2. Shopify sends a backend request to the payments app, specifying the amount and currency to be charged.
  3. The app responds with a redirect URL for a payment page hosted by the partner's app. (200 OK)
  4. Shopify redirects the customer to the redirect URL.
  5. The payments app collects the buyer’s payment credentials and processes the payment as described in the backend request.
  6. The payments app communicates the payment processing result to Shopify, implementing a retry policy as needed.
  7. Shopify replies with the redirect URL. (200 OK)
  8. The app redirects the customer to Shopify. (301 Redirect)
  9. Customer continues checkout.

For payment processing, both upstream and downstream requests must be idempotent with the payment ID used as the idempotency key.

Asynchronous communications

Payment processing relies on asynchronous communications over HTTP between Shopify and the payments app. In an asynchronous system, retry policies are crucial to ensure a robust communication strategy.

Requirements

Scopes

To use the GraphQL mutations, your app needs to be aware of access scopes for payments apps.

Initiate the payment flow

The payment flow begins with an HTTP request sent from Shopify to the provider's payment session URL provided during app extension configuration. This request contains information about the customer and the order.

Request Body

{
  "id": "8BLFxjEHP5PkA1kNsb6iRKX9",
  "gid": "gid://shopify/PaymentSession/8BLFxjEHP5PkA1kNsb6iRKX9",
  "group": "W_CUXwaUd69aOjMMlWOui7eK",
  "amount": "123.00",
  "currency": "CAD",
  "test": false,
  "merchant_locale": "en",
  "payment_method": {
    "type": "offsite",
    "data": {
      "cancel_url": "https://my-test-shop.com/1/checkouts/4c94d6f5b93f726a82dadfe45cdde432"
    }
  },
  "proposed_at": "2020-07-13T00:00:00Z",
  "customer": {
    "billing_address": {
      "given_name": "Alice",
      "family_name": "Smith",
      "line1": "123 Street",
      "line2": "Suite B",
      "city": "Montreal",
      "postal_code": "H2Z 0B3",
      "province": "Quebec",
      "country_code": "CAN",
      "company": ""
    },
    "shipping_address": {
      "given_name": "Alice",
      "family_name": "Smith",
      "line1": "123 Street",
      "line2": "Suite B",
      "city": "Montreal",
      "postal_code": "H2Z 0B3",
      "province": "Quebec",
      "country_code": "CAN",
      "company": ""
    },
    "email": "buyer@example.com",
    "phone_number": "5555555555",
    "locale": "fr",
    "kind": "sale"
  }
}

Request headers

Shopify-Shop-Domain: my-test-shop.myshopify.com
Shopify-Request-Id: 94169f7e-ac8d-4ef4-9fd2-90f0791daddf

Request attributes

Attribute Description Type
idRequired Unique identifier for the payment attempt. Used as the idempotency key. It can be assumed that requests with a given ID are identical to any previously-received requests with the same ID. string
gidRequired Identifies the payment when communicating with Shopify (in GraphQL mutations, for example). string
groupRequired Reference to the order of the payment. Shopify can make multiple payment attempts for the same id and group, redirecting to your app each time. However, the partner app can only call the PaymentSessionResolve once per id and group. string
cancel_urlRequired URL to send customers back to their checkout on Shopify. string
proposed_atRequired Can be used to order payment attempts that are a part of the same group. String (ISO-8601)
testRequired Indicates whether the payment is in test or live mode. Refer to Test mode for more information. Boolean
customer If customer is included, then one of customer.email or customer.phone must be present, but not both. The shipping_address and billing_address have line2, province, postal_code and company fields that are optional, depending on the customer's locality. Hash
kind If kind is included, then its value is either sale or authorization depending on the merchant configuration and whether you support payment authorization. If kind is sale, then you're expected to capture the funds instantly with this request. If kind is authorization, then you're expected to place a hold on the funds, but not capture them until later when a capture request is sent. string

Response

Shopify must receive a HTTP 2xx response for the payment session creation to be successful. If the request fails, then it will be retried a number of times and will eventually transition to an "aborted" state, at which point the buyer will have to retry their payment through Shopify checkout. The response must contain the URL for the partner page where Shopify will redirect the buyer.

Example payload:

{
  "redirect_url": "https://buyer-payment-page.com/12345"
}

Resolve a Payment

You must invoke the PaymentSessionResolve mutation after the buyer has successfully gone through the process of paying:

mutation PaymentSessionResolve($id: ID!) {
  paymentSessionResolve(id: $id) {
    paymentSession {
      id
      status {
        code
      }
      nextAction {
        action
        context {
          ... on PaymentSessionActionsRedirect {
            redirectUrl
          }
        }
      }
    }
    userErrors {
      field
      message
    }
  }
}

The id argument corresponds to the gid of the payment.

Variables:

{
  "id": "gid://shopify/PaymentSession/Z7yoyBgKrfTzPEAHhcaOJywI"
}

View response

JSON response:

The redirectUrl field is the URL to which the buyer is to be redirected by the partner.

{
  "data": {
    "paymentSessionResolve": {
      "paymentSession": {
        "id": "gid://shopify/PaymentSession/Z7yoyBgKrfTzPEAHhcaOJywI",
        "status": {
          "code": "RESOLVED"
        },
        "nextAction": {
          "action": "REDIRECT",
          "context": {
            "redirectUrl": "https://store-domain.myshopify.com/1552482326/checkouts/c4b4262b18860b1a451e3706122a524f/processing"
          }
        }
      },
      "userErrors": []
    }
  }
}

Reject a Payment

A payment should be rejected when the buyer is unable to complete payment with the provider. This signals to Shopify that the checkout process is to be halted.

You can reject a payment using the PaymentSessionReject mutation:

mutation PaymentSessionReject($id: ID!, $reason: PaymentSessionRejectionReasonInput!) {
    paymentSessionReject(id: $id, reason: $reason) {
        paymentSession {
            id
            status
            nextAction {
                action
                context {
                    ... on PaymentSessionActionsRedirect {
                        redirectUrl
                    }
                }
            }
        }
        userErrors{
            field
            message
        }
    }
}

Variables:

{
  "id": "gid://shopify/PaymentSession/4DxXBdw1z3W47wOk8f2-A2ZP",
  "reason": {
    "code": "PROCESSING_ERROR",
    "merchantMessage": "the payment didn't work"
  }
}

View response

JSON response:

{
  "data": {
    "paymentSessionReject": {
      "paymentSession": {
        "id": "gid://shopify/PaymentSession/4DxXBdw1z3W47wOk8f2-A2ZP",
        "status": {
          "code": "REJECTED"
        }
      },
      "userErrors": []
    }
  }
}

The rejection requires information on why the payment was rejected. This information is encapsulated in the PaymentSessionRejectionReasonInput.

The PaymentSessionRejectionReasonInput.code is a PaymentSessionStatusReasonRejectionCode, which is an enum of of standardized error codes.

The PaymentSessionRejectionReasonInput.merchantMessage argument is a localized error message presented to the merchant explaining why the payment was rejected.

Retry policy

If there is a Shopify service disruption (or if 500s are being returned), then you must implement a retry policy.

After the final request fails, you need to display an error to tell the customer that there was a problem processing the order.

Next action

Upon receiving the response from either the PaymentSessionResolve or PaymentSessionReject mutations, the next action that must be performed by the payments app is specified under nextAction.

The nextAction will either be nil or contain two fields. In the case where it is nil, no next action is expected of the payments app.

Otherwise, the fields are as follows:

  • action: An enum that specifies the type of the action the app must perform.
  • context: Union type requiring inline fragments to access data on the underlying type. Takes a type of PaymentSessionActionsRedirect

Next steps

Additional information