Skip to content
This repository was archived by the owner on Oct 17, 2023. It is now read-only.

Releases: billforward/bf-php

Curl Error Exposition

31 Aug 18:31

Choose a tag to compare

In the event of a failed request: attempt using using curl_error() to dig up a bit more information.

Subscription Revive

28 Jul 11:37

Choose a tag to compare

The SDK has had Subscription revive support for a while, but the feature was scoped to just uncancelling in-period subscription cancellations.

As of yesterday's Sandbox release, Subscription revive supports resurrection of Failed and out-of-period subscriptions. The response object is slightly different than before, so the PHP SDK has been updated to support this.

We now unserialize in a typed manner the executionInfo field upon Subscription. It is usually absent, but cases like reviving an out-of-period subscription will result in the production of Invoices. This field gives some information about the newly-generated invoices, and whether execution was performed upon them.

$sub = Bf_Subscription::shamWithID('SUB-1F565848-DB59-4CF0-861F-34BC474E');
var_export($sub->revive());

// outputs:
array (
  0 => 
  Bf_Subscription::__set_state(array(
     '@type' => 'subscription',
     'created' => '2016-07-28T11:29:07Z',
     'changedBy' => 'CF0CBF36-922B-403F-AA95-22BAD9960BF3',
     'updated' => '2016-07-28T11:29:07Z',
     'metadata' => 
    Bf_MetadataJson::__set_state(array(
    )),
     'id' => 'SUB-1F565848-DB59-4CF0-861F-34BC474E',
     'versionID' => 'SUB-943D2114-FA61-4185-9771-93DBB968',
     'accountID' => 'ACC-71F4DCB6-EBC5-4498-9FC1-3FA7E19C',
     'organizationID' => 'ORG-BE199668-332C-4EFD-B05D-D705142C',
     'productID' => 'PRO-8FCF4545-B5F8-4DCF-AF35-02B4ABED',
     'productRatePlanID' => 'PRP-E1B4973A-23DC-45A9-B196-345F61D5',
     'name' => 'Test subscription',
     'description' => 'Test plan',
     'type' => 'Subscription',
     'state' => 'AwaitingPayment',
     'currentPeriodStart' => '2016-07-28T11:29:07Z',
     'currentPeriodEnd' => '2016-08-28T11:29:07Z',
     'contractStart' => '2016-04-27T16:56:13Z',
     'initialPeriodStart' => '2016-07-28T11:29:07Z',
     'successfulPeriods' => 0,
     'totalPeriods' => 1,
     'dunning' => false,
     'managedBy' => 'BillForward',
     'versionStart' => '2016-07-28T11:29:07Z',
     'versionNumber' => 2,
     'creditEnabled' => true,
     'aggregateAllSubscriptionsOnAccount' => false,
     'failedPaymentBehaviour' => 'None',
     'pricingComponentValues' => 
    array (
      0 => 
      Bf_PricingComponentValue::__set_state(array(
         'pricingComponentID' => 'PCO-EADB4E2D-E286-427C-A3ED-9F628F94',
         'pricingComponentName' => 'Account fee',
         'organizationID' => 'ORG-BE199668-332C-4EFD-B05D-D705142C',
         'value' => 1,
         'inferredFromDefault' => true,
      )),
    ),
     'paymentMethodSubscriptionLinks' => 
    array (
    ),
     'fixedTerms' => 
    array (
    ),
     'executionInfo' => 
    Bf_ExecutionResponse::__set_state(array(
       'invoiceID' => 'INV-25792074-EFDD-46FC-81E9-CB514B3E',
       'invoiceType' => 'FinalArrears',
       'currency' => 'USD',
       'outstandingAmount' => 0,
       'executions' => 
      array (
      ),
    )),
     'currentTime' => '2016-07-28T11:29:07Z',
     'timeOffset' => 0,
     'paymentTerms' => 0,
  )),
)

More accurately unserialize "add charge to Invoice/Subscription" responses

01 Jul 16:57

Choose a tag to compare

The upstream version of the BillForward API will return to requests for "add charge to Invoice/Subscription" a response typed as Bf_AddChargeResponse.

Version 15 of the BF PHP SDK assumed that the response for:

  • Bf_Subscription->charge()
  • Bf_Invoice->charge()

Would be an array of Bf_SubscriptionCharge.

Version 16 of the BF PHP SDK will assume that the response for:

  • Bf_Subscription->charge()
  • Bf_Invoice->charge()

will be a Bf_AddChargeResponse.

Both versions of the BF PHP SDK will happily unserialize responses from either version of BIllForward (no exception will occur). The response would be incorrectly-typed, but as these are just associative arrays, it is still possible to access arbitrary properties of the entity as usual.

Timer Amendments

29 Jun 12:46

Choose a tag to compare

Support for timer amendments

<?php
$timerAmendment = $sub->setupTimer(
    array(
        'duration' => 1,
        'period' => 'Minutes',
        'direction' => 'Before',
        'event' => 'PeriodEnd'
        )
    );
var_export($timerAmendment);

// output example:
Bf_TimerAmendment::__set_state(array(
   '@type' => 'TimerAmendment',
   'created' => '2016-06-29T12:44:36Z',
   'changedBy' => 'CF0CBF36-922B-403F-AA95-22BAD9960BF3',
   'updated' => '2016-06-29T12:44:36Z',
   'id' => '209C846D-859C-46DF-8982-1DB0BF28C1B9',
   'organizationID' => 'ORG-BE199668-332C-4EFD-B05D-D705142C',
   'subscriptionID' => 'SUB-E080141E-CDF3-4747-9562-6739E947',
   'actioningTime' => '2016-06-29T12:46:09Z',
   'state' => 'Pending',
   'deleted' => false,
   'entityID' => 'SUB-E080141E-CDF3-4747-9562-6739E947',
   'entity' => 'Subscription',
   'offsetDuration' => 1,
   'offsetPeriod' => 'Minutes',
   'offsetDirection' => 'Before',
   'originalEventDate' => '2016-06-29T12:47:09Z',
   'timerEvent' => 'PeriodEnd',
))

You will receive a notification like this:

{
    "@type": "notification",
    "created": "2016-06-29T12:34:11Z",
    "id": "45A099B81DC9F60C8E398AFF55E7A3CDBILL",
    "domain": "Amendment",
    "action": "Updated",
    "organizationID": "ORG-BE199668-332C-4EFD-B05D-D705142C",
    "entityID": "531DA203-43D6-4020-8887-AEA1D55C411B",
    "entity": "{\"@type\":\"TimerAmendment\",\"created\":\"2016-06-29T12:33:43Z\",\"changedBy\":\"System\",\"updated\":\"2016-06-29T12:34:10Z\",\"id\":\"531DA203-43D6-4020-8887-AEA1D55C411B\",\"organizationID\":\"ORG-BE199668-332C-4EFD-B05D-D705142C\",\"subscriptionID\":\"SUB-E080141E-CDF3-4747-9562-6739E947\",\"amendmentType\":\"Timer\",\"actioningTime\":\"2016-06-29T12:34:09Z\",\"actionedTime\":\"2016-06-29T12:34:10Z\",\"state\":\"Succeeded\",\"deleted\":false,\"entityID\":\"SUB-E080141E-CDF3-4747-9562-6739E947\",\"entity\":\"Subscription\",\"offsetDuration\":1,\"offsetPeriod\":\"Minutes\",\"offsetDirection\":\"Before\",\"originalEventDate\":\"2016-06-29T12:35:09Z\",\"timerEvent\":\"PeriodEnd\"}",
    "changes": "{\"auditFieldChanges\":[{\"attributeName\":\"actionedTime\",\"newValue\":\"2016-06-29T12:34:10Z\"},{\"attributeName\":\"internalState\",\"previousValue\":\"Pending\",\"newValue\":\"Succeeded\"},{\"attributeName\":\"state\",\"previousValue\":\"Pending\",\"newValue\":\"Succeeded\"}]}"
}

Hotfix for `v15.2016.113.1`'s `makeUTCTimeFromBillForwardDate()`

03 May 15:47

Choose a tag to compare

v15.2016.113.1 refactored makeUTCTimeFromBillForwardDate() to move it into a utility class.
However this refactor did not correctly pass args through — meaning that makeUTCTimeFromBillForwardDate() would always use NULL as its date (i.e. "now").
This hotfix corrects that.

Revert to classic `Bf_Subscription->changeValues()`

22 Apr 13:39

Choose a tag to compare

API support is not yet released for the new "batch changes values" endpoint, so I am rolling back to the classic implementation.

Fix for v14's unserialization of `Bf_MigrationResponse`

22 Apr 13:10

Choose a tag to compare

Bug fix

v14 enforced that metadata field upon Bf_Subscription needs to be an associative array.

The current API does not successfully post-process the JSON string metadata upon Subscription entities nested within a MigrationResponse.

In this version of the PHP SDK: we compensate for metadata fields that escape post-processing.

Refactor

Since a lot of "utility" methods were living on Bf_BillingEntity, I have now introduced a real utility class (Bf_Util) for utility methods. It provides:

  • Bf_Util::jsonStrToAssociativeArray($jsonStr)
  • Bf_Util::associativeArrayToJsonStr($arr, $optionsBitMask = 0)
  • Bf_Util::makeBillForwardDate($time)
  • Bf_Util::makeUTCTimeFromBillForwardDate($date)

Bf_BillingEntity::getJson() and Bf_BillingEntity::printJson() now call out to Bf_Util::associativeArrayToJsonStr($arr, $optionsBitMask = 0). This means they now enjoy the exact same serialization code as does BillForwardClient.

Compatibility

Bf_BillingEntity::getJson() and Bf_BillingEntity::printJson() now support PHP 5.3 (because they now recruit JSON_PRETTY_PRINT only if available).

JSON encoding of billing entities will now use PHP 5.4's JSON_UNESCAPED_UNICODE option if available, but fallback to devilan's polyfill otherwise.

Deprecation

The following utility functions still exist upon Bf_BillingEntity, but forward the call to Bf_Util. We recommend switching to Bf_Util if you are relying upon these functions from Bf_BillingEntity presently:

  • Bf_BillingEntity::makeBillForwardDate($time)
  • Bf_BillingEntity::makeUTCTimeFromBillForwardDate($date)

Any breaking changes?

No breaking changes are expected. All unit tests continue to pass.

This is a major version release simply because it significantly touches important parts of unserialization — and therefore carries enough risk that it merits a retest of the use cases in your BillForward integration.

Fix for reflection

22 Apr 11:21

Choose a tag to compare

Use of protected static function callFunctionAbstractAndPageThrough() is now fixed: it had failed due to the renaming of an argument upon which reflection relied.

Semantic errors

21 Apr 16:30

Choose a tag to compare

Features

Added to Bf_APIException the following methods:

  • getHttpCode()

Added to Bf_NoAPIResponseException (extends Bf_APIException) the following methods:

  • getRawResponse()

Bf_APIErrorResponseException (extends Bf_APIException) the following methods:

  • getBFErrorType()
  • getBFErrorMessage()
  • getBFErrorParameters()
    • usually NULL, but in the case of ValidationError at least: yields a list of which fields upon your entity suffered from validation problems.
  • getBFErrorMessage()
  • getRawResponse()
  • getParsedResponse()

Example usage

<?php
try {
    Bf_Account::create(new Bf_Account(array(
        'profile' => new Bf_Profile(array(
            'email' => 'notemail'
            ))
        )));
} catch(Bf_APIErrorResponseException $err) {
    echo "Exception message:\n";
    echo $err->getMessage();
    echo "\n\nHTTP code:\n";
    echo $err->getHttpCode();
    echo "\n\nRaw response:\n";
    echo $err->getRawResponse();
    echo "\n\nParsed response:\n";
    var_export($err->getParsedResponse());
    echo "\n\nBF Error Type:\n";
    echo $err->getBFErrorType();
    echo "\n\nBF Error Message:\n";
    echo $err->getBFErrorMessage();
    echo "\n\nBF Error Parameters:\n";
    var_export($err->getBFErrorParameters());
    echo "\n";
}

Output:

Exception message:

====
Received error from API.
Error code:     <400>
Error type:     <ValidationError>
Error message:  <Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address
>.
====

HTTP code:
400

Raw response:
{
   "errorType" : "ValidationError",
   "errorMessage" : "Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address\n",
   "errorCode" : 0,
   "errorParameters" : [ "email" ]
}

Parsed response:
array (
  'errorType' => 'ValidationError',
  'errorMessage' => 'Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address
',
  'errorCode' => 0,
  'errorParameters' => 
  array (
    0 => 'email',
  ),
)

BF Error Type:
ValidationError

BF Error Message:
Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address


BF Error Parameters:
array (
  0 => 'email',
)

UTF-8 support, query params on POST/PUT/DELETE requests, preflight potentially removed on GETs, batch features

20 Apr 17:42

Choose a tag to compare

Refactor

  • Substantially refactored the BillForwardClient
    • Reduced code duplication
    • Undid the conflation of "query params" with "payload"; you can now have both
  • All code paths involved in GET/POST/PUT/DELETE now pass at least an empty queryParams object.
  • DELETE supports a JSON payload. Currently unused by the BillForward backend.

Bug fixes

  • Empty POST/PUT/DELETE payloads are now interpreted as JSON {} instead of []
  • metadata entity upon Account, Product, ProductRatePlan and Subscription is now interpreted as JSON {} instead of []
  • Replaced fake UTF-8 support with actual end-to-end UTF-8 support.
    • BillForward backend uses MySQL utf8 charset — so in practice only 3-byte UTF-8 can be supported.

Performance

  • For GET requests (or any request that happens to have no payload): I have moved authentication into query params instead of the authentication header. I believe this way I can avoid an OPTIONS preflight.

Features

  • POST/PUT/DELETE can now have query params
    • Enables use of the new void_charges query param on DELETE /invoices/{invoice-ID}
  • Initial support for batch endpoints (not final, but it's reasonably likely it will stay something like this)
    • Add charges
    • Add pricing component values

Tests

  • All unit tests passing on latest version of API
    • Honors newer constraints on name uniqueness
    • Honors new constraint on profile updates
    • Removes test upon roles of Account, because these are no longer exposed