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

# MoneyGram

> MoneyGram provider for PayKit.

```bash theme={null}
npm install @paykit-sdk/core @paykit-sdk/moneygram
```

MoneyGram is a money-transfer (remittance) API, not a traditional
checkout/subscription processor. There's no customer-object API and no
recurring payments — `createPayment` and `createCheckout` both run
MoneyGram's full **Quote → Update → Commit** transfer flow as a single call
(MoneyGram has no separate hosted checkout page, so "checkout" here just
means the same transfer, mapped onto PayKit's `Checkout` shape).

## Setup

<Tabs>
  <Tab title="Environment variables">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { moneygram } from '@paykit-sdk/moneygram';

    export const paykit = new PayKit(moneygram());
    ```

    Required env vars:

    ```bash theme={null}
    MONEYGRAM_CLIENT_ID=...
    MONEYGRAM_CLIENT_SECRET=...
    MONEYGRAM_AGENT_PARTNER_ID=30150519
    MONEYGRAM_OPERATOR_ID=paykit-web
    MONEYGRAM_SANDBOX=true
    ```
  </Tab>

  <Tab title="Direct config">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { createMoneygram } from '@paykit-sdk/moneygram';

    export const paykit = new PayKit(
      createMoneygram({
        clientId: '...',
        clientSecret: '...',
        agentPartnerId: '30150519',
        operatorId: 'paykit-web',
        isSandbox: true,
      }),
    );
    ```
  </Tab>
</Tabs>

## How it works

```typescript theme={null}
const payment = await paykit.payments.create({
  customer: { email: 'sender@example.com' },
  amount: 100,
  currency: 'USD',
  item_id: 'transfer-to-jane',
  capture_method: 'automatic', // MoneyGram commits immediately - manual capture isn't supported
  provider_metadata: {
    destinationCountryCode: 'PHL', // ISO alpha-3
    serviceOptionCode: 'WILL_CALL', // cash pickup; omit to let MoneyGram pick a default
    sender: {
      name: { firstName: 'John', lastName: 'Doe' },
      address: {
        line1: '123 Main St',
        city: 'Dallas',
        countryCode: 'USA',
      },
      mobilePhone: { number: '5551234567', countryDialCode: '1' },
      personalDetails: { dateOfBirth: '1990-01-01' },
      primaryIdentification: {
        typeCode: 'PPT',
        id: 'X1234567',
        issueCountryCode: 'USA',
      },
    },
    receiver: {
      name: { firstName: 'Jane', lastName: 'Smith' },
    },
  },
});
```

Under the hood this makes 3 calls to MoneyGram's Transfer API:

1. `POST /transfer/v1/transactions/quote` — reserves a `transactionId` and locks fees/FX for 30 minutes
2. `PUT /transfer/v1/transactions/{id}` — attaches the sender/receiver/compliance data from `provider_metadata`
3. `PUT /transfer/v1/transactions/{id}/commit` — actually moves the money

`sender` and `receiver` are required on every call — MoneyGram has no
customer-object API, so full KYC data must be supplied per-transfer. It
doesn't support customer management or subscriptions.

## Checkouts

Same flow, mapped onto `Checkout` instead of `Payment` — useful if your
code already calls `paykit.checkouts`. Since `Checkout` has no top-level
`amount`/`currency`, both go in `provider_metadata` too:

```typescript theme={null}
const checkout = await paykit.checkouts.create({
  customer: { email: 'sender@example.com' },
  item_id: 'transfer-to-jane',
  quantity: 1,
  session_type: 'one_time', // MoneyGram transfers are never recurring
  success_url: 'https://example.com/success', // unused - no redirect step
  cancel_url: 'https://example.com/cancel', // unused - no redirect step
  provider_metadata: {
    amount: 100,
    currency: 'USD',
    destinationCountryCode: 'PHL',
    serviceOptionCode: 'WILL_CALL',
    sender: {
      /* same shape as createPayment above */
    },
    receiver: {
      /* same shape as createPayment above */
    },
  },
});
```

`checkout.payment_url` is a receipt link (valid 5 minutes), not a "go pay
here" redirect — the transfer is already committed by the time
`createCheckout` returns. `retrieveCheckout`/`updateCheckout` behave the
same as `retrievePayment`/`updatePayment` below, since MoneyGram has one
transaction resource, not separate payment/checkout resources.

## Webhooks

MoneyGram doesn't use a shared secret for webhooks, and publishes exactly
one fixed public key per environment (sandbox/production) with no
per-partner issuance — so `webhookSecret` is unused. Pass `null`:

```typescript theme={null}
const webhook = paykit.webhooks
  .setup({ webhookSecret: null }) // unused - always verified against MoneyGram's published sandbox/production key
  .on('payment.created', async event => {
    /* UNFUNDED - customer must fund at a MoneyGram store */
  })
  .on('payment.succeeded', async event => {
    /* SENT - committed, funded, and accepted */
  })
  .on('payment.updated', async event => {
    /* AVAILABLE / IN_TRANSIT / RECEIVED / DELIVERED / PROCESSING / CLOSED */
  })
  .on('payment.failed', async event => {
    /* REJECTED */
  })
  .on('refund.created', async event => {
    /* REFUNDED */
  });

await webhook.handle({
  body: await request.text(),
  headersAsObject: Object.fromEntries(request.headers),
  fullUrl: request.url,
});
```

MoneyGram signs webhooks with an RSA-SHA256 `Signature` header
(`t=timestamp, s=signature`), verified against the public key above.

Since MoneyGram's `TRANSACTION_STATUS_EVENT` payload only carries status
fields (no amount, sender, or receiver data), this provider re-fetches the
full transaction from the Status API before emitting any PayKit event —
never trust the webhook body directly.

MoneyGram transaction statuses and their PayKit event mappings:

| MoneyGram status | PayKit event        |
| ---------------- | ------------------- |
| `UNFUNDED`       | `payment.created`   |
| `SENT`           | `payment.succeeded` |
| `AVAILABLE`      | `payment.updated`   |
| `IN_TRANSIT`     | `payment.updated`   |
| `RECEIVED`       | `payment.updated`   |
| `DELIVERED`      | `payment.updated`   |
| `PROCESSING`     | `payment.updated`   |
| `REJECTED`       | `payment.failed`    |
| `REFUNDED`       | `refund.created`    |
| `CLOSED`         | `payment.updated`   |

## Refunds

```typescript theme={null}
await paykit.refunds.create({
  payment_id: 'txn_1',
  amount: 100,
  reason: 'requested_by_customer',
  metadata: null,
  provider_metadata: {
    refundReasonCode: 'CUSTOMER_REQUEST', // required - MoneyGram's enumerated refund reason
  },
});
```

This retrieves the transaction for refund eligibility (`GET
/refund/v2/transactions/{id}`), then commits the refund (`PUT
/refund/v2/transactions/{id}/commit`).

## Amending a receiver's name

MoneyGram's only supported post-commit edit is correcting the receiver's
name (e.g. to match their ID for payout), via `updatePayment` (or
`updateCheckout` — same behavior, mapped onto `Checkout`):

```typescript theme={null}
await paykit.payments.update('txn_1', {
  metadata: {},
  provider_metadata: {
    receiverFirstName: 'Jane',
    receiverLastName: 'Smith Corrected',
  },
});
```

Without `receiverFirstName`/`receiverLastName`, `updatePayment` just
re-fetches the current transaction.

## Unsupported operations

`createCustomer`, `createSubscription`, `capturePayment`,
`cancelPayment`, and `deleteCheckout`/`deletePayment` throw
`ProviderNotSupportedError` — MoneyGram has no customer storage, manual
capture, recurring transfers, or a way to delete/void a committed
transfer. Use `createPayment` / `createCheckout` / `createRefund`
directly instead.
